mirror of
https://github.com/jonasroussel/dart_jsonwebtoken.git
synced 2025-08-26 06:10:22 +08:00
v1.6.0
This commit is contained in:
@ -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
|
## 1.5.0
|
||||||
|
|
||||||
- Debuging `_TypeError issue on sign method` (#4)
|
- Debuging `_TypeError issue on sign method` (#4)
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
20
README.md
20
README.md
@ -4,6 +4,7 @@
|
|||||||
A dart implementation of the famous javascript library `jsonwebtoken`.
|
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.
|
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.
|
https://jwt.io allows you to decode, verify and generate JWT.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -49,13 +50,16 @@ try {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Supported Algorithms
|
### Supported Algorithms
|
||||||
|
|
||||||
- HS256 (HMAC / SHA256)
|
JWTAlgorithm | Digital Signature or MAC Algorithm
|
||||||
- HS384 (HMAC / SHA384)
|
-------------|-----------------------------------
|
||||||
- HS512 (HMAC / SHA512)
|
HS256 | HMAC using SHA-256 hash algorithm
|
||||||
|
HS384 | HMAC using SHA-384 hash algorithm
|
||||||
- RS256 (RSA / SHA256)
|
HS512 | HMAC using SHA-512 hash algorithm
|
||||||
- RS384 (RSA / SHA384)
|
RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
|
||||||
- RS512 (RSA / SHA512)
|
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
5
example/ec_private.pem
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
|
||||||
|
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
|
||||||
|
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
|
||||||
|
-----END PRIVATE KEY-----
|
4
example/ec_public.pem
Normal file
4
example/ec_public.pem
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
||||||
|
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
||||||
|
-----END PUBLIC KEY-----
|
@ -1,7 +1,23 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||||
|
|
||||||
// HMAC SHA-256 algorithm
|
|
||||||
void main() {
|
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;
|
String token;
|
||||||
|
|
||||||
/* Sign */ {
|
/* Sign */ {
|
||||||
@ -55,40 +71,10 @@ void rs256() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Sign it
|
// Sign it
|
||||||
token = jwt.sign(
|
final pem = File('rsa_private.pem').readAsStringSync();
|
||||||
PrivateKey(
|
final key = RSAPrivateKey(pem);
|
||||||
'''
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
token = jwt.sign(key, algorithm: JWTAlgorithm.RS256);
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
print('Signed token: $token\n');
|
print('Signed token: $token\n');
|
||||||
}
|
}
|
||||||
@ -96,22 +82,53 @@ void rs256() {
|
|||||||
/* Verify */ {
|
/* Verify */ {
|
||||||
try {
|
try {
|
||||||
// Verify a token
|
// Verify a token
|
||||||
final jwt = JWT.verify(
|
final pem = File('rsa_public.pem').readAsStringSync();
|
||||||
token,
|
final key = RSAPublicKey(pem);
|
||||||
PublicKey(
|
|
||||||
'''
|
final jwt = JWT.verify(token, key);
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
|
print('Payload: ${jwt.payload}');
|
||||||
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
|
} on JWTExpiredError {
|
||||||
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
|
print('jwt expired');
|
||||||
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
|
} on JWTError catch (ex) {
|
||||||
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
|
print(ex.message); // ex: invalid signature
|
||||||
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
|
}
|
||||||
MwIDAQAB
|
}
|
||||||
-----END PUBLIC KEY-----
|
}
|
||||||
''',
|
|
||||||
),
|
// 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}');
|
print('Payload: ${jwt.payload}');
|
||||||
} on JWTExpiredError {
|
} on JWTExpiredError {
|
||||||
|
27
example/rsa_private.pem
Normal file
27
example/rsa_private.pem
Normal 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
9
example/rsa_public.pem
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq5QLAv9kYTgelglIhC17
|
||||||
|
KdfUoinkwvQ4F0TZAp7qgmu19dCxPPC5lhPTK8aAdfJNGrnlXw94t4BCmZbWysdU
|
||||||
|
bYHg8qo7sxQMlwixGiCij8/yC3JTfMxLgNhv7s1NMkG5JPQU1suDsyU3TLI9LcXb
|
||||||
|
ST5Ges480+GTZ419KnNcnBVMLmFlnBP6J0bn2xRs5DufZ2tp8nmzZuLKjVGA4DW3
|
||||||
|
b3VCZmLSQHL9Y0pTQjwkqbhjDfiuwoXGdNJsILuFqpxeVIcsEitbLPzrGEbjE/8a
|
||||||
|
far38gkkBhF5Dv2bt5XD5MpeTvUHBbL49weOoUdlCh4nrNl5qz0QqplCIMuTdiCG
|
||||||
|
DwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
@ -4,3 +4,4 @@ export 'src/jwt.dart';
|
|||||||
export 'src/errors.dart';
|
export 'src/errors.dart';
|
||||||
export 'src/algorithms.dart';
|
export 'src/algorithms.dart';
|
||||||
export 'src/keys.dart';
|
export 'src/keys.dart';
|
||||||
|
export 'src/parser.dart';
|
||||||
|
@ -2,18 +2,41 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||||
import 'package:pointycastle/pointycastle.dart' hide PrivateKey, PublicKey;
|
|
||||||
import 'package:rsa_pkcs/rsa_pkcs.dart' hide RSAPrivateKey, RSAPublicKey;
|
import 'errors.dart';
|
||||||
|
import 'keys.dart';
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
abstract class JWTAlgorithm {
|
abstract class JWTAlgorithm {
|
||||||
static const HS256 = HMACAlgorithm('HS256');
|
/// HMAC using SHA-256 hash algorithm
|
||||||
static const HS384 = HMACAlgorithm('HS384');
|
static const HS256 = _HMACAlgorithm('HS256');
|
||||||
static const HS512 = HMACAlgorithm('HS512');
|
|
||||||
static const RS256 = RSAAlgorithm('RS256');
|
|
||||||
static const RS384 = RSAAlgorithm('RS384');
|
|
||||||
static const RS512 = RSAAlgorithm('RS512');
|
|
||||||
|
|
||||||
|
/// 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) {
|
static JWTAlgorithm fromName(String name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'HS256':
|
case 'HS256':
|
||||||
@ -28,6 +51,12 @@ abstract class JWTAlgorithm {
|
|||||||
return JWTAlgorithm.RS384;
|
return JWTAlgorithm.RS384;
|
||||||
case 'RS512':
|
case 'RS512':
|
||||||
return JWTAlgorithm.RS512;
|
return JWTAlgorithm.RS512;
|
||||||
|
case 'ES256':
|
||||||
|
return JWTAlgorithm.ES256;
|
||||||
|
case 'ES384':
|
||||||
|
return JWTAlgorithm.ES384;
|
||||||
|
case 'ES512':
|
||||||
|
return JWTAlgorithm.ES512;
|
||||||
default:
|
default:
|
||||||
throw JWTInvalidError('unknown algorithm');
|
throw JWTInvalidError('unknown algorithm');
|
||||||
}
|
}
|
||||||
@ -35,30 +64,40 @@ abstract class JWTAlgorithm {
|
|||||||
|
|
||||||
const JWTAlgorithm();
|
const JWTAlgorithm();
|
||||||
|
|
||||||
|
/// `JWTAlgorithm` name
|
||||||
String get 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;
|
final String _name;
|
||||||
|
|
||||||
const HMACAlgorithm(this._name);
|
const _HMACAlgorithm(this._name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => _name;
|
String get name => _name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<int> sign(Key key, List<int> body) {
|
Uint8List sign(Key key, Uint8List body) {
|
||||||
assert(key is SecretKey, 'key must be a SecretKey');
|
assert(key is SecretKey, 'key must be a SecretKey');
|
||||||
final secretKey = key as SecretKey;
|
final secretKey = key as SecretKey;
|
||||||
|
|
||||||
final hmac = Hmac(_getHash(name), utf8.encode(secretKey.key));
|
final hmac = Hmac(_getHash(name), utf8.encode(secretKey.key));
|
||||||
|
|
||||||
return hmac.convert(body).bytes;
|
return hmac.convert(body).bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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');
|
assert(key is SecretKey, 'key must be a SecretKey');
|
||||||
|
|
||||||
final actual = sign(key, body);
|
final actual = sign(key, body);
|
||||||
@ -86,78 +125,44 @@ class HMACAlgorithm extends JWTAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RSAAlgorithm extends JWTAlgorithm {
|
class _RSAAlgorithm extends JWTAlgorithm {
|
||||||
final String _name;
|
final String _name;
|
||||||
|
|
||||||
const RSAAlgorithm(this._name);
|
const _RSAAlgorithm(this._name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => _name;
|
String get name => _name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<int> sign(Key key, List<int> body) {
|
Uint8List sign(Key key, Uint8List body) {
|
||||||
assert(key is PrivateKey, 'key must be a PrivateKey');
|
assert(key is RSAPrivateKey, 'key must be a RSAPrivateKey');
|
||||||
final privateKey = key as PrivateKey;
|
final privateKey = key as RSAPrivateKey;
|
||||||
|
|
||||||
final parser = RSAPKCSParser();
|
final signer = pc.Signer('${_getHash(name)}/RSA');
|
||||||
RSAKeyPair pair;
|
final params = pc.PrivateKeyParameter<pc.RSAPrivateKey>(privateKey.key);
|
||||||
|
|
||||||
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'),
|
|
||||||
);
|
|
||||||
|
|
||||||
signer.init(true, params);
|
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
|
@override
|
||||||
bool verify(Key key, List<int> body, List<int> signature) {
|
bool verify(Key key, Uint8List body, Uint8List signature) {
|
||||||
assert(key is PublicKey, 'key must be a PublicKey');
|
assert(key is RSAPublicKey, 'key must be a RSAPublicKey');
|
||||||
final publicKey = key as PublicKey;
|
final publicKey = key as RSAPublicKey;
|
||||||
|
|
||||||
final parser = RSAPKCSParser();
|
|
||||||
RSAKeyPair pair;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pair = parser.parsePEM(publicKey.key, password: publicKey.passphrase);
|
final signer = pc.Signer('${_getHash(name)}/RSA');
|
||||||
assert(pair.public != null);
|
final params = pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey.key);
|
||||||
} 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'),
|
|
||||||
);
|
|
||||||
|
|
||||||
signer.init(false, params);
|
signer.init(false, params);
|
||||||
|
|
||||||
final msg = Uint8List.fromList(body);
|
final msg = Uint8List.fromList(body);
|
||||||
final sign = RSASignature(Uint8List.fromList(signature));
|
final sign = pc.RSASignature(Uint8List.fromList(signature));
|
||||||
|
|
||||||
return signer.verifySignature(msg, sign);
|
return signer.verifySignature(msg, sign);
|
||||||
} catch (ex) {
|
} 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
|
/// JWTError objects thrown in the case of a jwt sign or verify failure.
|
||||||
class JWTError extends Error {
|
class JWTError extends Error {
|
||||||
JWTError(this.message);
|
JWTError(this.message);
|
||||||
|
|
||||||
|
/// Describes the error thrown
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'JWTError: $message';
|
String toString() => 'JWTError: $message';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error thrown when token is invalid
|
/// An error thrown when jwt is invalid
|
||||||
class JWTInvalidError extends JWTError {
|
class JWTInvalidError extends JWTError {
|
||||||
JWTInvalidError(String message) : super(message);
|
JWTInvalidError(String message) : super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error thrown when token is expired
|
/// An error thrown when jwt is expired
|
||||||
class JWTExpiredError extends JWTError {
|
class JWTExpiredError extends JWTError {
|
||||||
JWTExpiredError() : super('jwt expired');
|
JWTExpiredError() : super('jwt expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error thrown when token is not active
|
/// An error thrown when jwt is not active
|
||||||
class JWTNotActiveError extends JWTError {
|
class JWTNotActiveError extends JWTError {
|
||||||
JWTNotActiveError() : super('jwt not active');
|
JWTNotActiveError() : super('jwt not active');
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
import 'algorithms.dart';
|
||||||
|
import 'errors.dart';
|
||||||
import './utils.dart';
|
import 'keys.dart';
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
class JWT {
|
class JWT {
|
||||||
/// Verify a token.
|
/// Verify a token.
|
||||||
///
|
///
|
||||||
/// `key` must be
|
/// `key` must be
|
||||||
/// - SecretKey with HS256 algorithm
|
/// - SecretKey with HMAC algorithm
|
||||||
/// - PublicKey with RS256 algorithm
|
/// - RSAPublicKey with RSA algorithm
|
||||||
|
/// - ECPublicKey with ECDSA algorithm
|
||||||
static JWT verify(
|
static JWT verify(
|
||||||
String token,
|
String token,
|
||||||
Key key, {
|
Key key, {
|
||||||
@ -52,8 +54,9 @@ class JWT {
|
|||||||
if (payload is Map) {
|
if (payload is Map) {
|
||||||
// exp
|
// exp
|
||||||
if (checkExpiresIn && payload.containsKey('exp')) {
|
if (checkExpiresIn && payload.containsKey('exp')) {
|
||||||
final exp =
|
final exp = DateTime.fromMillisecondsSinceEpoch(
|
||||||
DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
|
payload['exp'] * 1000,
|
||||||
|
);
|
||||||
if (exp.isBefore(DateTime.now())) {
|
if (exp.isBefore(DateTime.now())) {
|
||||||
throw JWTExpiredError();
|
throw JWTExpiredError();
|
||||||
}
|
}
|
||||||
@ -61,8 +64,9 @@ class JWT {
|
|||||||
|
|
||||||
// nbf
|
// nbf
|
||||||
if (checkNotBefore && payload.containsKey('nbf')) {
|
if (checkNotBefore && payload.containsKey('nbf')) {
|
||||||
final nbf =
|
final nbf = DateTime.fromMillisecondsSinceEpoch(
|
||||||
DateTime.fromMillisecondsSinceEpoch(payload['nbf'] * 1000);
|
payload['nbf'] * 1000,
|
||||||
|
);
|
||||||
if (nbf.isAfter(DateTime.now())) {
|
if (nbf.isAfter(DateTime.now())) {
|
||||||
throw JWTNotActiveError();
|
throw JWTNotActiveError();
|
||||||
}
|
}
|
||||||
@ -73,8 +77,9 @@ class JWT {
|
|||||||
if (!payload.containsKey('iat')) {
|
if (!payload.containsKey('iat')) {
|
||||||
throw JWTInvalidError('invalid issue at');
|
throw JWTInvalidError('invalid issue at');
|
||||||
}
|
}
|
||||||
final iat =
|
final iat = DateTime.fromMillisecondsSinceEpoch(
|
||||||
DateTime.fromMillisecondsSinceEpoch(payload['iat'] * 1000);
|
payload['iat'] * 1000,
|
||||||
|
);
|
||||||
if (!iat.isAtSameMomentAs(DateTime.now())) {
|
if (!iat.isAtSameMomentAs(DateTime.now())) {
|
||||||
throw JWTInvalidError('invalid issue at');
|
throw JWTInvalidError('invalid issue at');
|
||||||
}
|
}
|
||||||
@ -154,8 +159,9 @@ class JWT {
|
|||||||
/// Sign and generate a new token.
|
/// Sign and generate a new token.
|
||||||
///
|
///
|
||||||
/// `key` must be
|
/// `key` must be
|
||||||
/// - SecretKey with HS256 algorithm
|
/// - SecretKey with HMAC algorithm
|
||||||
/// - PrivateKey with RS256 algorithm
|
/// - RSAPrivateKey with RSA algorithm
|
||||||
|
/// - ECPrivateKey with ECDSA algorithm
|
||||||
String sign(
|
String sign(
|
||||||
Key key, {
|
Key key, {
|
||||||
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
|
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
|
||||||
@ -194,7 +200,8 @@ class JWT {
|
|||||||
);
|
);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw JWTError(
|
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}';
|
final body = '${b64Header}.${b64Payload}';
|
||||||
|
@ -1,24 +1,50 @@
|
|||||||
|
import 'package:pointycastle/pointycastle.dart' as pc;
|
||||||
|
|
||||||
|
import 'parser.dart';
|
||||||
|
|
||||||
abstract class Key {}
|
abstract class Key {}
|
||||||
|
|
||||||
/// For HS256 algorithm
|
/// For HMAC algorithms
|
||||||
class SecretKey extends Key {
|
class SecretKey extends Key {
|
||||||
String key;
|
String key;
|
||||||
|
|
||||||
SecretKey(this.key);
|
SecretKey(this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For RS256 algorithm, in sign method
|
/// For RSA algorithm, in sign method
|
||||||
class PrivateKey extends Key {
|
class RSAPrivateKey extends Key {
|
||||||
String key;
|
pc.RSAPrivateKey key;
|
||||||
String passphrase;
|
|
||||||
|
|
||||||
PrivateKey(this.key, [this.passphrase = '']);
|
RSAPrivateKey(String pem) {
|
||||||
|
key = parseRSAPrivateKeyPEM(pem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For RS256 algorithm, in verify method
|
/// For RSA algorithm, in verify method
|
||||||
class PublicKey extends Key {
|
class RSAPublicKey extends Key {
|
||||||
String key;
|
pc.RSAPublicKey key;
|
||||||
String passphrase;
|
|
||||||
|
|
||||||
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
275
lib/src/parser.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
final jsonBase64 = json.fuse(utf8.fuse(base64Url));
|
final jsonBase64 = json.fuse(utf8.fuse(base64Url));
|
||||||
|
|
||||||
String base64Unpadded(String value) {
|
String base64Unpadded(String value) {
|
||||||
@ -24,3 +26,66 @@ String base64Padded(String value) {
|
|||||||
int secondsSinceEpoch(DateTime time) {
|
int secondsSinceEpoch(DateTime time) {
|
||||||
return time.millisecondsSinceEpoch ~/ 1000;
|
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));
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: dart_jsonwebtoken
|
name: dart_jsonwebtoken
|
||||||
description: A dart implementation of the famous javascript library 'jsonwebtoken' (JWT).
|
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
|
repository: https://github.com/jonasroussel/jsonwebtoken
|
||||||
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
|
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
|
||||||
|
|
||||||
@ -9,9 +9,7 @@ environment:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
crypto: ^2.1.5
|
crypto: ^2.1.5
|
||||||
rsa_pkcs: ^1.1.4
|
pointycastle: ^2.0.1
|
||||||
pointycastle: ^2.0.0
|
|
||||||
cryptography: ^1.4.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pedantic: ^1.9.2
|
pedantic: ^1.9.2
|
||||||
|
Reference in New Issue
Block a user