This commit is contained in:
Jonas Roussel
2021-02-10 18:16:10 +01:00
parent eb4da9be66
commit 85077b96e5
16 changed files with 675 additions and 158 deletions

View File

@ -1,3 +1,12 @@
## 1.6.0
- New ECDSA Algorithm (EC256, EC384, EC512)
- ECPrivateKey and ECPublicKey, two new keys for ECDSA algorithm
- PrivateKey is renamed in RSAPrivateKey
- PublicKey is renamed in RSAPublicKey
- Optimization of private & public keys parsing
- `rsa_pkcs` & `cryptography` have been removed
## 1.5.0
- Debuging `_TypeError issue on sign method` (#4)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Jonas Roussel
Copyright (c) 2021 Jonas Roussel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -4,6 +4,7 @@
A dart implementation of the famous javascript library `jsonwebtoken`.
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.
## Usage
@ -49,13 +50,16 @@ try {
}
```
### Supported Algorithms
- HS256 (HMAC / SHA256)
- HS384 (HMAC / SHA384)
- HS512 (HMAC / SHA512)
- RS256 (RSA / SHA256)
- RS384 (RSA / SHA384)
- RS512 (RSA / SHA512)
JWTAlgorithm | 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
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
ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm

5
example/ec_private.pem Normal file
View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----

4
example/ec_public.pem Normal file
View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----

View File

@ -1,7 +1,23 @@
import 'dart:io';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
// HMAC SHA-256 algorithm
void main() {
print('----- HMAC SHA-256 ------');
hs256();
print('-------------------------\n');
print('----- RSA SHA-256 -----');
rs256();
print('-----------------------\n');
print('----- ECDSA SHA-256 -----');
es256();
print('-------------------------');
}
// HMAC SHA-256 algorithm
void hs256() {
String token;
/* Sign */ {
@ -55,40 +71,10 @@ void rs256() {
);
// Sign it
token = jwt.sign(
PrivateKey(
'''
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----
''',
),
algorithm: JWTAlgorithm.RS256,
);
final pem = File('rsa_private.pem').readAsStringSync();
final key = RSAPrivateKey(pem);
token = jwt.sign(key, algorithm: JWTAlgorithm.RS256);
print('Signed token: $token\n');
}
@ -96,22 +82,53 @@ void rs256() {
/* Verify */ {
try {
// Verify a token
final jwt = JWT.verify(
token,
PublicKey(
'''
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
''',
),
);
final pem = File('rsa_public.pem').readAsStringSync();
final key = RSAPublicKey(pem);
final jwt = JWT.verify(token, key);
print('Payload: ${jwt.payload}');
} on JWTExpiredError {
print('jwt expired');
} on JWTError catch (ex) {
print(ex.message); // ex: invalid signature
}
}
}
// ECDSA SHA-256 algorithm
void es256() {
String token;
/* Sign */ {
// Create a json web token
final jwt = JWT(
{
'id': 123,
'server': {
'id': '3e4fc296',
'loc': 'euw-2',
}
},
issuer: 'https://github.com/jonasroussel/jsonwebtoken',
);
// Sign it
final pem = File('ec_private.pem').readAsStringSync();
final key = ECPrivateKey(pem);
token = jwt.sign(key, algorithm: JWTAlgorithm.ES256);
print('Signed token: $token\n');
}
/* Verify */ {
try {
// Verify a token
final pem = File('ec_public.pem').readAsStringSync();
final key = ECPublicKey(pem);
final jwt = JWT.verify(token, key);
print('Payload: ${jwt.payload}');
} on JWTExpiredError {

27
example/rsa_private.pem Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAq5QLAv9kYTgelglIhC17KdfUoinkwvQ4F0TZAp7qgmu19dCx
PPC5lhPTK8aAdfJNGrnlXw94t4BCmZbWysdUbYHg8qo7sxQMlwixGiCij8/yC3JT
fMxLgNhv7s1NMkG5JPQU1suDsyU3TLI9LcXbST5Ges480+GTZ419KnNcnBVMLmFl
nBP6J0bn2xRs5DufZ2tp8nmzZuLKjVGA4DW3b3VCZmLSQHL9Y0pTQjwkqbhjDfiu
woXGdNJsILuFqpxeVIcsEitbLPzrGEbjE/8afar38gkkBhF5Dv2bt5XD5MpeTvUH
BbL49weOoUdlCh4nrNl5qz0QqplCIMuTdiCGDwIDAQABAoIBADEDz8mBcAmd4I74
A/FOlNq1P5c75JAU+FkdX+kxwMiVQJwCjD9efYX0D+V3hYcsOv6hkHcBknjJeT0r
LB0tqF7INZzdKMTVu2sCHqXaINZ4jyrZ9CO4sPET45YSrDTpNZzq5XEfHnZ08UFF
wdhrp2NbGQNIG3RkApoYf2M7JX+o1ci/4t0Q74ZyzmbHHiKEr5VJvGsd/wajwF9r
waCdbFf7Y7atf27s4ue8pKLo8vVOkDro+gOZYXy/9jOINP9E/NmL1Ggda0mXkNe6
2DX3Riu7WDD8YqxCAWCQuuR+JdsDoAujSppgBeb4JC60Pfsk8QmjXTPqAqaBR2FS
QHh0J3ECgYEA4pWGQIgK+hb0naXCffK+l2KO7Rgj5JBLkrarIU/blpYPeIwbRUGz
qIPcDLdNzwi3CWCwlX3jh3SCeQgI3VCS+BHTJ0B0mZROoMi2jpFsbQUGHethPKj5
/iSqYrHvKfcgFowlrUGrg40rgJ3RD2H3UqT03FhzeQzg9R8yKNmVWq0CgYEAwdpp
mnDT9yEpuM6qAdhtRxwrsKorqdsQp5B5dFDIN11FW8cso6kQM08Rx/wLwx/1fOFc
tMIVxkKcNXLNZXCEPilswHqBTM8JNvxZf8pCACgHtsVClf76zC8vRKfHqN8fli0l
aPK66MBw8jeGrdVmMZ8ehmqCLiE1K8xU1O3P1ysCgYBSTxzgzkqmhLZBmlhnpkmj
9pKTAETxn5VEa4UI6diMAGEIWmUBqJdZ7OR/3gqu3ayYBH7BH2UK7MEEtG177+wj
n14EET8KFzzk3WSrr5oilEOflMTS41NFBg67IYYOyY3nguug340tkZeu9iVeoCjW
5cNpU9xGqe4CPV3cRwYhVQKBgE+AwhrC90u9YB0ZNQQ4vOiTFyAHSo0I+RWf3Q/l
KZf7OrzfPOGKLsi8PvCj4DcQXl/vFVRU3RVIsEQDZ1WZBcNwvdda5riXmJHigNo2
G0dxI2jEEgDuZ/Fh0KL5pJsPlybCMTuPgTR/6FKOjz+v64+iUKST/fvpRqNF0+zX
84utAoGARB+Oun9oJveeFL48Qbh5bxYvYfi3kEZtpqa5kh/2b0/DcaOQprfI5ERE
EAEXPTSykGI60hmlbEMWh6GcfMwHGrrZy0eWw/oQXZclx+HDIjrl869NRLt+WoWp
3vTAqqvIPiBL2BjgTVSLdwwaXKDJb53MutLozOmAliIhRxO5ywI=
-----END RSA PRIVATE KEY-----

9
example/rsa_public.pem Normal file
View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq5QLAv9kYTgelglIhC17
KdfUoinkwvQ4F0TZAp7qgmu19dCxPPC5lhPTK8aAdfJNGrnlXw94t4BCmZbWysdU
bYHg8qo7sxQMlwixGiCij8/yC3JTfMxLgNhv7s1NMkG5JPQU1suDsyU3TLI9LcXb
ST5Ges480+GTZ419KnNcnBVMLmFlnBP6J0bn2xRs5DufZ2tp8nmzZuLKjVGA4DW3
b3VCZmLSQHL9Y0pTQjwkqbhjDfiuwoXGdNJsILuFqpxeVIcsEitbLPzrGEbjE/8a
far38gkkBhF5Dv2bt5XD5MpeTvUHBbL49weOoUdlCh4nrNl5qz0QqplCIMuTdiCG
DwIDAQAB
-----END PUBLIC KEY-----

View File

@ -4,3 +4,4 @@ export 'src/jwt.dart';
export 'src/errors.dart';
export 'src/algorithms.dart';
export 'src/keys.dart';
export 'src/parser.dart';

View File

@ -2,18 +2,41 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:pointycastle/pointycastle.dart' hide PrivateKey, PublicKey;
import 'package:rsa_pkcs/rsa_pkcs.dart' hide RSAPrivateKey, RSAPublicKey;
import 'package:pointycastle/pointycastle.dart' as pc;
import 'errors.dart';
import 'keys.dart';
import 'utils.dart';
abstract class JWTAlgorithm {
static const HS256 = HMACAlgorithm('HS256');
static const HS384 = HMACAlgorithm('HS384');
static const HS512 = HMACAlgorithm('HS512');
static const RS256 = RSAAlgorithm('RS256');
static const RS384 = RSAAlgorithm('RS384');
static const RS512 = RSAAlgorithm('RS512');
/// HMAC using SHA-256 hash algorithm
static const HS256 = _HMACAlgorithm('HS256');
/// HMAC using SHA-384 hash algorithm
static const HS384 = _HMACAlgorithm('HS384');
/// HMAC using SHA-512 hash algorithm
static const HS512 = _HMACAlgorithm('HS512');
/// RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
static const RS256 = _RSAAlgorithm('RS256');
/// RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
static const RS384 = _RSAAlgorithm('RS384');
/// RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
static const RS512 = _RSAAlgorithm('RS512');
/// ECDSA using P-256 curve and SHA-256 hash algorithm
static const ES256 = _ECDSAAlgorithm('ES256');
/// ECDSA using P-384 curve and SHA-384 hash algorithm
static const ES384 = _ECDSAAlgorithm('ES384');
/// ECDSA using P-512 curve and SHA-512 hash algorithm
static const ES512 = _ECDSAAlgorithm('ES512');
/// Return the `JWTAlgorithm` from his string name
static JWTAlgorithm fromName(String name) {
switch (name) {
case 'HS256':
@ -28,6 +51,12 @@ abstract class JWTAlgorithm {
return JWTAlgorithm.RS384;
case 'RS512':
return JWTAlgorithm.RS512;
case 'ES256':
return JWTAlgorithm.ES256;
case 'ES384':
return JWTAlgorithm.ES384;
case 'ES512':
return JWTAlgorithm.ES512;
default:
throw JWTInvalidError('unknown algorithm');
}
@ -35,30 +64,40 @@ abstract class JWTAlgorithm {
const JWTAlgorithm();
/// `JWTAlgorithm` name
String get name;
List<int> sign(Key key, List<int> body);
bool verify(Key key, List<int> body, List<int> signature);
/// Create a signature of the `body` with `key`
///
/// return the signature as bytes
Uint8List sign(Key key, Uint8List body);
/// Verify the `signature` of `body` with `key`
///
/// return `true` if the signature is correct `false` otherwise
bool verify(Key key, Uint8List body, Uint8List signature);
}
class HMACAlgorithm extends JWTAlgorithm {
class _HMACAlgorithm extends JWTAlgorithm {
final String _name;
const HMACAlgorithm(this._name);
const _HMACAlgorithm(this._name);
@override
String get name => _name;
@override
List<int> sign(Key key, List<int> body) {
Uint8List sign(Key key, Uint8List body) {
assert(key is SecretKey, 'key must be a SecretKey');
final secretKey = key as SecretKey;
final hmac = Hmac(_getHash(name), utf8.encode(secretKey.key));
return hmac.convert(body).bytes;
}
@override
bool verify(Key key, List<int> body, List<int> signature) {
bool verify(Key key, Uint8List body, Uint8List signature) {
assert(key is SecretKey, 'key must be a SecretKey');
final actual = sign(key, body);
@ -86,78 +125,44 @@ class HMACAlgorithm extends JWTAlgorithm {
}
}
class RSAAlgorithm extends JWTAlgorithm {
class _RSAAlgorithm extends JWTAlgorithm {
final String _name;
const RSAAlgorithm(this._name);
const _RSAAlgorithm(this._name);
@override
String get name => _name;
@override
List<int> sign(Key key, List<int> body) {
assert(key is PrivateKey, 'key must be a PrivateKey');
final privateKey = key as PrivateKey;
Uint8List sign(Key key, Uint8List body) {
assert(key is RSAPrivateKey, 'key must be a RSAPrivateKey');
final privateKey = key as RSAPrivateKey;
final parser = RSAPKCSParser();
RSAKeyPair pair;
pair = parser.parsePEM(privateKey.key, password: privateKey.passphrase);
if (pair.private == null) {
throw JWTInvalidError('invalid private RSA key');
}
final signer = Signer('${_getHash(name)}/RSA');
final params = ParametersWithRandom(
PrivateKeyParameter<RSAPrivateKey>(
RSAPrivateKey(
pair.private.modulus,
pair.private.privateExponent,
pair.private.prime1,
pair.private.prime2,
),
),
SecureRandom('AES/CTR/PRNG'),
);
final signer = pc.Signer('${_getHash(name)}/RSA');
final params = pc.PrivateKeyParameter<pc.RSAPrivateKey>(privateKey.key);
signer.init(true, params);
RSASignature signature = signer.generateSignature(Uint8List.fromList(body));
pc.RSASignature signature = signer.generateSignature(
Uint8List.fromList(body),
);
return signature.bytes.toList(growable: false);
return signature.bytes;
}
@override
bool verify(Key key, List<int> body, List<int> signature) {
assert(key is PublicKey, 'key must be a PublicKey');
final publicKey = key as PublicKey;
final parser = RSAPKCSParser();
RSAKeyPair pair;
bool verify(Key key, Uint8List body, Uint8List signature) {
assert(key is RSAPublicKey, 'key must be a RSAPublicKey');
final publicKey = key as RSAPublicKey;
try {
pair = parser.parsePEM(publicKey.key, password: publicKey.passphrase);
assert(pair.public != null);
} catch (ex) {
throw JWTInvalidError('invalid public RSA key');
}
try {
final signer = Signer('${_getHash(name)}/RSA');
final params = ParametersWithRandom(
PublicKeyParameter<RSAPublicKey>(
RSAPublicKey(
pair.public.modulus,
BigInt.from(pair.public.publicExponent),
),
),
SecureRandom('AES/CTR/PRNG'),
);
final signer = pc.Signer('${_getHash(name)}/RSA');
final params = pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey.key);
signer.init(false, params);
final msg = Uint8List.fromList(body);
final sign = RSASignature(Uint8List.fromList(signature));
final sign = pc.RSASignature(Uint8List.fromList(signature));
return signer.verifySignature(msg, sign);
} catch (ex) {
@ -178,3 +183,66 @@ class RSAAlgorithm extends JWTAlgorithm {
}
}
}
class _ECDSAAlgorithm extends JWTAlgorithm {
final String _name;
const _ECDSAAlgorithm(this._name);
@override
String get name => _name;
@override
Uint8List sign(Key key, Uint8List body) {
assert(key is ECPrivateKey, 'key must be a ECPublicKey');
final privateKey = key as ECPrivateKey;
final signer = pc.Signer('${_getHash(name)}/DET-ECDSA');
final params = pc.PrivateKeyParameter<pc.ECPrivateKey>(privateKey.key);
signer.init(true, params);
pc.ECSignature signature = signer.generateSignature(
Uint8List.fromList(body),
);
final len = privateKey.size;
final bytes = Uint8List(len * 2);
bytes.setRange(0, len, bigIntToBytes(signature.r).toList().reversed);
bytes.setRange(len, len * 2, bigIntToBytes(signature.s).toList().reversed);
return bytes;
}
@override
bool verify(Key key, Uint8List body, Uint8List signature) {
assert(key is ECPublicKey, 'key must be a ECPublicKey');
final publicKey = key as ECPublicKey;
final signer = pc.Signer('${_getHash(name)}/DET-ECDSA');
final params = pc.PublicKeyParameter<pc.ECPublicKey>(publicKey.key);
signer.init(false, params);
final len = signature.length ~/ 2;
final sign = pc.ECSignature(
bigIntFromBytes(signature.sublist(0, len)),
bigIntFromBytes(signature.sublist(len)),
);
return signer.verifySignature(body, sign);
}
String _getHash(String name) {
switch (name) {
case 'ES256':
return 'SHA-256';
case 'ES384':
return 'SHA-384';
case 'ES512':
return 'SHA-512';
default:
throw ArgumentError.value(name, 'name', 'unknown hash name');
}
}
}

View File

@ -1,23 +1,25 @@
/// JWTError objects thrown in the case of a jwt sign or verify failure.
class JWTError extends Error {
JWTError(this.message);
/// Describes the error thrown
final String message;
@override
String toString() => 'JWTError: $message';
}
/// An error thrown when token is invalid
/// An error thrown when jwt is invalid
class JWTInvalidError extends JWTError {
JWTInvalidError(String message) : super(message);
}
/// An error thrown when token is expired
/// An error thrown when jwt is expired
class JWTExpiredError extends JWTError {
JWTExpiredError() : super('jwt expired');
}
/// An error thrown when token is not active
/// An error thrown when jwt is not active
class JWTNotActiveError extends JWTError {
JWTNotActiveError() : super('jwt not active');
}

View File

@ -1,15 +1,17 @@
import 'dart:convert';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import './utils.dart';
import 'algorithms.dart';
import 'errors.dart';
import 'keys.dart';
import 'utils.dart';
class JWT {
/// Verify a token.
///
/// `key` must be
/// - SecretKey with HS256 algorithm
/// - PublicKey with RS256 algorithm
/// - SecretKey with HMAC algorithm
/// - RSAPublicKey with RSA algorithm
/// - ECPublicKey with ECDSA algorithm
static JWT verify(
String token,
Key key, {
@ -52,8 +54,9 @@ class JWT {
if (payload is Map) {
// exp
if (checkExpiresIn && payload.containsKey('exp')) {
final exp =
DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
final exp = DateTime.fromMillisecondsSinceEpoch(
payload['exp'] * 1000,
);
if (exp.isBefore(DateTime.now())) {
throw JWTExpiredError();
}
@ -61,8 +64,9 @@ class JWT {
// nbf
if (checkNotBefore && payload.containsKey('nbf')) {
final nbf =
DateTime.fromMillisecondsSinceEpoch(payload['nbf'] * 1000);
final nbf = DateTime.fromMillisecondsSinceEpoch(
payload['nbf'] * 1000,
);
if (nbf.isAfter(DateTime.now())) {
throw JWTNotActiveError();
}
@ -73,8 +77,9 @@ class JWT {
if (!payload.containsKey('iat')) {
throw JWTInvalidError('invalid issue at');
}
final iat =
DateTime.fromMillisecondsSinceEpoch(payload['iat'] * 1000);
final iat = DateTime.fromMillisecondsSinceEpoch(
payload['iat'] * 1000,
);
if (!iat.isAtSameMomentAs(DateTime.now())) {
throw JWTInvalidError('invalid issue at');
}
@ -154,8 +159,9 @@ class JWT {
/// Sign and generate a new token.
///
/// `key` must be
/// - SecretKey with HS256 algorithm
/// - PrivateKey with RS256 algorithm
/// - SecretKey with HMAC algorithm
/// - RSAPrivateKey with RSA algorithm
/// - ECPrivateKey with ECDSA algorithm
String sign(
Key key, {
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
@ -194,7 +200,8 @@ class JWT {
);
} catch (ex) {
throw JWTError(
'invalid payload json format (Map keys must be String type)');
'invalid payload json format (Map keys must be String type)',
);
}
final body = '${b64Header}.${b64Payload}';

View File

@ -1,24 +1,50 @@
import 'package:pointycastle/pointycastle.dart' as pc;
import 'parser.dart';
abstract class Key {}
/// For HS256 algorithm
/// For HMAC algorithms
class SecretKey extends Key {
String key;
SecretKey(this.key);
}
/// For RS256 algorithm, in sign method
class PrivateKey extends Key {
String key;
String passphrase;
/// For RSA algorithm, in sign method
class RSAPrivateKey extends Key {
pc.RSAPrivateKey key;
PrivateKey(this.key, [this.passphrase = '']);
RSAPrivateKey(String pem) {
key = parseRSAPrivateKeyPEM(pem);
}
}
/// For RS256 algorithm, in verify method
class PublicKey extends Key {
String key;
String passphrase;
/// For RSA algorithm, in verify method
class RSAPublicKey extends Key {
pc.RSAPublicKey key;
PublicKey(this.key, [this.passphrase = '']);
RSAPublicKey(String pem) {
key = parseRSAPublicKeyPEM(pem);
}
}
/// For ECDSA algorithm, in sign method
class ECPrivateKey extends Key {
pc.ECPrivateKey key;
int size;
ECPrivateKey(String pem) {
key = parseECPrivateKeyPEM(pem);
size = (key.parameters.curve.fieldSize / 8).round();
}
}
/// For ECDSA algorithm, in verify method
class ECPublicKey extends Key {
pc.ECPublicKey key;
ECPublicKey(String pem) {
key = parseECPublicKeyPEM(pem);
}
}

275
lib/src/parser.dart Normal file
View File

@ -0,0 +1,275 @@
import 'dart:typed_data';
import 'dart:convert';
import 'package:pointycastle/pointycastle.dart' hide ECPoint;
import 'package:pointycastle/ecc/ecc_fp.dart';
import 'utils.dart';
// RSA Private Key -> PKCS#1 format
const String _pkcs1RSAPrivateHeader = '-----BEGIN RSA PRIVATE KEY-----';
const String _pkcs1RSAPrivateFooter = '-----END RSA PRIVATE KEY-----';
// RSA Private Key -> PKCS#8 format
const String _pkcs8RSAPrivateHeader = '-----BEGIN PRIVATE KEY-----';
const String _pkcs8RSAPrivateFooter = '-----END PRIVATE KEY-----';
// RSA Public Key -> PKCS#1 format
const String _pkcs1RSAPublicHeader = '-----BEGIN RSA PUBLIC KEY-----';
const String _pkcs1RSAPublicFooter = '-----END RSA PUBLIC KEY-----';
// RSA Public Key -> PKCS#1 format
const String _pkcs8RSAPublicHeader = '-----BEGIN PUBLIC KEY-----';
const String _pkcs8RSAPublicFooter = '-----END PUBLIC KEY-----';
// ECDSA Private Key -> SEC 1 format
const String _sec1ECPrivateHeader = '-----BEGIN EC PRIVATE KEY-----';
const String _sec1ECPrivateFooter = '-----END EC PRIVATE KEY-----';
// ECDSA Private Key -> PKCS#8 format
const String _pkcs8ECPrivateHeader = '-----BEGIN PRIVATE KEY-----';
const String _pkcs8ECPrivateFooter = '-----END PRIVATE KEY-----';
// ECDSA Public Key -> PKCS#8 format
const String _pkcs8ECPublicHeader = '-----BEGIN PUBLIC KEY-----';
const String _pkcs8ECPublicFooter = '-----END PUBLIC KEY-----';
// ECDSA Curves OID to DomainName
const Map<String, String> _ecCurves = const {
'1.2.840.10045.3.1.7': 'prime256v1', // P-256
'1.3.132.0.10': 'secp256k1', // P-256
'1.3.132.0.34': 'secp384r1', // P-384
'1.3.132.0.35': 'secp521r1', // P-512
};
/// RSA Private Key -> PKCS#1 parser
RSAPrivateKey _pkcs1RSAPrivateKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final values = seq.elements.cast<ASN1Integer>();
return RSAPrivateKey(
values[1].integer, // modulus
values[3].integer, // privateExponent
values[4].integer, // prime1
values[5].integer, // prime2
);
}
/// RSA Private Key -> PKCS#8 parser
RSAPrivateKey _pkcs8RSAPrivateKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final keySeq = seq.elements[2] as ASN1OctetString;
final keyParser = ASN1Parser(keySeq.octets);
final valuesSeq = keyParser.nextObject() as ASN1Sequence;
final values = valuesSeq.elements.cast<ASN1Integer>();
return RSAPrivateKey(
values[1].integer, // modulus
values[3].integer, // privateExponent
values[4].integer, // prime1
values[5].integer, // prime2
);
}
/// RSA Public Key -> PKCS#1 parser
RSAPublicKey _pkcs1RSAPublicKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final values = seq.elements.cast<ASN1Integer>();
return RSAPublicKey(
values[0].integer, // modulus
values[1].integer, // publicExponent
);
}
/// RSA Public Key -> PKCS#8 parser
RSAPublicKey _pkcs8RSAPublicKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final keySeq = seq.elements[1] as ASN1BitString;
final keyParser = ASN1Parser(keySeq.stringValues);
final valuesSeq = keyParser.nextObject() as ASN1Sequence;
final values = valuesSeq.elements.cast<ASN1Integer>();
return RSAPublicKey(
values[0].integer, // modulus
values[1].integer, // publicExponent
);
}
/// ECDSA Private Key -> SEC 1 parser
ECPrivateKey _sec1ECPrivateKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final privateKey = seq.elements[1] as ASN1OctetString;
final params = seq.elements[2];
final paramsParser = ASN1Parser(params.valueBytes);
final oid = (paramsParser.nextObject() as ASN1ObjectIdentifier)
.objectIdentifierAsString;
final curve = _ecCurves[oid];
return ECPrivateKey(
decodeBigInt(privateKey.valueBytes),
ECDomainParameters(curve),
);
}
/// ECDSA Private Key -> PKCS#8 parser
ECPrivateKey _pkcs8ECPrivateKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final oidSeq = seq.elements[1] as ASN1Sequence;
final oid =
(oidSeq.elements[1] as ASN1ObjectIdentifier).objectIdentifierAsString;
final curve = _ecCurves[oid];
final privateKeyParser = ASN1Parser(seq.elements[2].valueBytes);
final privateKeySeq = privateKeyParser.nextObject() as ASN1Sequence;
final privateKey = (privateKeySeq.elements[1] as ASN1OctetString);
return ECPrivateKey(
decodeBigInt(privateKey.valueBytes),
ECDomainParameters(curve),
);
}
/// ECDSA Public Key -> PKCS#8 parser
ECPublicKey _pkcs8ECPublicKey(Uint8List bytes) {
final parser = ASN1Parser(bytes);
final seq = parser.nextObject() as ASN1Sequence;
final oidSeq = seq.elements[0] as ASN1Sequence;
final oid =
(oidSeq.elements[1] as ASN1ObjectIdentifier).objectIdentifierAsString;
final curve = _ecCurves[oid];
var publicKeyBytes = seq.elements[1].valueBytes;
if (publicKeyBytes[0] == 0) {
publicKeyBytes = publicKeyBytes.sublist(1);
}
final compressed = publicKeyBytes[0] != 4;
final x = publicKeyBytes.sublist(1, (publicKeyBytes.length / 2).round());
final y = publicKeyBytes.sublist(1 + x.length, publicKeyBytes.length);
final bigX = decodeBigIntWithSign(1, x);
final bigY = decodeBigIntWithSign(1, y);
final params = ECDomainParameters(curve);
return ECPublicKey(
ECPoint(
params.curve,
params.curve.fromBigInteger(bigX),
params.curve.fromBigInteger(bigY),
compressed,
),
params,
);
}
/// Parse RSA private key from pem string
RSAPrivateKey parseRSAPrivateKeyPEM(String pem) {
if (pem.contains(_pkcs1RSAPrivateHeader) &&
pem.contains(_pkcs1RSAPrivateFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs1RSAPrivateHeader) + _pkcs1RSAPrivateHeader.length,
pem.indexOf(_pkcs1RSAPrivateFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs1RSAPrivateKey(base64.decode(data));
} else if (pem.contains(_pkcs8RSAPrivateHeader) &&
pem.contains(_pkcs8RSAPrivateFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs8RSAPrivateHeader) + _pkcs8RSAPrivateHeader.length,
pem.indexOf(_pkcs8RSAPrivateFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs8RSAPrivateKey(base64.decode(data));
} else {
return null;
}
}
/// Parse RSA public key from pem string
RSAPublicKey parseRSAPublicKeyPEM(String pem) {
if (pem.contains(_pkcs1RSAPublicHeader) &&
pem.contains(_pkcs1RSAPublicFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs1RSAPublicHeader) + _pkcs1RSAPublicHeader.length,
pem.indexOf(_pkcs1RSAPublicFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs1RSAPublicKey(base64.decode(data));
} else if (pem.contains(_pkcs8RSAPublicHeader) &&
pem.contains(_pkcs8RSAPublicFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs8RSAPublicHeader) + _pkcs8RSAPublicHeader.length,
pem.indexOf(_pkcs8RSAPublicFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs8RSAPublicKey(base64.decode(data));
} else {
return null;
}
}
/// Parse ECDSA private key from pem string
ECPrivateKey parseECPrivateKeyPEM(String pem) {
if (pem.contains(_sec1ECPrivateHeader) &&
pem.contains(_sec1ECPrivateFooter)) {
final data = pem
.substring(
pem.indexOf(_sec1ECPrivateHeader) + _sec1ECPrivateHeader.length,
pem.indexOf(_sec1ECPrivateFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _sec1ECPrivateKey(base64.decode(data));
} else if (pem.contains(_pkcs8ECPrivateHeader) &&
pem.contains(_pkcs8ECPrivateFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs8ECPrivateHeader) + _pkcs8ECPrivateHeader.length,
pem.indexOf(_pkcs8ECPrivateFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs8ECPrivateKey(base64.decode(data));
} else {
return null;
}
}
/// Parse ECDSA public key from pem string
ECPublicKey parseECPublicKeyPEM(String pem) {
if (pem.contains(_pkcs8ECPublicHeader) &&
pem.contains(_pkcs8ECPublicFooter)) {
final data = pem
.substring(
pem.indexOf(_pkcs8ECPublicHeader) + _pkcs8ECPublicHeader.length,
pem.indexOf(_pkcs8ECPublicFooter),
)
.replaceAll(RegExp(r'[\n\r ]'), '');
return _pkcs8ECPublicKey(base64.decode(data));
} else {
return null;
}
}

View File

@ -1,5 +1,7 @@
import 'dart:convert';
import 'dart:typed_data';
final jsonBase64 = json.fuse(utf8.fuse(base64Url));
String base64Unpadded(String value) {
@ -24,3 +26,66 @@ String base64Padded(String value) {
int secondsSinceEpoch(DateTime time) {
return time.millisecondsSinceEpoch ~/ 1000;
}
BigInt decodeBigInt(List<int> bytes) {
var negative = bytes.isNotEmpty && bytes[0] & 0x80 == 0x80;
BigInt result;
if (bytes.length == 1) {
result = BigInt.from(bytes[0]);
} else {
result = BigInt.zero;
for (var i = 0; i < bytes.length; i++) {
var item = bytes[bytes.length - i - 1];
result |= (BigInt.from(item) << (8 * i));
}
}
if (result == BigInt.zero) return BigInt.zero;
return negative ? result.toSigned(result.bitLength) : result;
}
BigInt decodeBigIntWithSign(int sign, List<int> bytes) {
if (sign == 0) return BigInt.zero;
BigInt result;
if (bytes.length == 1) {
result = BigInt.from(bytes[0]);
} else {
result = BigInt.zero;
for (var i = 0; i < bytes.length; i++) {
var item = bytes[bytes.length - i - 1];
result |= (BigInt.from(item) << (8 * i));
}
}
if (result == BigInt.zero) return BigInt.zero;
return sign < 0
? result.toSigned(result.bitLength)
: result.toUnsigned(result.bitLength);
}
Uint8List bigIntToBytes(BigInt v) {
final _b256 = BigInt.from(256);
List<int> bytes = List();
while (v.sign != 0) {
bytes.add((v % _b256).toInt());
v = v ~/ _b256;
}
return Uint8List.fromList(bytes);
}
BigInt bigIntFromBytes(Uint8List bytes) {
final _b256 = BigInt.from(256);
return bytes.fold(BigInt.zero, (a, b) => a * _b256 + BigInt.from(b));
}

View File

@ -1,6 +1,6 @@
name: dart_jsonwebtoken
description: A dart implementation of the famous javascript library 'jsonwebtoken' (JWT).
version: 1.5.0
version: 1.6.0
repository: https://github.com/jonasroussel/jsonwebtoken
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
@ -9,9 +9,7 @@ environment:
dependencies:
crypto: ^2.1.5
rsa_pkcs: ^1.1.4
pointycastle: ^2.0.0
cryptography: ^1.4.1
pointycastle: ^2.0.1
dev_dependencies:
pedantic: ^1.9.2