This commit is contained in:
Jonas Roussel
2023-08-23 12:34:24 +02:00
parent 1e69c815c5
commit 3847ffcca1
9 changed files with 314 additions and 36 deletions

View File

@ -1,3 +1,10 @@
## 2.10.0
- New ECDSA algorithm (ES256K)
- New RSA algorithm with PSS padding (PS256, PS384, PS512)
- README.md improved
- example/example.dart improved
## 2.9.1
- Adding a new class factory `ECPublicKey.cert`
@ -127,7 +134,7 @@
## 1.6.0
- New ECDSA Algorithm (EC256, EC384, EC512)
- New ECDSA Algorithm (ES256, ES384, ES512)
- ECPrivateKey and ECPublicKey, two new keys for ECDSA algorithm
- PrivateKey is renamed in RSAPrivateKey
- PublicKey is renamed in RSAPublicKey

View File

@ -8,6 +8,10 @@ JSON Web Tokens are an open, industry standard RFC 7519 method for representing
https://jwt.io allows you to decode, verify and generate 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.
## Usage
### Import
@ -19,27 +23,22 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
### Creating & signing a JWT
```dart
// Create a json web token
// Pass the payload to be sent in the form of a map
// Generate a JSON Web Token
// You can provide the payload as a key-value map or a string
final jwt = JWT(
// Payload
{
'id': 123,
'server': {
'id': '3e4fc296',
'loc': 'euw-2',
}
},
issuer: 'https://github.com/jonasroussel/dart_jsonwebtoken',
);
// Sign it (default with HS256 algorithm)
token = jwt.sign(SecretKey('secret passphrase'));
final token = jwt.sign(SecretKey('secret passphrase'));
print('Signed token: $token\n');
```
@ -48,7 +47,7 @@ print('Signed token: $token\n');
```dart
try {
// Verify a token
// Verify a token (SecretKey for HMAC & PublicKey for all the others)
final jwt = JWT.verify(token, SecretKey('secret passphrase'));
print('Payload: ${jwt.payload}');
@ -60,23 +59,77 @@ try {
```
### You can also, decode the token without checking its signature
```dart
final jwt = JWT.decode(token);
print('Payload: ${jwt.payload}');
```
### Keys creation for all the algorithms
The raw PEM content provided here is intended for learning purposes. In a production environment, it's recommended to read the private and public keys from separate files. Then, you can pass the content of these files (as strings) in the parameters
```dart
// H256, H384, H512
final hmacKey = SecretKey('secret passphrase');
// RS256, RS384, RS512, PS256, PS384, PS512
final rsaPrivKey = RSAPrivateKey('''
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAq5QLAv9kYTgelglIhC17KdfUoinkwvQ4F0TZAp7qgmu19dCx
...
-----END RSA PRIVATE KEY-----
''');
// You can also extract the public key from a certificate with RSAPublicKey.cert(...)
final rsaPubKey = RSAPublicKey('''
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq5QLAv9kYTgelglIhC17
...
-----END PUBLIC KEY-----
'''
);
// ES256, ES256K, ES384, ES512
final ecPrivKey = ECPrivateKey('''
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
...
-----END PRIVATE KEY-----
''');
// You can also extract the public key from a certificate with ECPublicKey.cert(...)
final ecPubKey = ECPublicKEy('''
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
...
-----END PUBLIC KEY-----
''');
// EdDSA (PEM parsing is not available for EdDSA keys)
final edPrivKey = EdDSAPrivateKey([1, 42, 12, 84, ...]);
final edPubKey = EdDSAPublicKey([1, 42, 12, 84, ...]);
```
### Supported Algorithms
| JWT Algorithms | Digital Signature or MAC Algorithm |
| -------------- | ---------------------------------------------------- |
| -------------- | ----------------------------------------------------- |
| HS256 | HMAC using SHA-256 hash algorithm |
| HS384 | HMAC using SHA-384 hash algorithm |
| HS512 | HMAC using SHA-512 hash algorithm |
| PS256 | RSASSA-PSS using SHA-256 hash algorithm |
| PS384 | RSASSA-PSS using SHA-384 hash algorithm |
| PS512 | RSASSA-PSS using SHA-512 hash algorithm |
| RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm |
| RS384 | RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm |
| RS512 | RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm |
| ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm |
| ES256K | ECDSA using secp256k curve and SHA-256 hash algorithm |
| ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm |
| ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm |
| EdDSA | EdDSA using ed25519 curve and SHA-512 hash algorithm |

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEINCRiJnNDnzfo2So2tWY4AIuzeC2ZBp/hmMDcZz3Fh45oAcGBSuBBAAK
oUQDQgAE0aELkvG/Xeo5y6o0WXRAjlediLptGz7Q8zjDmpGFXkKBYZ6IiL7JJ2Tk
cHzd83bmeUeGX33RGTYFPXs5t/VBnw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE0aELkvG/Xeo5y6o0WXRAjlediLptGz7Q
8zjDmpGFXkKBYZ6IiL7JJ2TkcHzd83bmeUeGX33RGTYFPXs5t/VBnw==
-----END PUBLIC KEY-----

