diff --git a/README.md b/README.md index ef1d17c..319179f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/eddsa_private.pem b/example/eddsa_private.pem new file mode 100644 index 0000000..95927d4 --- /dev/null +++ b/example/eddsa_private.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICXCjBHvjArjXquUI5jo3x5SHI4ofZA2azwJ39IC/Qct +-----END PRIVATE KEY----- diff --git a/example/eddsa_public.pem b/example/eddsa_public.pem new file mode 100644 index 0000000..766d479 --- /dev/null +++ b/example/eddsa_public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAEi7MNW0Q9T83UA3Rw+8DbspMgqeuxCqa2wXaWS+tHqY= +-----END PUBLIC KEY----- diff --git a/example/example.dart b/example/example.dart index 2809eea..c65f070 100644 --- a/example/example.dart +++ b/example/example.dart @@ -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 + } + } +} diff --git a/lib/src/key_parser.dart b/lib/src/key_parser.dart index 6d1cb33..3cf3274 100644 --- a/lib/src/key_parser.dart +++ b/lib/src/key_parser.dart @@ -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 // //--------------// diff --git a/lib/src/keys.dart b/lib/src/keys.dart index 71d4597..966352e 100644 --- a/lib/src/keys.dart +++ b/lib/src/keys.dart @@ -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 bytes) { - key = ed.PrivateKey(bytes); - } + EdDSAPrivateKey(List 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 bytes) { - key = ed.PublicKey(bytes); - } + EdDSAPublicKey(List bytes) : key = ed.PublicKey(bytes); + + EdDSAPublicKey.fromPEM(String pem) : key = KeyParser.edPublicKeyFromPEM(pem); } diff --git a/test/sign_test.dart b/test/sign_test.dart index a0110e0..bd74996 100644 --- a/test/sign_test.dart +++ b/test/sign_test.dart @@ -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)); }); diff --git a/test/verify_test.dart b/test/verify_test.dart index bdb7c9f..eeea84c 100644 --- a/test/verify_test.dart +++ b/test/verify_test.dart @@ -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);