Passed
Pull Request — master (#23)
by
unknown
08:19
created

CoseKey::createKey()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 12
ccs 7
cts 8
cp 0.875
crap 4.0312
rs 10
1
<?php
2
3
namespace MadWizard\WebAuthn\Crypto;
4
5
use MadWizard\WebAuthn\Exception\DataValidationException;
6
use MadWizard\WebAuthn\Exception\WebAuthnException;
7
use MadWizard\WebAuthn\Format\ByteBuffer;
8
use MadWizard\WebAuthn\Format\CborDecoder;
9
use MadWizard\WebAuthn\Format\CborMap;
10
use MadWizard\WebAuthn\Format\DataValidator;
11
12
abstract class CoseKey implements CoseKeyInterface
13
{
14
    /**
15
     * OKP key type.
16
     */
17
    protected const COSE_KTY_OKP = 1;
18
19
    /**
20
     * EC2 key type.
21
     */
22
    protected const COSE_KTY_EC2 = 2;
23
24
    /**
25
     * RSA key type.
26
     */
27
    protected const COSE_KTY_RSA = 3;
28
29
    /**
30
     * @var int
31
     */
32
    private $algorithm;
33
34
    /**
35
     * Identification of the key type.
36
     *
37
     * @see https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters
38
     */
39
    protected const COSE_KEY_PARAM_KTY = 1;
40
41
    /**
42
     * Key identification value.
43
     *
44
     * @see https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters
45
     */
46
    protected const COSE_KEY_PARAM_KID = 2;
47
48
    /**
49
     * Key usage restriction to this algorithm.
50
     *
51
     * @see https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters
52
     */
53
    protected const COSE_KEY_PARAM_ALG = 3;
54
55
    /**
56
     * CoseKey constructor.
57
     *
58
     * @param int $algorithm IANA COSE Algorithm
59
     *
60
     * @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms
61
     */
62 49
    public function __construct(int $algorithm)
63
    {
64 49
        if (!$this->algorithmSupported($algorithm)) {
65
            throw new WebAuthnException('Algorithm not supported');
66
        }
67 49
        $this->algorithm = $algorithm;
68 49
    }
69
70
    public function toString(): string
71
    {
72
        return $this->getCbor()->getBase64Url();
73
    }
74
75 20
    public static function parseCbor(ByteBuffer $buffer, int $offset = 0, int &$endOffset = null): CoseKey
76
    {
77
        // Fix incorrect EdDSA keys
78 20
        $isIncorrectKey = $buffer->getBytes($offset, 17) == "\xa3\x01\x63\x4f\x4b\x50\x03\x27\x20\x67\x45\x64\x32\x35\x35\x31\x39";
79 20
        if ($isIncorrectKey) {
80
            $buffer = new ByteBuffer($buffer->getBytes(0, $offset) . "\xa4" . $buffer->getBytes($offset + 1, $buffer->getLength() - $offset - 1));
81
        }
82 20
        $data = CborDecoder::decodeInPlace($buffer, $offset, $endOffset);
83
84 20
        if (!$data instanceof CborMap) {
85
            throw new DataValidationException('Failed to decode CBOR encoded COSE key'); // TODO: change exceptions
86
        }
87
88
        // Replace textual kty's with their numeric counterparts
89 20
        if ($data->get(self::COSE_KEY_PARAM_KTY) === 'OKP')
90
            $data->set(self::COSE_KEY_PARAM_KTY, 1);
91 20
        elseif ($data->get(self::COSE_KEY_PARAM_KTY) === 'EC2')
92
            $data->set(self::COSE_KEY_PARAM_KTY, 2);
93
94 20
        DataValidator::checkMap(
95 20
            $data,
96
            [
97 20
                self::COSE_KEY_PARAM_KTY => 'integer',
98
            ],
99 20
            false
100
        );
101
102
        // Fix incorrect EdDSA keys, part 2: x coordinate should be bstr, not array
103 20
        if ($isIncorrectKey && $data->has(-2) && is_array($data->get(-2))) {
104
            $data->set(-2, new ByteBuffer(implode(array_map('chr', $data->get(-2)))));
105
        }
106
107 20
        $keyType = $data->get(self::COSE_KEY_PARAM_KTY);
108 20
        return self::createKey($keyType, $data);
109
    }
110
111 5
    public static function fromString(string $key): CoseKey
112
    {
113 5
        return self::parseCbor(ByteBuffer::fromBase64Url($key));
114
    }
115
116 20
    private static function createKey(int $keyType, CborMap $data): CoseKey
117
    {
118 20
        if ($keyType === self::COSE_KTY_EC2) {
119 16
            return Ec2Key::fromCborData($data);
120
        }
121 4
        if ($keyType === self::COSE_KTY_OKP) {
122 1
            return OkpKey::fromCborData($data);
123
        }
124 3
        if ($keyType === self::COSE_KTY_RSA) {
125 3
            return RsaKey::fromCborData($data);
126
        }
127
        throw new WebAuthnException(sprintf('Key type %d not supported', $keyType));
128
    }
129
130
    abstract public function getCbor(): ByteBuffer;
131
132 19
    public function getAlgorithm(): int
133
    {
134 19
        return $this->algorithm;
135
    }
136
137
    abstract public function verifySignature(ByteBuffer $data, ByteBuffer $signature): bool;
138
139
    abstract protected function algorithmSupported(int $algorithm): bool;
140
141
    abstract public function asDer(): string;
142
143 15
    public function asPem(): string
144
    {
145 15
        return Der::pem('PUBLIC KEY', $this->asDer());
146
    }
147
}
148