Skip to content

Commit 1fb8455

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

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

src/JWK.php

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

9+
use function in_array;
10+
911
/**
1012
* JSON Web Key implementation, based on this spec:
1113
* https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
@@ -30,6 +32,11 @@ class JWK
3032
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
3133
];
3234

35+
// 'crv' identifier => JWT 'alg'
36+
private const OKP_CURVES = [
37+
'Ed25519' => 'EdDSA',
38+
];
39+
3340
/**
3441
* Parse a set of JWK keys
3542
*
@@ -96,11 +103,17 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
96103
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
97104
}
98105

99-
if (!isset($jwk['alg'])) {
106+
$ktyNotRequiringAlg = [
107+
// In Octet Key Pair (OKP) keys, the signing algorithm (alg) will not be read from the
108+
// JWK, as it can be inferred directly from the curve type (crv).
109+
// @see https://datatracker.ietf.org/doc/html/rfc8037#section-3.1
110+
'OKP',
111+
];
112+
if (!isset($jwk['alg']) && !in_array($jwk['kty'], $ktyNotRequiringAlg, true)) {
100113
if (\is_null($defaultAlg)) {
101114
// The "alg" parameter is optional in a KTY, but an algorithm is required
102-
// for parsing in this library. Use the $defaultAlg parameter when parsing the
103-
// key set in order to prevent this error.
115+
// for parsing certain key types in this library. Use the $defaultAlg parameter
116+
// when parsing the key set in order to prevent this error.
104117
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
105118
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
106119
}
@@ -144,8 +157,28 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
144157

145158
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
146159
return new Key($publicKey, $jwk['alg']);
160+
case 'OKP':
161+
if (isset($jwk['d'])) {
162+
// The key is actually a private key
163+
throw new UnexpectedValueException('Key data must be for a public key');
164+
}
165+
166+
if (! isset($jwk['crv'])) {
167+
throw new UnexpectedValueException('crv not set');
168+
}
169+
170+
if (!isset(self::OKP_CURVES[$jwk['crv']])) {
171+
throw new DomainException('Unrecognised or unsupported OKP curve');
172+
}
173+
174+
if (empty($jwk['x'])) {
175+
throw new UnexpectedValueException('x not set');
176+
}
177+
178+
$publicKey = \base64_encode(JWT::urlsafeB64Decode($jwk['x']));
179+
$alg = self::OKP_CURVES[$jwk['crv']];
180+
return new Key($publicKey, $alg);
147181
default:
148-
// Currently only RSA is supported
149182
break;
150183
}
151184

src/JWT.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public static function encode(
210210
*
211211
* @param string $msg The message to sign
212212
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
213-
* @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
213+
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'HS256', 'HS384',
214214
* 'HS512', 'RS256', 'RS384', and 'RS512'
215215
*
216216
* @return string An encrypted message
@@ -270,7 +270,7 @@ public static function sign(
270270
*
271271
* @param string $msg The original message (header and body)
272272
* @param string $signature The original signature
273-
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
273+
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
274274
* @param string $alg The algorithm
275275
*
276276
* @return bool

tests/JWKTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public function provideDecodeByJwkKeySet()
151151
return [
152152
['rsa1-private.pem', 'rsa-jwkset.json', 'RS256'],
153153
['ecdsa256-private.pem', 'ec-jwkset.json', 'ES256'],
154+
['ed25519-1.sec', 'ed25519-jwkset.json', 'EdDSA'],
154155
];
155156
}
156157

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)