feat: Add JWTKey.fromJWK method for parsing JWK to various key types

This commit is contained in:
Jonas Roussel
2025-02-27 14:24:19 +01:00
parent cd4b6ee1b9
commit e1a4d6cce9
3 changed files with 285 additions and 120 deletions

View File

@ -127,15 +127,24 @@ String curveOpenSSLToNIST(String curveName) {
return "P-384";
case "secp521r1":
return "P-521";
case "secp192r1":
return "P-192";
case "secp224r1":
return "P-224";
default:
return curveName; // Return the original name if not found
}
}
String curveNISTToOpenSSL(String curveName) {
switch (curveName) {
case "P-256":
return "prime256v1";
case "P-384":
return "secp384r1";
case "P-521":
return "secp521r1";
default:
return curveName;
}
}
ECDSAAlgorithm? ecCurveToAlgorithm(String curveName) {
switch (curveName) {
case "P-256":

View File

@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
import 'package:pointycastle/pointycastle.dart' as pc;
import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp;
import 'algorithms.dart';
import 'exceptions.dart';
@ -10,7 +11,114 @@ import 'helpers.dart';
import 'key_parser.dart';
abstract class JWTKey {
/// Convert the key to a JWK JSON object representation
Map<String, dynamic> toJWK({String? keyID});
/// Parse a JWK JSON object into any valid JWTKey,
///
/// Including `SecretKey`, `RSAPrivateKey`, `RSAPublicKey`, `ECPrivateKey`,
/// `ECPublicKey`, `EdDSAPrivateKey` and `EdDSAPublicKey`.
///
/// Throws a `JWTParseException` if the JWK is invalid or unsupported.
static JWTKey fromJWK(Map<String, dynamic> jwk) {
if (jwk['kty'] == 'oct') {
final key = base64Padded(jwk['k']);
return SecretKey(key, isBase64Encoded: true);
}
if (jwk['kty'] == 'RSA') {
// Private key
if (jwk['p'] != null &&
jwk['q'] != null &&
jwk['d'] != null &&
jwk['n'] != null) {
final p = bigIntFromBytes(base64Url.decode(base64Padded(jwk['p'])));
final q = bigIntFromBytes(base64Url.decode(base64Padded(jwk['q'])));
final d = bigIntFromBytes(base64Url.decode(base64Padded(jwk['d'])));
final n = bigIntFromBytes(base64Url.decode(base64Padded(jwk['n'])));
return RSAPrivateKey.raw(pc.RSAPrivateKey(n, d, p, q));
}
// Public key
if (jwk['e'] != null && jwk['n'] != null) {
final e = bigIntFromBytes(base64Url.decode(base64Padded(jwk['e'])));
final n = bigIntFromBytes(base64Url.decode(base64Padded(jwk['n'])));
return RSAPublicKey.raw(pc.RSAPublicKey(n, e));
}
throw JWTParseException('Invalid JWK');
}
if (jwk['kty'] == 'EC') {
final crv = jwk['crv'];
if (!['P-256', 'P-384', 'P-521', 'secp256k1'].contains(crv)) {
throw JWTParseException('Unsupported curve');
}
// Private key
if (jwk['d'] != null) {
final d = bigIntFromBytes(base64Url.decode(base64Padded(jwk['d'])));
return ECPrivateKey.raw(pc.ECPrivateKey(
d,
pc.ECDomainParameters(curveNISTToOpenSSL(crv)),
));
}
// Public key
if (jwk['x'] != null && jwk['y'] != null) {
final x = bigIntFromBytes(base64Url.decode(base64Padded(jwk['x'])));
final y = bigIntFromBytes(base64Url.decode(base64Padded(jwk['y'])));
final params = pc.ECDomainParameters(curveNISTToOpenSSL(crv));
return ECPublicKey.raw(pc.ECPublicKey(
ecc_fp.ECPoint(
params.curve as ecc_fp.ECCurve,
params.curve.fromBigInteger(x) as ecc_fp.ECFieldElement?,
params.curve.fromBigInteger(y) as ecc_fp.ECFieldElement?,
false,
),
params,
));
}
throw JWTParseException('Invalid JWK');
}
if (jwk['kty'] == 'OKP') {
final crv = jwk['crv'];
if (crv != 'Ed25519') throw JWTParseException('Unsupported curve');
// Private key
if (jwk['d'] != null && jwk['x'] != null) {
final d = base64Url.decode(base64Padded(jwk['d']));
final x = base64Url.decode(base64Padded(jwk['x']));
return EdDSAPrivateKey(
Uint8List(d.length + x.length)
..setAll(0, d)
..setAll(d.length, x),
);
}
// Public key
if (jwk['x'] != null) {
final x = base64Url.decode(base64Padded(jwk['x']));
return EdDSAPublicKey(x);
}
throw JWTParseException('Invalid JWK');
}
throw JWTParseException('Unsupported key type');
}
}
/// For HMAC algorithms

View File

@ -5,6 +5,7 @@ import 'keys_const.dart';
void main() {
group('JWTKey', () {
group('.toJWK', () {
test('should convert SecretKey to JWK', () {
final jwk = hsKey.toJWK();
@ -47,7 +48,9 @@ void main() {
}),
);
final jwkWithAlgorithm = rsaPrivKey.toJWK(algorithm: JWTAlgorithm.RS256);
final jwkWithAlgorithm = rsaPrivKey.toJWK(
algorithm: JWTAlgorithm.RS256,
);
expect(jwkWithAlgorithm['alg'], equals('RS256'));
});
@ -133,4 +136,49 @@ void main() {
);
});
});
group('.fromJWK', () {
test('should parse JWK to SecretKey', () {
final jwk = hsKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<SecretKey>());
});
test('should parse JWK to RSAPrivateKey', () {
final jwk = rsaPrivKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<RSAPrivateKey>());
});
test('should parse JWK to RSAPublicKey', () {
final jwk = rsaPubKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<RSAPublicKey>());
});
test('should parse JWK to ECPrivateKey', () {
final jwk = ecPrivKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<ECPrivateKey>());
});
test('should parse JWK to ECPublicKey', () {
final jwk = ecPubKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<ECPublicKey>());
});
test('should parse JWK to EdDSAPrivateKey', () {
final jwk = edPrivKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<EdDSAPrivateKey>());
});
test('should parse JWK to EdDSAPublicKey', () {
final jwk = edPubKey.toJWK();
final key = JWTKey.fromJWK(jwk);
expect(key, isA<EdDSAPublicKey>());
});
});
});
}