Lines 56.14% 32 / 57
Methods 50.00% 1 / 2
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 fromCoseToPkcs 48.97% 24 / 49 0.00% 0 / 1 197.69
 publicKeyToPem 100.00% 8 / 8 100.00% 1 / 1 3
27class PublicKeyConverter
28{
29    private const int ES256 = -7;
30    private const int RS256 = -257;
31
32    /**
33     * Convert COSE ECDHA to PKCS
34     *
35     * @throws Exception
36     * @throws \Exception
37     */
38    public static function fromCoseToPkcs(string $binary): ?string
39    {
40        $cosePubKey = CBOREncoder::decode($binary);
41
42        if (!is_array($cosePubKey)) {
43            return null;
44        }
45
46        if (!array_key_exists(3, $cosePubKey) || $cosePubKey[3] === null) { /* cose_alg */
47            return null;
48        }
49
50        switch ($cosePubKey[3]) {
51            case self::ES256:
52                /* COSE Alg: ECDSA w/ SHA-256 */
53                if (!array_key_exists(-1, $cosePubKey) || $cosePubKey[-1] === null) { /* cose_crv */
54                    throw new Exception('Cannot decode key response for curve');
55                }
56
57                if (!array_key_exists(-2, $cosePubKey) || $cosePubKey[-2] === null) { /* cose_crv_x */
58                    throw new Exception('Cannot decode key response for x coordinate');
59                }
60
61                if ($cosePubKey[-1] !== 1) { /* cose_crv_P256 */
62                    throw new Exception('Cannot decode key response for curve P256');
63                }
64
65                if (!array_key_exists(-2, $cosePubKey) || $cosePubKey[-2] === null) { /* cose_crv_x */
66                    throw new Exception('x coordinate for curve missing');
67                }
68
69                if (!array_key_exists(1, $cosePubKey) || $cosePubKey[1] === null) { /* cose_kty */
70                    throw new Exception('Cannot decode key response for key type');
71                }
72
73                if (!array_key_exists(-3, $cosePubKey) || $cosePubKey[-3] === null) { /* cose_crv_y */
74                    throw new Exception('Cannot decode key response for y coordinate');
75                }
76
77                if (!array_key_exists(-3, $cosePubKey) || $cosePubKey[-3] === null) { /* cose_crv_y */
78                    throw new Exception('y coordinate for curve missing');
79                }
80
81                if ($cosePubKey[1] !== 2) { /* cose_kty_ec2 */
82                    throw new Exception('Cannot decode key response for key type EC2');
83                }
84
85                $xEntry = $cosePubKey[-2];
86                $yEntry = $cosePubKey[-3];
87                if (
88                    !is_object($xEntry)
89                    || !method_exists($xEntry, 'get_byte_string')
90                    || !is_object($yEntry)
91                    || !method_exists($yEntry, 'get_byte_string')
92                ) {
93                    throw new Exception('Cannot decode key response for x or y coordinate');
94                }
95
96                $x = (string) $xEntry->get_byte_string();
97                $y = (string) $yEntry->get_byte_string();
98                if (strlen($x) !== 32 || strlen($y) !== 32) {
99                    throw new Exception('Cannot decode key response for x or y coordinate');
100                }
101
102                return self::publicKeyToPem("\x04" . $x . $y);
103            case self::RS256:
104                if (!array_key_exists(-2, $cosePubKey) || $cosePubKey[-2] === null) {
105                    throw new Exception('RSA Exponent missing');
106                }
107
108                if (!array_key_exists(-1, $cosePubKey) || $cosePubKey[-1] === null) {
109                    throw new Exception('RSA Modulus missing');
110                }
111
112                $exponentEntry = $cosePubKey[-2];
113                $modulusEntry = $cosePubKey[-1];
114                if (
115                    !is_object($exponentEntry)
116                    || !method_exists($exponentEntry, 'get_byte_string')
117                    || !is_object($modulusEntry)
118                    || !method_exists($modulusEntry, 'get_byte_string')
119                ) {
120                    throw new Exception('Cannot decode key response for RSA exponent or modulus');
121                }
122
123                $e = new BigInteger(bin2hex((string) $exponentEntry->get_byte_string()), 16);
124                $n = new BigInteger(bin2hex((string) $modulusEntry->get_byte_string()), 16);
125                return (string) PublicKeyLoader::load(['e' => $e, 'n' => $n]);
126            default:
127                return null;
128        }
129    }
130
131    private static function publicKeyToPem(string $key): ?string
132    {
133        if (strlen($key) !== 65 || $key[0] !== "\x04") {
134            return null;
135        }
136
137        $der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
138        $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
139        $der .= "\x00" . $key;
140        $pem = "-----BEGIN PUBLIC KEY-----\x0A";
141        $pem .= chunk_split(string: base64_encode(string: $der), length: 64);
142
143        return $pem . "-----END PUBLIC KEY-----\x0A";
144    }
145}