X5cParameterReader   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Test Coverage

Coverage 83.72%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 49
c 3
b 1
f 0
dl 0
loc 94
ccs 36
cts 43
cp 0.8372
rs 10
wmc 18

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getX5cParameter() 0 17 5
A extractChain() 0 23 5
B keyFromCert() 0 33 8
1
<?php
2
3
namespace MadWizard\WebAuthn\Pki\Jwt;
4
5
use MadWizard\WebAuthn\Crypto\CoseAlgorithm;
6
use MadWizard\WebAuthn\Crypto\CoseKeyInterface;
7
use MadWizard\WebAuthn\Crypto\Ec2Key;
8
use MadWizard\WebAuthn\Crypto\RsaKey;
9
use MadWizard\WebAuthn\Exception\ParseException;
10
use MadWizard\WebAuthn\Exception\UnsupportedException;
11
use MadWizard\WebAuthn\Format\ByteBuffer;
12
use MadWizard\WebAuthn\Pki\X509Certificate;
13
14
final class X5cParameterReader
15
{
16
    private const ALG_INFO =
17
        [
18
            'ES256' => ['keyType' => OPENSSL_KEYTYPE_EC, 'curveName' => 'prime256v1', 'curve' => Ec2Key::CURVE_P256, 'coseAlg' => CoseAlgorithm::ES256],
19
            'ES384' => ['keyType' => OPENSSL_KEYTYPE_EC, 'curveName' => 'secp384r1', 'curve' => Ec2Key::CURVE_P384, 'coseAlg' => CoseAlgorithm::ES384],
20
            'ES512' => ['keyType' => OPENSSL_KEYTYPE_EC, 'curveName' => 'secp521r1', 'curve' => Ec2Key::CURVE_P521, 'coseAlg' => CoseAlgorithm::ES512],
21
            'RS256' => ['keyType' => OPENSSL_KEYTYPE_RSA, 'coseAlg' => CoseAlgorithm::RS256],
22
            'RS384' => ['keyType' => OPENSSL_KEYTYPE_RSA, 'coseAlg' => CoseAlgorithm::RS256],
23
            'RS512' => ['keyType' => OPENSSL_KEYTYPE_RSA, 'coseAlg' => CoseAlgorithm::RS512],
24
        ];
25
26 6
    public static function getX5cParameter(JwtInterface $token): ?X5cParameter
27
    {
28 6
        $header = $token->getHeader();
29
30 6
        $chain = self::extractChain($header);
31 5
        if ($chain === null) {
32 1
            return null;
33
        }
34
35 4
        $alg = $header['alg'] ?? null;
36 4
        if (!is_string($alg) || !isset(self::ALG_INFO[$alg])) {
37 1
            throw new UnsupportedException(sprintf('Unsupported algorithm %s.', is_string($alg) ? $alg : '?'));
38
        }
39
        // Note: chain is never empty here
40 3
        $key = self::keyFromCert($chain[0], $alg);
41
42 3
        return new X5cParameter($chain, $key);
43
    }
44
45
    /**
46
     * @return X509Certificate[]|null
47
     *
48
     * @throws ParseException
49
     */
50 6
    private static function extractChain(array $header): ?array
51
    {
52 6
        $x5c = $header['x5c'] ?? null;
53 6
        if ($x5c === null) {
54 1
            return null;
55
        }
56 5
        if (!is_array($x5c)) {
57 1
            throw new ParseException('Expecting array for x5c.');
58
        }
59
        /**
60
         * @var X509Certificate[] $chain
61
         */
62
        $chain = array_map(function ($x) {
63 4
            if (!is_string($x)) {
64
                throw new ParseException('Expecting array of strings for X5C');
65
            }
66 4
            return X509Certificate::fromBase64($x);
67 4
        }, $x5c);
68
69 4
        if (count($chain) === 0) {
70
            return null;
71
        }
72 4
        return $chain;
73
    }
74
75 3
    private static function keyFromCert(X509Certificate $cert, string $algorithm): CoseKeyInterface
76
    {
77 3
        $pkey = openssl_pkey_get_public($cert->asPem());
78 3
        if ($pkey === false) {
79
            throw new ParseException('Failed to parse X5C certificate.');
80
        }
81 3
        $details = openssl_pkey_get_details($pkey);
82 3
        if (PHP_VERSION_ID < 80000) {
83 3
            openssl_free_key($pkey);
84
        }
85 3
        if ($details === false) {
86
            throw new ParseException('Failed to get X5C key details.');
87
        }
88
89 3
        $algInfo = self::ALG_INFO[$algorithm];
90
91 3
        $type = $details['type'];
92 3
        if ($type !== $algInfo['keyType']) {
93
            throw new UnsupportedException(sprintf('Unsupported key type %d.', $details['type']));
94
        }
95
96 3
        if ($type === OPENSSL_KEYTYPE_EC) {
97 1
            if ($details['ec']['curve_name'] !== $algInfo['curveName']) {
98
                throw new UnsupportedException(sprintf('Mismatching curve type %s', $details['ec']['curve_name']));
99
            }
100 1
            return new Ec2Key(new ByteBuffer($details['ec']['x']), new ByteBuffer($details['ec']['y']), $algInfo['curve'], $algInfo['coseAlg']);
101
        }
102 2
        if ($type === OPENSSL_KEYTYPE_RSA) {
103 2
            return new RsaKey(new ByteBuffer($details['rsa']['n']), new ByteBuffer($details['rsa']['e']), $algInfo['coseAlg']);
104
        }
105
106
        // @phpstan-ignore-next-line
107
        throw new UnsupportedException('Mising type handler');
108
    }
109
}
110