Skip to content

Commit e1a4d6c

Browse files
committed
feat: Add JWTKey.fromJWK method for parsing JWK to various key types
1 parent cd4b6ee commit e1a4d6c

File tree

3 files changed

+293
-128
lines changed

3 files changed

+293
-128
lines changed

lib/src/helpers.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,24 @@ String curveOpenSSLToNIST(String curveName) {
127127
return "P-384";
128128
case "secp521r1":
129129
return "P-521";
130-
case "secp192r1":
131-
return "P-192";
132-
case "secp224r1":
133-
return "P-224";
134130
default:
135131
return curveName; // Return the original name if not found
136132
}
137133
}
138134

135+
String curveNISTToOpenSSL(String curveName) {
136+
switch (curveName) {
137+
case "P-256":
138+
return "prime256v1";
139+
case "P-384":
140+
return "secp384r1";
141+
case "P-521":
142+
return "secp521r1";
143+
default:
144+
return curveName;
145+
}
146+
}
147+
139148
ECDSAAlgorithm? ecCurveToAlgorithm(String curveName) {
140149
switch (curveName) {
141150
case "P-256":

lib/src/keys.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,122 @@ import 'dart:typed_data';
33

44
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
55
import 'package:pointycastle/pointycastle.dart' as pc;
6+
import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp;
67

78
import 'algorithms.dart';
89
import 'exceptions.dart';
910
import 'helpers.dart';
1011
import 'key_parser.dart';
1112

1213
abstract class JWTKey {
14+
/// Convert the key to a JWK JSON object representation
1315
Map<String, dynamic> toJWK({String? keyID});
16+
17+
/// Parse a JWK JSON object into any valid JWTKey,
18+
///
19+
/// Including `SecretKey`, `RSAPrivateKey`, `RSAPublicKey`, `ECPrivateKey`,
20+
/// `ECPublicKey`, `EdDSAPrivateKey` and `EdDSAPublicKey`.
21+
///
22+
/// Throws a `JWTParseException` if the JWK is invalid or unsupported.
23+
static JWTKey fromJWK(Map<String, dynamic> jwk) {
24+
if (jwk['kty'] == 'oct') {
25+
final key = base64Padded(jwk['k']);
26+
27+
return SecretKey(key, isBase64Encoded: true);
28+
}
29+
30+
if (jwk['kty'] == 'RSA') {
31+
// Private key
32+
if (jwk['p'] != null &&
33+
jwk['q'] != null &&
34+
jwk['d'] != null &&
35+
jwk['n'] != null) {
36+
final p = bigIntFromBytes(base64Url.decode(base64Padded(jwk['p'])));
37+
final q = bigIntFromBytes(base64Url.decode(base64Padded(jwk['q'])));
38+
final d = bigIntFromBytes(base64Url.decode(base64Padded(jwk['d'])));
39+
final n = bigIntFromBytes(base64Url.decode(base64Padded(jwk['n'])));
40+
41+
return RSAPrivateKey.raw(pc.RSAPrivateKey(n, d, p, q));
42+
}
43+
44+
// Public key
45+
if (jwk['e'] != null && jwk['n'] != null) {
46+
final e = bigIntFromBytes(base64Url.decode(base64Padded(jwk['e'])));
47+
final n = bigIntFromBytes(base64Url.decode(base64Padded(jwk['n'])));
48+
49+
return RSAPublicKey.raw(pc.RSAPublicKey(n, e));
50+
}
51+
52+
throw JWTParseException('Invalid JWK');
53+
}
54+
55+
if (jwk['kty'] == 'EC') {
56+
final crv = jwk['crv'];
57+
58+
if (!['P-256', 'P-384', 'P-521', 'secp256k1'].contains(crv)) {
59+
throw JWTParseException('Unsupported curve');
60+
}
61+
62+
// Private key
63+
if (jwk['d'] != null) {
64+
final d = bigIntFromBytes(base64Url.decode(base64Padded(jwk['d'])));
65+
66+
return ECPrivateKey.raw(pc.ECPrivateKey(
67+
d,
68+
pc.ECDomainParameters(curveNISTToOpenSSL(crv)),
69+
));
70+
}
71+
72+
// Public key
73+
if (jwk['x'] != null && jwk['y'] != null) {
74+
final x = bigIntFromBytes(base64Url.decode(base64Padded(jwk['x'])));
75+
final y = bigIntFromBytes(base64Url.decode(base64Padded(jwk['y'])));
76+
77+
final params = pc.ECDomainParameters(curveNISTToOpenSSL(crv));
78+
79+
return ECPublicKey.raw(pc.ECPublicKey(
80+
ecc_fp.ECPoint(
81+
params.curve as ecc_fp.ECCurve,
82+
params.curve.fromBigInteger(x) as ecc_fp.ECFieldElement?,
83+
params.curve.fromBigInteger(y) as ecc_fp.ECFieldElement?,
84+
false,
85+
),
86+
params,
87+
));
88+
}
89+
90+
throw JWTParseException('Invalid JWK');
91+
}
92+
93+
if (jwk['kty'] == 'OKP') {
94+
final crv = jwk['crv'];
95+
96+
if (crv != 'Ed25519') throw JWTParseException('Unsupported curve');
97+
98+
// Private key
99+
if (jwk['d'] != null && jwk['x'] != null) {
100+
final d = base64Url.decode(base64Padded(jwk['d']));
101+
final x = base64Url.decode(base64Padded(jwk['x']));
102+
103+
return EdDSAPrivateKey(
104+
Uint8List(d.length + x.length)
105+
..setAll(0, d)
106+
..setAll(d.length, x),
107+
);
108+
}
109+
110+
// Public key
111+
if (jwk['x'] != null) {
112+
final x = base64Url.decode(base64Padded(jwk['x']));
113+
114+
return EdDSAPublicKey(x);
115+
}
116+
117+
throw JWTParseException('Invalid JWK');
118+
}
119+
120+
throw JWTParseException('Unsupported key type');
121+
}
14122
}
15123

16124
/// For HMAC algorithms

0 commit comments

Comments
 (0)