add support of PEM parsing for EdDSA

This commit is contained in:
Jonas Roussel
2025-01-22 11:38:49 +01:00
parent 1be10d9387
commit b843d932bd
8 changed files with 116 additions and 27 deletions

View File

@ -2,16 +2,18 @@
[![pub version](https://img.shields.io/pub/v/dart_jsonwebtoken.svg)](https://pub.dev/packages/dart_jsonwebtoken)
A dart implementation of the famous javascript library `jsonwebtoken`.
An easy to use JSON Web Token implementation in Dart (all algorithms supported).
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
https://jwt.io allows you to decode, verify and generate JWT.
`dart_jsonwebtoken` allows you to sign, decode and verify JWT.
## Examples
Check out the [Example File](https://github.com/jonasroussel/dart_jsonwebtoken/blob/main/example/example.dart) for a full example code of all the differents algorithms.
You can also check out the [jwt.io](https://jwt.io) website for more information.
## Usage
### Import
@ -110,9 +112,16 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
''');
// EdDSA (PEM parsing is not available for EdDSA keys)
final edPrivKey = EdDSAPrivateKey([1, 42, 12, 84, ...]);
final edPubKey = EdDSAPublicKey([1, 42, 12, 84, ...]);
// EdDSA
final edPrivKey = EdDSAPrivateKey.fromPEM('''-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICXCjBHvjArjXquUI5jo3x5SHI4ofZA2azwJ39IC/Qct
-----END PRIVATE KEY-----
''');
final edPubKey = EdDSAPublicKey.fromPEM('''-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAEi7MNW0Q9T83UA3Rw+8DbspMgqeuxCqa2wXaWS+tHqY=
-----END PUBLIC KEY-----
''');
```
### Supported Algorithms

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICXCjBHvjArjXquUI5jo3x5SHI4ofZA2azwJ39IC/Qct
-----END PRIVATE KEY-----

3
example/eddsa_public.pem Normal file
View File

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAEi7MNW0Q9T83UA3Rw+8DbspMgqeuxCqa2wXaWS+tHqY=
-----END PUBLIC KEY-----

View File

@ -26,6 +26,10 @@ void main() {
print('----- RSA Certificate -----');
rsaCert();
print('---------------------------\n');
print('----- EdDSA -----');
eddsa();
print('-----------------\n');
}
// HMAC SHA-256 algorithm
@ -279,3 +283,45 @@ void rsaCert() {
}
}
}
void eddsa() {
String token;
/* Sign */ {
// Create a json web token
final jwt = JWT(
{
'id': 123,
'server': {
'id': '3e4fc296',
'loc': 'euw-2',
}
},
issuer: 'https://github.com/jonasroussel/dart_jsonwebtoken',
);
// Sign it
final privPem = File('./example/eddsa_private.pem').readAsStringSync();
final key = EdDSAPrivateKey.fromPEM(privPem);
token = jwt.sign(key, algorithm: JWTAlgorithm.EdDSA);
print('Signed token: $token\n');
}
/* Verify */ {
try {
// Verify a token
final pem = File('./example/eddsa_public.pem').readAsStringSync();
final key = EdDSAPublicKey.fromPEM(pem);
final jwt = JWT.verify(token, key);
print('Payload: ${jwt.payload}');
} on JWTExpiredException {
print('jwt expired');
} on JWTException catch (ex) {
print(ex.message); // ex: invalid signature
}
}
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:pointycastle/asn1/object_identifiers.dart';
import 'package:pointycastle/pointycastle.dart';
import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp;
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
import 'helpers.dart';
@ -245,6 +246,38 @@ abstract class KeyParser {
return pubKey;
}
//---------------//
// EdDSA Parsing //
//---------------//
static ed.PrivateKey edPrivateKeyFromPEM(String pem) {
final bytes = bytesFromPEM(pem);
return edPrivateKey(bytes);
}
static ed.PrivateKey edPrivateKey(Uint8List bytes) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
return ed.newKeyFromSeed(octetString.valueBytes!.sublist(2));
}
static ed.PublicKey edPublicKeyFromPEM(String pem) {
final bytes = bytesFromPEM(pem);
return edPublicKey(bytes);
}
static ed.PublicKey edPublicKey(Uint8List bytes) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var bitString = topLevelSeq.elements!.elementAt(1) as ASN1BitString;
return ed.PublicKey(bitString.valueBytes!.sublist(1));
}
//--------------//
// PEM to Bytes //
//--------------//

View File

@ -115,18 +115,19 @@ class ECPublicKey extends JWTKey {
/// For EdDSA algorithm, in sign method
class EdDSAPrivateKey extends JWTKey {
late ed.PrivateKey key;
ed.PrivateKey key;
EdDSAPrivateKey(List<int> bytes) {
key = ed.PrivateKey(bytes);
}
EdDSAPrivateKey(List<int> bytes) : key = ed.PrivateKey(bytes);
EdDSAPrivateKey.fromPEM(String pem)
: key = KeyParser.edPrivateKeyFromPEM(pem);
}
/// For EdDSA algorithm, in verify method
class EdDSAPublicKey extends JWTKey {
late ed.PublicKey key;
ed.PublicKey key;
EdDSAPublicKey(List<int> bytes) {
key = ed.PublicKey(bytes);
}
EdDSAPublicKey(List<int> bytes) : key = ed.PublicKey(bytes);
EdDSAPublicKey.fromPEM(String pem) : key = KeyParser.edPublicKeyFromPEM(pem);
}

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:math';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
@ -43,12 +42,9 @@ MHQCAQEEINCRiJnNDnzfo2So2tWY4AIuzeC2ZBp/hmMDcZz3Fh45oAcGBSuBBAAK
oUQDQgAE0aELkvG/Xeo5y6o0WXRAjlediLptGz7Q8zjDmpGFXkKBYZ6IiL7JJ2Tk
cHzd83bmeUeGX33RGTYFPXs5t/VBnw==
-----END EC PRIVATE KEY-----''');
final edKey = EdDSAPrivateKey(
base64Decode(
'nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2DXWpgBgrEKt9VL/' +
'tPJZAc6DuFy89qmIyWvAhpo9wdRGg==',
),
);
final edKey = EdDSAPrivateKey.fromPEM('''-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICXCjBHvjArjXquUI5jo3x5SHI4ofZA2azwJ39IC/Qct
-----END PRIVATE KEY-----''');
class MockRSAAlgorithm extends RSAAlgorithm {
MockRSAAlgorithm(String name) : super(name, Random(42));
@ -218,7 +214,7 @@ void main() {
final expectedToken = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9' +
'.eyJmb28iOiJiYXIifQ' +
'.8tRIxs_o_isQItc2FtzA34Ah-EEvBj7Fw6lKh2tD53IOx5CinBM36yIGo2TDHNmm-ElATCdnMisUKt_UJ5pTAg';
'.6Bw5vvdpJ_kgDwidU1l7aagtKCD9-QIJxrz44HXxtc6OJoOmImNko0dgXYpTtXhcEuX7vamSR5JPfGP1Q9d9DA';
expect(token, equals(expectedToken));
});

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:test/test.dart';
@ -21,9 +19,9 @@ final secp256kKey = ECPublicKey('''-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE0aELkvG/Xeo5y6o0WXRAjlediLptGz7Q
8zjDmpGFXkKBYZ6IiL7JJ2TkcHzd83bmeUeGX33RGTYFPXs5t/VBnw==
-----END PUBLIC KEY-----''');
final edKey = EdDSAPublicKey(
base64Decode('11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo='),
);
final edKey = EdDSAPublicKey.fromPEM('''-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAEi7MNW0Q9T83UA3Rw+8DbspMgqeuxCqa2wXaWS+tHqY=
-----END PUBLIC KEY-----''');
void main() {
group('Verify a JWT', () {
@ -244,7 +242,7 @@ void main() {
test('.verify EdDSA', () {
final token = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9' +
'.eyJmb28iOiJiYXIifQ' +
'.8tRIxs_o_isQItc2FtzA34Ah-EEvBj7Fw6lKh2tD53IOx5CinBM36yIGo2TDHNmm-ElATCdnMisUKt_UJ5pTAg';
'.6Bw5vvdpJ_kgDwidU1l7aagtKCD9-QIJxrz44HXxtc6OJoOmImNko0dgXYpTtXhcEuX7vamSR5JPfGP1Q9d9DA';
final jwt = JWT.tryVerify(token, edKey);