Passed
Push — master ( 2515b7...d118c3 )
by Thomas
02:22
created

OkpKey::algorithmSupported()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace MadWizard\WebAuthn\Crypto;
4
5
use MadWizard\WebAuthn\Exception\UnsupportedException;
6
use MadWizard\WebAuthn\Exception\VerificationException;
7
use MadWizard\WebAuthn\Exception\WebAuthnException;
8
use MadWizard\WebAuthn\Format\ByteBuffer;
9
use MadWizard\WebAuthn\Format\CborEncoder;
10
use MadWizard\WebAuthn\Format\CborMap;
11
use MadWizard\WebAuthn\Format\DataValidator;
12
use SodiumException;
13
14
class OkpKey extends CoseKey
15
{
16
    public const CURVE_ED25519 = 6;
17
18
    private const KTP_CRV = -1;
19
20
    private const KTP_X = -2;
21
22
    private const SUPPORTED_CURVES = [self::CURVE_ED25519];
23
24
    private const CURVE_KEY_LENGTH = [
25
        self::CURVE_ED25519 => 32,
26
    ];
27
28
    /**
29
     * @var ByteBuffer X coordinate of key
30
     */
31
    private $x;
32
33
    /**
34
     * @var int
35
     */
36
    private $curve;
37
38 5
    public function __construct(ByteBuffer $x, int $curve, int $algorithm)
39
    {
40 5
        parent::__construct($algorithm);
41
42 5
        if (!in_array($curve, self::SUPPORTED_CURVES, true)) {
43
            throw new UnsupportedException('Unsupported curve');
44
        }
45
46 5
        $coordLength = self::CURVE_KEY_LENGTH[$curve];
47
48 5
        if ($x->getLength() !== $coordLength) {
49
            throw new WebAuthnException(sprintf('Expecting length %d for x', $coordLength));
50
        }
51 5
        $this->x = $x;
52 5
        $this->curve = $curve;
53 5
    }
54
55 1
    public function getCbor(): ByteBuffer
56
    {
57
        $map = [
58 1
            self::COSE_KEY_PARAM_KTY => self::COSE_KTY_OKP,
59 1
            self::COSE_KEY_PARAM_ALG => $this->getAlgorithm(),
60 1
            self::KTP_CRV => $this->curve,
61 1
            self::KTP_X => $this->x,
62
        ];
63
64 1
        return new ByteBuffer(CborEncoder::encodeMap(CborMap::fromArray($map)));
65
    }
66
67 2
    public function getCurve(): int
68
    {
69 2
        return $this->curve;
70
    }
71
72 2
    public function getX(): ByteBuffer
73
    {
74 2
        return $this->x;
75
    }
76
77 2
    public function verifySignature(ByteBuffer $data, ByteBuffer $signature): bool
78
    {
79 2
        if ($this->curve === self::CURVE_ED25519) {
80
            try {
81 2
                return sodium_crypto_sign_verify_detached(
82 2
                    $signature->getBinaryString(),
83 2
                    $data->getBinaryString(),
84 2
                    $this->x->getBinaryString()
85
                );
86 1
            } catch (SodiumException $e) {
87 1
                throw new VerificationException('Failed to verify signature: ' . $e->getMessage(), 0, $e);
88
            }
89
        }
90
        throw new UnsupportedException('Unsupported curve');
91
    }
92
93 5
    protected function algorithmSupported(int $algorithm): bool
94
    {
95 5
        return $algorithm === CoseAlgorithm::EDDSA;
96
    }
97
98 1
    public function asDer(): string
99
    {
100 1
        if ($this->curve !== self::CURVE_ED25519) {
101
            throw new UnsupportedException('Unsupported curve.');
102
        }
103
        return
104 1
            Der::sequence(
105 1
                Der::sequence(
106 1
                    Der::oid("\x2B\x65\x70") // OID 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm)
107
                ) .
108 1
                Der::bitString(
109 1
                    $this->x->getBinaryString()
110
                )
111
            );
112
    }
113
114 3
    public static function fromCborData(CborMap $data): OkpKey
115
    {
116
        // Note: leading zeroes in X and Y coordinates are preserved in CBOR
117
        // See RFC8152 13.1.1. Double Coordinate Curves
118 3
        DataValidator::checkMap(
119 3
            $data,
120
            [
121 3
                self::COSE_KEY_PARAM_KTY => 'integer',
122 3
                self::KTP_CRV => 'integer',
123 3
                self::COSE_KEY_PARAM_ALG => 'integer',
124 3
                self::KTP_X => ByteBuffer::class,
125
            ]
126
        );
127
128 1
        $curve = $data->get(self::KTP_CRV);
129 1
        $x = $data->get(self::KTP_X);
130 1
        $algorithm = $data->get(self::COSE_KEY_PARAM_ALG);
131
132 1
        return new OkpKey($x, $curve, $algorithm);
133
    }
134
}
135