Skip to content

Commit e53979a

Browse files
authored
feat: add ed25519 support to JWK (public keys) (#452)
1 parent 5a9cf79 commit e53979a

File tree

4 files changed

+57
-4
lines changed

4 files changed

+57
-4
lines changed

Diff for: src/JWK.php

+27-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class JWK
3131
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
3232
];
3333

34+
// For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype.
35+
// This library supports the following subtypes:
36+
private const OKP_SUBTYPES = [
37+
'Ed25519' => true, // RFC 8037
38+
];
39+
3440
/**
3541
* Parse a set of JWK keys
3642
*
@@ -145,8 +151,28 @@ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
145151

146152
$publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
147153
return new Key($publicKey, $jwk['alg']);
154+
case 'OKP':
155+
if (isset($jwk['d'])) {
156+
// The key is actually a private key
157+
throw new UnexpectedValueException('Key data must be for a public key');
158+
}
159+
160+
if (!isset($jwk['crv'])) {
161+
throw new UnexpectedValueException('crv not set');
162+
}
163+
164+
if (empty(self::OKP_SUBTYPES[$jwk['crv']])) {
165+
throw new DomainException('Unrecognised or unsupported OKP key subtype');
166+
}
167+
168+
if (empty($jwk['x'])) {
169+
throw new UnexpectedValueException('x not set');
170+
}
171+
172+
// This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
173+
$publicKey = JWT::convertBase64urlToBase64($jwk['x']);
174+
return new Key($publicKey, $jwk['alg']);
148175
default:
149-
// Currently only RSA is supported
150176
break;
151177
}
152178

Diff for: src/JWT.php

+18-3
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public static function encode(
220220
*
221221
* @param string $msg The message to sign
222222
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
223-
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
223+
* @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
224224
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
225225
*
226226
* @return string An encrypted message
@@ -283,7 +283,7 @@ public static function sign(
283283
*
284284
* @param string $msg The original message (header and body)
285285
* @param string $signature The original signature
286-
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
286+
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
287287
* @param string $alg The algorithm
288288
*
289289
* @return bool
@@ -404,13 +404,28 @@ public static function jsonEncode(array $input): string
404404
* @throws InvalidArgumentException invalid base64 characters
405405
*/
406406
public static function urlsafeB64Decode(string $input): string
407+
{
408+
return \base64_decode(self::convertBase64UrlToBase64($input));
409+
}
410+
411+
/**
412+
* Convert a string in the base64url (URL-safe Base64) encoding to standard base64.
413+
*
414+
* @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding)
415+
*
416+
* @return string A Base64 encoded string with standard characters (+/) and padding (=), when
417+
* needed.
418+
*
419+
* @see https://www.rfc-editor.org/rfc/rfc4648
420+
*/
421+
public static function convertBase64UrlToBase64(string $input): string
407422
{
408423
$remainder = \strlen($input) % 4;
409424
if ($remainder) {
410425
$padlen = 4 - $remainder;
411426
$input .= \str_repeat('=', $padlen);
412427
}
413-
return \base64_decode(\strtr($input, '-_', '+/'));
428+
return \strtr($input, '-_', '+/');
414429
}
415430

416431
/**

Diff for: tests/JWKTest.php

+1
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

Diff for: tests/data/ed25519-jwkset.json

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

0 commit comments

Comments
 (0)