Ec2Key   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Test Coverage

Coverage 96.36%

Importance

Changes 0
Metric Value
eloc 72
c 0
b 0
f 0
dl 0
loc 189
ccs 53
cts 55
cp 0.9636
rs 10
wmc 14

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getX() 0 3 1
A getY() 0 3 1
A asDer() 0 11 1
A fromCborData() 0 21 1
A getUncompressedCoordinates() 0 6 1
A getCbor() 0 11 1
A verifySignature() 0 4 1
A algorithmSupported() 0 3 1
A getCurve() 0 3 1
A getCurveOid() 0 3 1
A __construct() 0 17 4
1
<?php
2
3
namespace MadWizard\WebAuthn\Crypto;
4
5
use MadWizard\WebAuthn\Exception\UnsupportedException;
6
use MadWizard\WebAuthn\Exception\WebAuthnException;
7
use MadWizard\WebAuthn\Format\ByteBuffer;
8
use MadWizard\WebAuthn\Format\CborEncoder;
9
use MadWizard\WebAuthn\Format\CborMap;
10
use MadWizard\WebAuthn\Format\DataValidator;
11
12
class Ec2Key extends CoseKey // TODO exceptions
13
{
14
    /**
15
     * @var ByteBuffer
16
     */
17
    private $x;
18
19
    /**
20
     * @var ByteBuffer
21
     */
22
    private $y;
23
24
    /**
25
     * @var int
26
     */
27
    private $curve;
28
29
    /**
30
     * NIST P-256 also known as secp256r1.
31
     *
32
     * @see https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
33
     */
34
    public const CURVE_P256 = 1;
35
36
    /**
37
     * NIST P-384 also known as secp384r1.
38
     *
39
     * @see https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
40
     */
41
    public const CURVE_P384 = 2;
42
43
    /**
44
     * NIST P-521 also known as secp521r1.
45
     *
46
     * @see https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
47
     */
48
    public const CURVE_P521 = 3;
49
50
    private const SUPPORTED_CURVES = [
51
        self::CURVE_P256,
52
        self::CURVE_P384,
53
        self::CURVE_P521,
54
    ];
55
56
    /**
57
     *  EC identifier.
58
     */
59
    private const KTP_CRV = -1;
60
61
    /**
62
     * X-coordinate.
63
     */
64
    private const KTP_X = -2;
65
66
    /**
67
     * Y-coordinate.
68
     */
69
    private const KTP_Y = -3;
70
71
    private const SUPPORTED_ALGORITHMS = [
72
        CoseAlgorithm::ES256,
73
        CoseAlgorithm::ES384,
74
        CoseAlgorithm::ES512,
75
    ];
76
77
    private const CURVE_KEY_LENGTH = [
78
        self::CURVE_P256 => 32,
79
        self::CURVE_P384 => 48,
80
        self::CURVE_P521 => 66,
81
    ];
82
83
    private const CURVE_OID = [
84
        // 1.2.840.10045.3.1.7 NIST P-256 / secp256r1
85
        self::CURVE_P256 => "\x2A\x86\x48\xCE\x3D\x03\x01\x07",
86
87
        // 1.3.132.0.34 NIST P-384 / secp384r1
88
        self::CURVE_P384 => "\x2B\x81\x04\x00\x22",
89
90
        // 1.3.132.0.35 NIST P-521 / secp521r1
91
        self::CURVE_P521 => "\x2B\x81\x04\x00\x23",
92
    ];
93
94 37
    public function __construct(ByteBuffer $x, ByteBuffer $y, int $curve, int $algorithm)
95
    {
96 37
        parent::__construct($algorithm);
97
98 37
        if (!in_array($curve, self::SUPPORTED_CURVES, true)) {
99
            throw new UnsupportedException('Unsupported curve');
100
        }
101
102 37
        $coordLength = self::CURVE_KEY_LENGTH[$curve];
103
104 37
        if ($x->getLength() !== $coordLength || $y->getLength() !== $coordLength) {
105
            throw new WebAuthnException(sprintf('Expecting length %d for x and y', $coordLength));
106
        }
107
108 37
        $this->x = $x;
109 37
        $this->y = $y;
110 37
        $this->curve = $curve;
111 37
    }
112
113 18
    public static function fromCborData(CborMap $data): Ec2Key
114
    {
115
        // Note: leading zeroes in X and Y coordinates are preserved in CBOR
116
        // See RFC8152 13.1.1. Double Coordinate Curves
117 18
        DataValidator::checkMap(
118 18
            $data,
119
            [
120 18
                self::COSE_KEY_PARAM_KTY => 'integer',
121 18
                self::KTP_CRV => 'integer',
122 18
                self::COSE_KEY_PARAM_ALG => 'integer',
123 18
                self::KTP_X => ByteBuffer::class,
124 18
                self::KTP_Y => ByteBuffer::class,
125
            ]
126
        );
127
128 16
        $curve = $data->get(self::KTP_CRV);
129 16
        $x = $data->get(self::KTP_X);
130 16
        $y = $data->get(self::KTP_Y);
131 16
        $algorithm = $data->get(self::COSE_KEY_PARAM_ALG);
132
133 16
        return new Ec2Key($x, $y, $curve, $algorithm);
134
    }
135
136 8
    public function getX(): ByteBuffer
137
    {
138 8
        return $this->x;
139
    }
140
141 8
    public function getY(): ByteBuffer
142
    {
143 8
        return $this->y;
144
    }
145
146 3
    public function getCurve(): int
147
    {
148 3
        return $this->curve;
149
    }
150
151 11
    public function asDer(): string
152
    {
153
        // DER encoded P256 curve
154
        return
155 11
            Der::sequence(
156 11
                Der::sequence(
157 11
                    Der::oid("\x2A\x86\x48\xCE\x3D\x02\x01") . // OID 1.2.840.10045.2.1 ecPublicKey
158 11
                    Der::oid($this->getCurveOid())
159
                ) .
160 11
                Der::bitString(
161 11
                    $this->getUncompressedCoordinates()->getBinaryString()
162
                )
163
            );
164
    }
165
166 11
    private function getCurveOid(): string
167
    {
168 11
        return self::CURVE_OID[$this->curve];
169
    }
170
171 1
    public function getCbor(): ByteBuffer
172
    {
173
        $map = [
174 1
            self::COSE_KEY_PARAM_KTY => self::COSE_KTY_EC2,
175 1
            self::COSE_KEY_PARAM_ALG => $this->getAlgorithm(),
176 1
            self::KTP_CRV => $this->curve,
177 1
            self::KTP_X => $this->x,
178 1
            self::KTP_Y => $this->y,
179
        ];
180
181 1
        return new ByteBuffer(CborEncoder::encodeMap(CborMap::fromArray($map)));
182
    }
183
184 11
    public function getUncompressedCoordinates(): ByteBuffer
185
    {
186
        $data = "\x04" . // ECC uncompressed key format
187 11
            $this->x->getBinaryString() .
188 11
            $this->y->getBinaryString();
189 11
        return new ByteBuffer($data);
190
    }
191
192 8
    public function verifySignature(ByteBuffer $data, ByteBuffer $signature): bool
193
    {
194 8
        $verifier = new OpenSslVerifier($this->getAlgorithm());
195 8
        return $verifier->verify($data->getBinaryString(), $signature->getBinaryString(), $this->asPem());
196
    }
197
198 37
    protected function algorithmSupported(int $algorithm): bool
199
    {
200 37
        return in_array($algorithm, self::SUPPORTED_ALGORITHMS, true);
201
    }
202
}
203