View File

@ -11,9 +11,17 @@ void main() {
rs256();
print('-----------------------\n');
print('----- ECDSA SHA-256 -----');
print('----- ECDSA P-256 -----');
es256();
print('-------------------------');
print('-----------------------\n');
print('----- ECDSA secp256k -----');
es256k();
print('--------------------------\n');
print('----- RSA-PSS SHA-256 -----');
ps256();
print('---------------------------\n');
}
// HMAC SHA-256 algorithm
@ -96,7 +104,7 @@ void rs256() {
}
}
// ECDSA SHA-256 algorithm
// ECDSA P-256 algorithm
void es256() {
String token;
@ -138,3 +146,89 @@ void es256() {
}
}
}
// ECDSA secp256k algorithm
void es256k() {
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 pem = File('./example/ec_256k_private.pem').readAsStringSync();
final key = ECPrivateKey(pem);
token = jwt.sign(key, algorithm: JWTAlgorithm.ES256K);
print('Signed token: $token\n');
}
/* Verify */ {
try {
// Verify a token
final pem = File('./example/ec_256k_public.pem').readAsStringSync();
final key = ECPublicKey(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
}
}
}
// RSA-PSS SHA-256 algorithm
void ps256() {
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 pem = File('./example/rsa_pss_private.pem').readAsStringSync();
final key = RSAPrivateKey(pem);
token = jwt.sign(key, algorithm: JWTAlgorithm.PS256);
print('Signed token: $token\n');
}
/* Verify */ {
try {
// Verify a token
final pem = File('./example/rsa_pss_public.pem').readAsStringSync();
final key = RSAPublicKey(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

@ -0,0 +1,29 @@
-----BEGIN PRIVATE KEY-----
MIIE7QIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3
DQEBCDALBglghkgBZQMEAgGiAwIBIASCBKcwggSjAgEAAoIBAQC5dJxueDHgMQ3j
NL0MGpCXunskqPwJNhqE82vLKgnu1uEVie1NWwha+VpB9OPAS2B1YapiI1oFw2dM
P+jkZDP2CbuiPhZjbAcxXky5eVYiTusPDmJjHxwP3AeO7F3Ht2FQih3kT1vNOCRZ
oAM2vHh7q1iETxQAuIrgooYmd35IOPjFQ7s/9iXZSHfW6FIj6WFMx1XrDVknkHIA
KTvrYWl44oIuTygK/3vovWM9Tyh7lLAsYWt7PT8jyDmJdkEQEYty7LR4gvommcZq
5JGl8q+rk05+vD1506t5EtVyp0iVJyMfFAHeaU0bugnS6gXvWlfaGS3vngvY9SQ2
AsZ1HnQvAgMBAAECggEAFai+1FAJUyuneY+hPJalqHLJCwEJXnIJKtnbDwE7478I
zqErK8uj8CwLFITrTHwBKfRcF9YVH3Mwz0DvjCz/vI5qcYpaxwXI2UUwJME9BAi/
fySo7PanKlsCLvkAAuEx5lwbHZz/Fbqm1CjR90SPtbHPRrSNm3/yCEYy8K8n+nsp
9j87y/oP1HEL7rsaz2KTQzMCnZX1BiEGK26hkzzG5UZ0pBJEABvR6Nvj5Xx+B1xL
3aFuK/zeaZKIwdJ6yHZ4tK8A60e3uxHifNcGFfoAbLzg0U1J3RWoEdnCBUDxAwZQ
ebBH18CdXz+7MYvwFcjIJrK/HP8HVLlkDz1d91wnkQKBgQDhRKGLhxT/LibjdUKe
OIb+23bbGLKj2/hrbMqB44s/oYxjdZkIL5a9n85s1L0wgViAeLqDV5EeyCsm1vB5
YYyV9V5xMuIyQuHd34s6tce7ZhuJQgYAUMB1YkMuXtvvnE4lBUxwZesZDE1hFbQj
W8mVvSH7LU0dnqBrlPc0TVNiPQKBgQDSwYv5923mkWo2ia57LX7K3s8ueglHYxJc
/H/OGlqsnY/8QQJhVTpAgMvAYTLuA+x5wUE8YJPDj/WPxoyVNnicjQSLCfAmLrfs
VT+wRh9eRbEc3HL46r8nqBvZJX0j/mwbd3i3iCFzrh/8HLIvT+hS0gPqDgjPEF1k
HzmNlRey2wKBgQCLykgpSqlH9X7iddjiUJfNPamTKs9oic+t8jP0yJyX3ws8iTRu
9QEpKSszNA03NX0TNFghu0xt+q90ibtux48zW7HAs1/U6tY5FkjTJQ0OCL4bviH1
PidAcLrZ4rm/BpMjvBcEROrQf2bhUVOZAZOl3VliAmmxcMeCG04QYc/VzQKBgG+m
h2A6W2d9e9Y+pEevN2+EECAgGJBQBOdsAM0QGx89LY1GZ1tnCkAnu0lEdPWw8HMk
FUpGI6HDvySW290kiUruTgzWi/m+YEN8egwJag7IPub6RpJl0jTRE0e7W4tY+q6m
YTZhkM823Mzz6tWzsHFHvzkcjEAd/EvRWu0NogtnAoGABK2GucOgxdyd2W0QJloX
2y7l5W6ymIrbpSKeaDFXas/NVtls6nDseKzUTQf9RfFVXa1FEkvrRZE/Yl4sn/Xs
1tDERXraeKfcQ6A9uokEMflgBqnp8AcsBGVFfuw7xJAmF7fKwJqwOyFc8BK4nkZ8
vwWmPat2rQ7MRvKeTY11kT4=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,10 @@
-----BEGIN PUBLIC KEY-----
MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB
CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAuXScbngx4DEN4zS9DBqQ
l7p7JKj8CTYahPNryyoJ7tbhFYntTVsIWvlaQfTjwEtgdWGqYiNaBcNnTD/o5GQz
9gm7oj4WY2wHMV5MuXlWIk7rDw5iYx8cD9wHjuxdx7dhUIod5E9bzTgkWaADNrx4
e6tYhE8UALiK4KKGJnd+SDj4xUO7P/Yl2Uh31uhSI+lhTMdV6w1ZJ5ByACk762Fp
eOKCLk8oCv976L1jPU8oe5SwLGFrez0/I8g5iXZBEBGLcuy0eIL6JpnGauSRpfKv
q5NOfrw9edOreRLVcqdIlScjHxQB3mlNG7oJ0uoF71pX2hkt754L2PUkNgLGdR50
LwIDAQAB
-----END PUBLIC KEY-----

View File

@ -1,10 +1,11 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
import 'package:pointycastle/pointycastle.dart' as pc;
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
import 'exceptions.dart';
import 'keys.dart';
import 'utils.dart';
@ -19,6 +20,15 @@ abstract class JWTAlgorithm {
/// HMAC using SHA-512 hash algorithm
static const HS512 = _HMACAlgorithm('HS512');
/// RSASSA-PSS using SHA-256 hash algorithm
static const PS256 = _RSAAlgorithm('PS256');
/// RSASSA-PSS using SHA-384 hash algorithm
static const PS384 = _RSAAlgorithm('PS384');
/// RSASSA-PSS using SHA-512 hash algorithm
static const PS512 = _RSAAlgorithm('PS512');
/// RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
static const RS256 = _RSAAlgorithm('RS256');
@ -37,6 +47,9 @@ abstract class JWTAlgorithm {
/// ECDSA using P-512 curve and SHA-512 hash algorithm
static const ES512 = _ECDSAAlgorithm('ES512');
/// ECDSA using secp256k1 curve and SHA-256 hash algorithm
static const ES256K = _ECDSAAlgorithm('ES256K');
/// EdDSA using Ed25519 curve algorithm
static const EdDSA = _EdDSAAlgorithm('EdDSA');
@ -61,8 +74,16 @@ abstract class JWTAlgorithm {
return JWTAlgorithm.ES384;
case 'ES512':
return JWTAlgorithm.ES512;
case 'ES256K':
return JWTAlgorithm.ES256K;
case 'EdDSA':
return JWTAlgorithm.EdDSA;
case 'PS256':
return JWTAlgorithm.PS256;
case 'PS384':
return JWTAlgorithm.PS384;
case 'PS512':
return JWTAlgorithm.PS512;
default:
throw JWTInvalidException('unknown algorithm');
}
@ -173,16 +194,33 @@ class _RSAAlgorithm extends JWTAlgorithm {
assert(key is RSAPrivateKey, 'key must be a RSAPrivateKey');
final privateKey = key as RSAPrivateKey;
final signer = pc.Signer('${_getHash(name)}/RSA');
final params = pc.PrivateKeyParameter<pc.RSAPrivateKey>(privateKey.key);
final algorithm = _getAlgorithm(name);
final signer = pc.Signer('${_getHash(name)}/${algorithm}');
pc.CipherParameters params = pc.PrivateKeyParameter<pc.RSAPrivateKey>(
privateKey.key,
);
if (algorithm == 'PSS') {
final random = Random.secure();
final salt =
Uint8List.fromList(List.generate(32, (_) => random.nextInt(256)));
params = pc.ParametersWithSalt(
params,
salt,
);
}
signer.init(true, params);
final signature = signer.generateSignature(
Uint8List.fromList(body),
) as pc.RSASignature;
final signature = signer.generateSignature(Uint8List.fromList(body));
if (signature is pc.PSSSignature) {
return signature.bytes;
} else {
return (signature as pc.RSASignature).bytes;
}
}
@override
@ -191,13 +229,32 @@ class _RSAAlgorithm extends JWTAlgorithm {
final publicKey = key as RSAPublicKey;
try {
final signer = pc.Signer('${_getHash(name)}/RSA');
final params = pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey.key);
final algorithm = _getAlgorithm(name);
final signer = pc.Signer('${_getHash(name)}/${algorithm}');
pc.CipherParameters params = pc.PublicKeyParameter<pc.RSAPublicKey>(
publicKey.key,
);
if (algorithm == 'PSS') {
final secureRandom = pc.SecureRandom('Fortuna');
final random = Random.secure();
final seed = List.generate(32, (_) => random.nextInt(256));
secureRandom.seed(pc.KeyParameter(Uint8List.fromList(seed)));
params = pc.ParametersWithSaltConfiguration(
params,
secureRandom,
32,
);
}
signer.init(false, params);
final msg = Uint8List.fromList(body);
final sign = pc.RSASignature(Uint8List.fromList(signature));
final sign = algorithm == 'PSS'
? pc.PSSSignature(Uint8List.fromList(signature))
: pc.RSASignature(Uint8List.fromList(signature));
return signer.verifySignature(msg, sign);
} catch (ex) {
@ -208,15 +265,33 @@ class _RSAAlgorithm extends JWTAlgorithm {
String _getHash(String name) {
switch (name) {
case 'RS256':
case 'PS256':
return 'SHA-256';
case 'RS384':
case 'PS384':
return 'SHA-384';
case 'RS512':
case 'PS512':
return 'SHA-512';
default:
throw ArgumentError.value(name, 'name', 'unknown hash name');
}
}
String _getAlgorithm(String name) {
switch (name) {
case 'RS256':
case 'RS384':
case 'RS512':
return 'RSA';
case 'PS256':
case 'PS384':
case 'PS512':
return 'PSS';
default:
throw ArgumentError.value(name, 'name', 'unknown algorithm name');
}
}
}
class _ECDSAAlgorithm extends JWTAlgorithm {
@ -282,6 +357,7 @@ class _ECDSAAlgorithm extends JWTAlgorithm {
String _getHash(String name) {
switch (name) {
case 'ES256':
case 'ES256K':
return 'SHA-256';
case 'ES384':
return 'SHA-384';

View File

@ -1,6 +1,6 @@
name: dart_jsonwebtoken
description: A dart implementation of the famous javascript library 'jsonwebtoken' (JWT).
version: 2.9.1
version: 2.10.0
repository: https://github.com/jonasroussel/dart_jsonwebtoken
homepage: https://github.com/jonasroussel/dart_jsonwebtoken#readme