|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace MadWizard\WebAuthn\Pki\Jwt; |
|
4
|
|
|
|
|
5
|
|
|
use MadWizard\WebAuthn\Crypto\Der; |
|
6
|
|
|
use MadWizard\WebAuthn\Exception\ParseException; |
|
7
|
|
|
use MadWizard\WebAuthn\Exception\VerificationException; |
|
8
|
|
|
use MadWizard\WebAuthn\Format\ByteBuffer; |
|
9
|
|
|
|
|
10
|
|
|
final class JwtValidator implements JwtValidatorInterface |
|
11
|
|
|
{ |
|
12
|
|
|
private const ALG_INFO = |
|
13
|
|
|
[ |
|
14
|
|
|
'ES256' => ['convert' => true, 'sigComponentLen' => 32], |
|
15
|
|
|
'ES384' => ['convert' => true, 'sigComponentLen' => 48], |
|
16
|
|
|
'ES512' => ['convert' => true, 'sigComponentLen' => 66], |
|
17
|
|
|
'RS256' => ['convert' => false], |
|
18
|
|
|
'RS384' => ['convert' => false], |
|
19
|
|
|
'RS512' => ['convert' => false], |
|
20
|
|
|
]; |
|
21
|
|
|
|
|
22
|
7 |
|
public function __construct() |
|
23
|
|
|
{ |
|
24
|
7 |
|
} |
|
25
|
|
|
|
|
26
|
5 |
|
public function validate(JwtInterface $token, ValidationContext $context): array |
|
27
|
|
|
{ |
|
28
|
|
|
// TODO: validate other header items |
|
29
|
5 |
|
$header = $token->getHeader(); |
|
30
|
5 |
|
$alg = $this->validateAlgorithm($header, $context); |
|
31
|
|
|
|
|
32
|
4 |
|
$asn1Sig = $this->convertSignature($token->getSignature(), $alg); |
|
33
|
4 |
|
if (!$context->getKey()->verifySignature($token->getSignedData(), $asn1Sig)) { |
|
34
|
2 |
|
throw new VerificationException('Invalid signature.'); |
|
35
|
|
|
} |
|
36
|
|
|
/* TODO |
|
37
|
|
|
$now = $context->getReferenceUnixTime(); |
|
38
|
|
|
|
|
39
|
|
|
$exp = $header['exp'] ?? null; |
|
40
|
|
|
if ($exp !== null) { |
|
41
|
|
|
if (!is_int($exp)) { |
|
42
|
|
|
throw new VerificationException('Invalid "exp" header value.'); |
|
43
|
|
|
} |
|
44
|
|
|
} |
|
45
|
|
|
*/ |
|
46
|
2 |
|
return $token->getBody(); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
4 |
|
private function convertSignature(ByteBuffer $signature, string $algorithm): ByteBuffer |
|
50
|
|
|
{ |
|
51
|
4 |
|
$algInfo = self::ALG_INFO[$algorithm]; |
|
52
|
4 |
|
if (!$algInfo['convert']) { |
|
53
|
2 |
|
return $signature; |
|
54
|
|
|
} |
|
55
|
2 |
|
$componentLen = $algInfo['sigComponentLen']; |
|
56
|
2 |
|
if ($signature->getLength() !== ($componentLen * 2)) { |
|
57
|
|
|
throw new ParseException(sprintf('Invalid signature length %d.', $signature->getLength())); |
|
58
|
|
|
} |
|
59
|
2 |
|
$r = $signature->getBytes(0, $componentLen); |
|
60
|
2 |
|
$s = $signature->getBytes($componentLen, $componentLen); |
|
61
|
2 |
|
return new ByteBuffer(Der::sequence(Der::unsignedInteger($r) . Der::unsignedInteger($s))); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
5 |
|
private function validateAlgorithm(array $header, ValidationContext $ctx): string |
|
65
|
|
|
{ |
|
66
|
5 |
|
$alg = $header['alg'] ?? null; |
|
67
|
5 |
|
if (in_array($alg, $ctx->getAllowedAlgorithms(), true)) { |
|
68
|
4 |
|
return $alg; |
|
|
|
|
|
|
69
|
|
|
} |
|
70
|
1 |
|
throw new VerificationException('Algorithm not allowed.'); |
|
71
|
|
|
} |
|
72
|
|
|
} |
|
73
|
|
|
|