Passed
Pull Request — master (#18)
by
unknown
08:11
created

X5cParameterReader::keyFromCert()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7.8934

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 2
b 0
f 0
nc 7
nop 2
dl 0
loc 31
ccs 14
cts 19
cp 0.7368
crap 7.8934
rs 8.8333
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
        openssl_free_key($pkey);
83 3
        if ($details === false) {
84
            throw new ParseException('Failed to get X5C key details.');
85
        }
86
87 3
        $algInfo = self::ALG_INFO[$algorithm];
88
89 3
        $type = $details['type'];
90 3
        if ($type !== $algInfo['keyType']) {
91
            throw new UnsupportedException(sprintf('Unsupported key type %d.', $details['type']));
92
        }
93
94 3
        if ($type === OPENSSL_KEYTYPE_EC) {
95 1
            if ($details['ec']['curve_name'] !== $algInfo['curveName']) {
96
                throw new UnsupportedException(sprintf('Mismatching curve type %s', $details['ec']['curve_name']));
97
            }
98 1
            return new Ec2Key(new ByteBuffer($details['ec']['x']), new ByteBuffer($details['ec']['y']), $algInfo['curve'], $algInfo['coseAlg']);
99
        }
100 2
        if ($type === OPENSSL_KEYTYPE_RSA) {
101 2
            return new RsaKey(new ByteBuffer($details['rsa']['n']), new ByteBuffer($details['rsa']['e']), $algInfo['coseAlg']);
102
        }
103
104
        // @phpstan-ignore-next-line
105
        throw new UnsupportedException('Mising type handler');
106
    }
107
}
108