Skip to content

Commit 9fdf25d

Browse files
committed
feat: add ed25519 support to JWK (public keys)
Reference documentation: https://datatracker.ietf.org/doc/html/rfc8037
1 parent f9322a3 commit 9fdf25d

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

src/JWK.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use InvalidArgumentException;
77
use UnexpectedValueException;
88

9+
use function base64_encode;
10+
use function in_array;
11+
912
/**
1013
* JSON Web Key implementation, based on this spec:
1114
* https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
@@ -31,6 +34,11 @@ class JWK
3134
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
3235
];
3336

37+
// 'crv' identifier => JWT 'alg'
38+
private const OKP_CURVES = [
39+
'Ed25519' => 'EdDSA',
40+
];
41+
3442
/**
3543
* Parse a set of JWK keys
3644
*
@@ -93,9 +101,10 @@ public static function parseKey(array $jwk): ?Key
93101
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
94102
}
95103

96-
if (!isset($jwk['alg'])) {
97-
// The "alg" parameter is optional in a KTY, but is required for parsing in
98-
// this library. Add it manually to your JWK array if it doesn't already exist.
104+
$ktyRequiringAlg = ['RSA', 'EC'];
105+
if (!isset($jwk['alg']) && in_array($jwk['kty'], $ktyRequiringAlg, true)) {
106+
// The "alg" parameter is optional in a KTY, but is required for parsing certain key
107+
// types in this library. Add it manually to your JWK array if it doesn't already exist.
99108
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
100109
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
101110
}
@@ -137,8 +146,28 @@ public static function parseKey(array $jwk): ?Key
137146

138147
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
139148
return new Key($publicKey, $jwk['alg']);
149+
case 'OKP':
150+
if (isset($jwk['d'])) {
151+
// The key is actually a private key
152+
throw new UnexpectedValueException('Key data must be for a public key');
153+
}
154+
155+
if (! isset($jwk['crv'])) {
156+
throw new UnexpectedValueException('crv not set');
157+
}
158+
159+
if (!isset(self::OKP_CURVES[$jwk['crv']])) {
160+
throw new DomainException('Unrecognised or unsupported OKP curve');
161+
}
162+
163+
if (empty($jwk['x'])) {
164+
throw new UnexpectedValueException('x not set');
165+
}
166+
167+
$publicKey = base64_encode(JWT::urlsafeB64Decode($jwk['x']));
168+
$alg = self::OKP_CURVES[$jwk['crv']];
169+
return new Key($publicKey, $alg);
140170
default:
141-
// Currently only RSA is supported
142171
break;
143172
}
144173

src/JWT.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public static function encode(
198198
*
199199
* @param string $msg The message to sign
200200
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate|array<mixed> $key The secret key.
201-
* @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
201+
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'HS256', 'HS384',
202202
* 'HS512', 'RS256', 'RS384', and 'RS512'
203203
*
204204
* @return string An encrypted message
@@ -258,7 +258,7 @@ public static function sign(
258258
*
259259
* @param string $msg The original message (header and body)
260260
* @param string $signature The original signature
261-
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate|array<mixed> $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
261+
* @param string|OpenSSLAsymmetricKey|OpenSSLCertificate|array<mixed> $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
262262
* @param string $alg The algorithm
263263
*
264264
* @return bool

tests/JWKTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public function provideDecodeByJwkKeySet()
139139
return [
140140
['rsa1-private.pem', 'rsa-jwkset.json', 'RS256'],
141141
['ecdsa256-private.pem', 'ec-jwkset.json', 'ES256'],
142+
['ed25519-1.sec', 'ed25519-jwkset.json', 'EdDSA'],
142143
];
143144
}
144145

tests/data/ed25519-jwkset.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"keys": [
3+
{
4+
"kid": "jwk1",
5+
"kty": "OKP",
6+
"crv": "Ed25519",
7+
"x": "uOSJMhbKSG4V5xUHS7B9YHmVg_1yVd-G-Io6oBFhSfY"
8+
}
9+
]
10+
}

0 commit comments

Comments
 (0)