mirror of
https://github.com/jonasroussel/dart_jsonwebtoken.git
synced 2025-07-15 02:59:09 +08:00
feat: Add JWK (JSON Web Key) conversion methods to key classes
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@ -7,8 +6,8 @@ import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
|
||||
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||
|
||||
import 'exceptions.dart';
|
||||
import 'keys.dart';
|
||||
import 'helpers.dart';
|
||||
import 'keys.dart';
|
||||
|
||||
abstract class JWTAlgorithm {
|
||||
/// HMAC using SHA-256 hash algorithm
|
||||
@ -147,17 +146,7 @@ class HMACAlgorithm extends JWTAlgorithm {
|
||||
assert(key is SecretKey, 'key must be a SecretKey');
|
||||
final secretKey = key as SecretKey;
|
||||
|
||||
Uint8List keyBytes;
|
||||
|
||||
if (secretKey.isBase64Encoded) {
|
||||
if (RegExp(r'-|_+').hasMatch(secretKey.key)) {
|
||||
keyBytes = base64Url.decode(secretKey.key);
|
||||
} else {
|
||||
keyBytes = base64.decode(secretKey.key);
|
||||
}
|
||||
} else {
|
||||
keyBytes = utf8.encode(secretKey.key);
|
||||
}
|
||||
final keyBytes = decodeHMACSecret(secretKey.key, secretKey.isBase64Encoded);
|
||||
|
||||
final hmac = Hmac(
|
||||
_getHash(name),
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'algorithms.dart';
|
||||
|
||||
final jsonBase64 = json.fuse(utf8.fuse(base64Url));
|
||||
|
||||
String base64Unpadded(String value) {
|
||||
@ -97,3 +99,48 @@ List<String> chunkString(String s, int chunkSize) {
|
||||
}
|
||||
return chunked;
|
||||
}
|
||||
|
||||
Uint8List decodeHMACSecret(String secret, bool isBase64Encoded) {
|
||||
if (isBase64Encoded) {
|
||||
if (RegExp(r'-|_+').hasMatch(secret)) {
|
||||
return base64Url.decode(secret);
|
||||
} else {
|
||||
return base64.decode(secret);
|
||||
}
|
||||
} else {
|
||||
return utf8.encode(secret);
|
||||
}
|
||||
}
|
||||
|
||||
String curveOpenSSLToNIST(String curveName) {
|
||||
switch (curveName) {
|
||||
case "prime256v1":
|
||||
case "secp256r1":
|
||||
return "P-256";
|
||||
case "secp384r1":
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
ECDSAAlgorithm? ecCurveToAlgorithm(String curveName) {
|
||||
switch (curveName) {
|
||||
case "P-256":
|
||||
return JWTAlgorithm.ES256;
|
||||
case "P-384":
|
||||
return JWTAlgorithm.ES384;
|
||||
case "P-521":
|
||||
return JWTAlgorithm.ES512;
|
||||
case "secp256k1":
|
||||
return JWTAlgorithm.ES256K;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
|
||||
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||
|
||||
import 'algorithms.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'helpers.dart';
|
||||
import 'key_parser.dart';
|
||||
|
||||
abstract class JWTKey {}
|
||||
abstract class JWTKey {
|
||||
Map<String, dynamic> toJWK({String? keyID});
|
||||
}
|
||||
|
||||
/// For HMAC algorithms
|
||||
class SecretKey extends JWTKey {
|
||||
@ -14,6 +19,22 @@ class SecretKey extends JWTKey {
|
||||
bool isBase64Encoded;
|
||||
|
||||
SecretKey(this.key, {this.isBase64Encoded = false});
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID, HMACAlgorithm? algorithm}) {
|
||||
final keyBytes = decodeHMACSecret(key, isBase64Encoded);
|
||||
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'oct',
|
||||
'use': 'sig',
|
||||
'k': base64Url.encode(keyBytes),
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
if (algorithm != null) jwk['alg'] = algorithm.name;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For RSA algorithm, in sign method
|
||||
@ -30,6 +51,44 @@ class RSAPrivateKey extends JWTKey {
|
||||
RSAPrivateKey.raw(pc.RSAPrivateKey _key) : key = _key;
|
||||
RSAPrivateKey.clone(RSAPrivateKey _key) : key = _key.key;
|
||||
RSAPrivateKey.bytes(Uint8List bytes) : key = KeyParser.rsaPrivateKey(bytes);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID, RSAAlgorithm? algorithm}) {
|
||||
final p = key.p;
|
||||
if (p == null) throw ArgumentError('p is null');
|
||||
final q = key.q;
|
||||
if (q == null) throw ArgumentError('q is null');
|
||||
final n = key.n;
|
||||
if (n == null) throw ArgumentError('n is null');
|
||||
final e = key.publicExponent;
|
||||
if (e == null) throw ArgumentError('e is null');
|
||||
final d = key.privateExponent;
|
||||
if (d == null) throw ArgumentError('d is null');
|
||||
final dp = d % (p - BigInt.one);
|
||||
final dq = d % (q - BigInt.one);
|
||||
final qi = q.modInverse(p);
|
||||
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'RSA',
|
||||
'use': 'sig',
|
||||
'p': base64Unpadded(base64Url.encode(bigIntToBytes(p).reversed.toList())),
|
||||
'q': base64Unpadded(base64Url.encode(bigIntToBytes(q).reversed.toList())),
|
||||
'd': base64Unpadded(base64Url.encode(bigIntToBytes(d).reversed.toList())),
|
||||
'e': base64Unpadded(base64Url.encode(bigIntToBytes(e).reversed.toList())),
|
||||
'dp':
|
||||
base64Unpadded(base64Url.encode(bigIntToBytes(dp).reversed.toList())),
|
||||
'dq':
|
||||
base64Unpadded(base64Url.encode(bigIntToBytes(dq).reversed.toList())),
|
||||
'qi':
|
||||
base64Unpadded(base64Url.encode(bigIntToBytes(qi).reversed.toList())),
|
||||
'n': base64Unpadded(base64Url.encode(bigIntToBytes(n).reversed.toList())),
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
if (algorithm != null) jwk['alg'] = algorithm.name;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For RSA algorithm, in verify method
|
||||
@ -57,6 +116,26 @@ class RSAPublicKey extends JWTKey {
|
||||
|
||||
key = RSAPublicKey.bytes(bytes).key;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID, RSAAlgorithm? algorithm}) {
|
||||
final e = key.publicExponent;
|
||||
if (e == null) throw ArgumentError('e is null');
|
||||
final n = key.modulus;
|
||||
if (n == null) throw ArgumentError('n is null');
|
||||
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'RSA',
|
||||
'use': 'sig',
|
||||
'e': base64Unpadded(base64Url.encode(bigIntToBytes(e).reversed.toList())),
|
||||
'n': base64Unpadded(base64Url.encode(bigIntToBytes(n).reversed.toList())),
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
if (algorithm != null) jwk['alg'] = algorithm.name;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For ECDSA algorithm, in sign method
|
||||
@ -93,6 +172,36 @@ class ECPrivateKey extends JWTKey {
|
||||
: key = _key.key,
|
||||
size = _key.size;
|
||||
ECPrivateKey.bytes(Uint8List bytes) : key = KeyParser.ecPrivateKey(bytes);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID, ECDSAAlgorithm? algorithm}) {
|
||||
final params = key.parameters;
|
||||
if (params == null) throw ArgumentError('parameters is null');
|
||||
final curve = curveOpenSSLToNIST(params.domainName);
|
||||
final d = key.d;
|
||||
if (d == null) throw ArgumentError('d is null');
|
||||
final Q = params.G * d;
|
||||
if (Q == null) throw ArgumentError('Q is null');
|
||||
final x = Q.x?.toBigInteger();
|
||||
if (x == null) throw ArgumentError('x is null');
|
||||
final y = Q.y?.toBigInteger();
|
||||
if (y == null) throw ArgumentError('y is null');
|
||||
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'EC',
|
||||
'use': 'sig',
|
||||
'crv': curve,
|
||||
'd': base64Unpadded(base64Url.encode(bigIntToBytes(d).reversed.toList())),
|
||||
'x': base64Unpadded(base64Url.encode(bigIntToBytes(x).reversed.toList())),
|
||||
'y': base64Unpadded(base64Url.encode(bigIntToBytes(y).reversed.toList())),
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
final alg = algorithm?.name ?? ecCurveToAlgorithm(curve)?.name;
|
||||
if (alg != null) jwk['alg'] = alg;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For ECDSA algorithm, in verify method
|
||||
@ -111,6 +220,29 @@ class ECPublicKey extends JWTKey {
|
||||
|
||||
key = ECPublicKey.bytes(bytes).key;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID, ECDSAAlgorithm? algorithm}) {
|
||||
final curve = key.parameters?.domainName;
|
||||
if (curve == null) throw ArgumentError('curve is null');
|
||||
final x = key.Q?.x?.toBigInteger();
|
||||
if (x == null) throw ArgumentError('x is null');
|
||||
final y = key.Q?.y?.toBigInteger();
|
||||
if (y == null) throw ArgumentError('y is null');
|
||||
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'EC',
|
||||
'use': 'sig',
|
||||
'crv': curveOpenSSLToNIST(curve),
|
||||
'x': base64Unpadded(base64Url.encode(bigIntToBytes(x).reversed.toList())),
|
||||
'y': base64Unpadded(base64Url.encode(bigIntToBytes(y).reversed.toList())),
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
if (algorithm != null) jwk['alg'] = algorithm.name;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For EdDSA algorithm, in sign method
|
||||
@ -121,6 +253,22 @@ class EdDSAPrivateKey extends JWTKey {
|
||||
|
||||
EdDSAPrivateKey.fromPEM(String pem)
|
||||
: key = KeyParser.edPrivateKeyFromPEM(pem);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID}) {
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'OKP',
|
||||
'use': 'sig',
|
||||
'crv': 'Ed25519',
|
||||
'd': base64Unpadded(base64Url.encode(key.bytes.sublist(0, 32))),
|
||||
'x': base64Unpadded(base64Url.encode(key.bytes.sublist(32))),
|
||||
'alg': 'EdDSA',
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
||||
/// For EdDSA algorithm, in verify method
|
||||
@ -130,4 +278,19 @@ class EdDSAPublicKey extends JWTKey {
|
||||
EdDSAPublicKey(List<int> bytes) : key = ed.PublicKey(bytes);
|
||||
|
||||
EdDSAPublicKey.fromPEM(String pem) : key = KeyParser.edPublicKeyFromPEM(pem);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJWK({String? keyID}) {
|
||||
Map<String, dynamic> jwk = {
|
||||
'kty': 'OKP',
|
||||
'use': 'sig',
|
||||
'crv': 'Ed25519',
|
||||
'x': base64Unpadded(base64Url.encode(key.bytes)),
|
||||
'alg': 'EdDSA',
|
||||
};
|
||||
|
||||
if (keyID != null) jwk['kid'] = keyID;
|
||||
|
||||
return jwk;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user