mirror of
https://github.com/jonasroussel/dart_jsonwebtoken.git
synced 2025-07-03 18:26:59 +08:00
Support EdDSA for JWT (#7)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,3 +11,5 @@ build/
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
.idea/
|
||||
|
@ -71,7 +71,7 @@ void rs256() {
|
||||
);
|
||||
|
||||
// Sign it
|
||||
final pem = File('rsa_private.pem').readAsStringSync();
|
||||
final pem = File('./example/rsa_private.pem').readAsStringSync();
|
||||
final key = RSAPrivateKey(pem);
|
||||
|
||||
token = jwt.sign(key, algorithm: JWTAlgorithm.RS256);
|
||||
@ -82,7 +82,7 @@ void rs256() {
|
||||
/* Verify */ {
|
||||
try {
|
||||
// Verify a token
|
||||
final pem = File('rsa_public.pem').readAsStringSync();
|
||||
final pem = File('./example/rsa_public.pem').readAsStringSync();
|
||||
final key = RSAPublicKey(pem);
|
||||
|
||||
final jwt = JWT.verify(token, key);
|
||||
@ -114,7 +114,7 @@ void es256() {
|
||||
);
|
||||
|
||||
// Sign it
|
||||
final pem = File('ec_private.pem').readAsStringSync();
|
||||
final pem = File('./example/ec_private.pem').readAsStringSync();
|
||||
final key = ECPrivateKey(pem);
|
||||
|
||||
token = jwt.sign(key, algorithm: JWTAlgorithm.ES256);
|
||||
@ -125,7 +125,7 @@ void es256() {
|
||||
/* Verify */ {
|
||||
try {
|
||||
// Verify a token
|
||||
final pem = File('ec_public.pem').readAsStringSync();
|
||||
final pem = File('./example/ec_public.pem').readAsStringSync();
|
||||
final key = ECPublicKey(pem);
|
||||
|
||||
final jwt = JWT.verify(token, key);
|
||||
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
|
||||
|
||||
import 'errors.dart';
|
||||
import 'keys.dart';
|
||||
@ -36,6 +37,9 @@ abstract class JWTAlgorithm {
|
||||
/// ECDSA using P-512 curve and SHA-512 hash algorithm
|
||||
static const ES512 = _ECDSAAlgorithm('ES512');
|
||||
|
||||
/// EdDSA using Ed25519 curve algorithm
|
||||
static const Ed25519 = _EdDSAAlgorithm('EdDSA');
|
||||
|
||||
/// Return the `JWTAlgorithm` from his string name
|
||||
static JWTAlgorithm fromName(String name) {
|
||||
switch (name) {
|
||||
@ -57,6 +61,8 @@ abstract class JWTAlgorithm {
|
||||
return JWTAlgorithm.ES384;
|
||||
case 'ES512':
|
||||
return JWTAlgorithm.ES512;
|
||||
case 'EdDSA':
|
||||
return JWTAlgorithm.Ed25519;
|
||||
default:
|
||||
throw JWTInvalidError('unknown algorithm');
|
||||
}
|
||||
@ -78,6 +84,35 @@ abstract class JWTAlgorithm {
|
||||
bool verify(Key key, Uint8List body, Uint8List signature);
|
||||
}
|
||||
|
||||
class _EdDSAAlgorithm extends JWTAlgorithm {
|
||||
final String _name;
|
||||
|
||||
const _EdDSAAlgorithm(this._name);
|
||||
|
||||
@override
|
||||
String get name => _name;
|
||||
|
||||
@override
|
||||
Uint8List sign(Key key, Uint8List body) {
|
||||
assert(key is EdDSAPrivateKey, 'key must be a EdDSAPrivateKey');
|
||||
final privateKey = key as EdDSAPrivateKey;
|
||||
|
||||
return ed.sign(privateKey.key, body);
|
||||
}
|
||||
|
||||
@override
|
||||
bool verify(Key key, Uint8List body, Uint8List signature) {
|
||||
assert(key is EdDSAPublicKey, 'key must be a EdDSAPublicKey');
|
||||
final publicKey = key as EdDSAPublicKey;
|
||||
|
||||
try {
|
||||
return ed.verify(publicKey.key, body, signature);
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HMACAlgorithm extends JWTAlgorithm {
|
||||
final String _name;
|
||||
|
||||
|
@ -13,6 +13,7 @@ class JWT {
|
||||
/// - SecretKey with HMAC algorithm
|
||||
/// - RSAPublicKey with RSA algorithm
|
||||
/// - ECPublicKey with ECDSA algorithm
|
||||
/// - EdDSAPublicKey with EdDSA algorithm
|
||||
static JWT verify(
|
||||
String token,
|
||||
Key key, {
|
||||
@ -166,6 +167,7 @@ class JWT {
|
||||
/// - SecretKey with HMAC algorithm
|
||||
/// - RSAPrivateKey with RSA algorithm
|
||||
/// - ECPrivateKey with ECDSA algorithm
|
||||
/// - EdDSAPrivateKey with EdDSA algorithm
|
||||
String sign(
|
||||
Key key, {
|
||||
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
|
||||
@ -210,7 +212,7 @@ class JWT {
|
||||
);
|
||||
}
|
||||
|
||||
final body = '${b64Header}.${b64Payload}';
|
||||
final body = '$b64Header.$b64Payload';
|
||||
final signature = base64Unpadded(
|
||||
base64Url.encode(
|
||||
algorithm.sign(
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
|
||||
|
||||
import 'errors.dart';
|
||||
import 'parser.dart';
|
||||
@ -12,6 +13,24 @@ class SecretKey extends Key {
|
||||
SecretKey(this.key);
|
||||
}
|
||||
|
||||
/// For EdDSA algorithm, in sign method
|
||||
class EdDSAPrivateKey extends Key {
|
||||
late ed.PrivateKey key;
|
||||
|
||||
EdDSAPrivateKey(List<int> bytes) {
|
||||
key = ed.PrivateKey(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// For EdDSA algorithm, in verify method
|
||||
class EdDSAPublicKey extends Key {
|
||||
late ed.PublicKey key;
|
||||
|
||||
EdDSAPublicKey(List<int> bytes) {
|
||||
key = ed.PublicKey(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// For RSA algorithm, in sign method
|
||||
class RSAPrivateKey extends Key {
|
||||
late pc.RSAPrivateKey key;
|
||||
|
@ -10,6 +10,9 @@ environment:
|
||||
dependencies:
|
||||
crypto: ^3.0.0
|
||||
pointycastle: ^3.0.0-nullsafety.2
|
||||
ed25519_edwards: ^0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
uuid: ^3.0.0
|
||||
test: ^1.0.0
|
||||
pedantic: ^1.9.2
|
||||
|
70
test/jwt_test.dart
Normal file
70
test/jwt_test.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:dart_jsonwebtoken/src/jwt.dart';
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
List<int> _base64ToBytes(String encoded) {
|
||||
encoded += List.filled((4 - encoded.length % 4) % 4, '=').join();
|
||||
return base64Url.decode(encoded);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('JWT Examples', () {
|
||||
String token;
|
||||
|
||||
var d = _base64ToBytes('nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A');
|
||||
var x = _base64ToBytes('11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo');
|
||||
var privateKeyBytes = <int>[];
|
||||
privateKeyBytes.addAll(d);
|
||||
privateKeyBytes.addAll(x);
|
||||
var publicKey = EdDSAPublicKey(x);
|
||||
var privateKey = EdDSAPrivateKey(privateKeyBytes);
|
||||
{
|
||||
final jwt = JWT('Example of Ed25519 signing');
|
||||
|
||||
token = jwt.sign(privateKey,
|
||||
algorithm: JWTAlgorithm.Ed25519, noIssueAt: true);
|
||||
|
||||
print('Signed token: $token\n');
|
||||
expect(token,
|
||||
'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.x2iH_vGNptaSYh3czRpZpW_W37qTwu63pHxesEjw367bDHZ44uVma-ZrH31QSJmJCpPdA7kAlWBTIgwfKGO4CA');
|
||||
}
|
||||
|
||||
try {
|
||||
final jwt = JWT.verify(token, publicKey);
|
||||
|
||||
print('Payload: ${jwt.payload}');
|
||||
} on JWTExpiredError {
|
||||
print('jwt expired');
|
||||
} on JWTError catch (ex) {
|
||||
print(ex.message); // ex: invalid signature
|
||||
}
|
||||
});
|
||||
|
||||
test('test sign & verify', () {
|
||||
var keyPair = ed.generateKey();
|
||||
var public = EdDSAPublicKey(keyPair.publicKey!.bytes);
|
||||
var private = EdDSAPrivateKey(keyPair.privateKey!.bytes);
|
||||
final jwt = JWT({
|
||||
'uid': Uuid().v4(),
|
||||
'sid': Uuid().v4(),
|
||||
'iat': (DateTime.now().millisecondsSinceEpoch / 1000).floor(),
|
||||
'exp': (DateTime.now().add(Duration(days: 365)).millisecondsSinceEpoch /
|
||||
1000)
|
||||
.floor(),
|
||||
'jti': Uuid().v4(),
|
||||
'sig': Uuid().v4(),
|
||||
'scp': 'FULL',
|
||||
});
|
||||
|
||||
var token = jwt.sign(private, algorithm: JWTAlgorithm.Ed25519);
|
||||
print('Signed token: $token\n');
|
||||
|
||||
final verifiedJwt = JWT.verify(token, public);
|
||||
|
||||
print('VerifiedJwt Payload: ${verifiedJwt.payload}');
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user