This commit is contained in:
Jonas Roussel
2020-06-12 13:35:07 +02:00
parent 3377ae8b0b
commit 997fe84f7f
8 changed files with 168 additions and 46 deletions

View File

@ -1,3 +1,10 @@
## 1.0.0
- New RSA Algorithm (RS256)
- Keys a now using an abstract class 'Key' instead of a string
- SecretKey: for HMAC (HS256)
- PrivateKey & PublicKey: for RSA (RS256)
## 0.2.1
- Formatting

View File

@ -1,8 +1,11 @@
# JsonWebToken
# JSON Web Token
[![pub package](https://img.shields.io/pub/v/dart_jsonwebtoken.svg)](https://pub.dev/packages/dart_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.
https://jwt.io allows you to decode, verify and generate JWT.
## Usage
### Import
@ -25,8 +28,8 @@ final jwt = JWT(
issuer: 'https://github.com/jonasroussel/jsonwebtoken',
);
// Sign it
token = jwt.sign('secret-key');
// Sign it (default with HS256 algorithm)
token = jwt.sign(SecretKey('secret passphrase'));
print('Signed token: $token\n');
```
@ -36,7 +39,7 @@ print('Signed token: $token\n');
```dart
try {
// Verify a token
final jwt = JWT.verify(token, 'secret-key');
final jwt = JWT.verify(token, SecretKey('secret passphrase'));
print('Payload: ${jwt.payload}');
} on JWTExpiredError {
@ -45,3 +48,9 @@ try {
print(ex.message); // ex: invalid signature
}
```
### Supported Algorithms
- HS256 (HMAC / SHA256)
- RS256 (RSA / SHA256)

View File

@ -17,7 +17,7 @@ main() {
);
// Sign it
token = jwt.sign('secret-key');
token = jwt.sign(SecretKey('secret passphrase'));
print('Signed token: $token\n');
}
@ -25,7 +25,7 @@ main() {
/* Verify */ {
try {
// Verify a token
final jwt = JWT.verify(token, 'secret-key');
final jwt = JWT.verify(token, SecretKey('secret passphrase'));
print('Payload: ${jwt.payload}');
} on JWTExpiredError {

View File

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

View File

@ -1,15 +1,21 @@
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;
abstract class JWTAlgorithm {
static const HS256 = HS256Algorithm();
static const RS256 = RS256Algorithm();
static JWTAlgorithm fromName(String name) {
switch (name) {
case 'HS256':
return JWTAlgorithm.HS256;
case 'RS256':
return JWTAlgorithm.RS256;
default:
throw JWTInvalidError('unknown algorithm');
}
@ -18,8 +24,8 @@ abstract class JWTAlgorithm {
const JWTAlgorithm();
String get name;
List<int> sign(String key, List<int> body);
bool verify(String key, List<int> body, List<int> signature);
List<int> sign(Key key, List<int> body);
bool verify(Key key, List<int> body, List<int> signature);
}
class HS256Algorithm extends JWTAlgorithm {
@ -29,13 +35,18 @@ class HS256Algorithm extends JWTAlgorithm {
String get name => 'HS256';
@override
List<int> sign(String key, List<int> body) {
final hmac = Hmac(sha256, utf8.encode(key));
List<int> sign(Key key, List<int> body) {
assert(key is SecretKey, 'key must be a SecretKey');
final secretKey = key as SecretKey;
final hmac = Hmac(sha256, utf8.encode(secretKey.key));
return hmac.convert(body).bytes;
}
@override
bool verify(String key, List<int> body, List<int> signature) {
bool verify(Key key, List<int> body, List<int> signature) {
assert(key is SecretKey, 'key must be a SecretKey');
final actual = sign(key, body);
if (actual.length != signature.length) return false;
@ -47,3 +58,80 @@ class HS256Algorithm extends JWTAlgorithm {
return true;
}
}
class RS256Algorithm extends JWTAlgorithm {
const RS256Algorithm();
@override
String get name => 'RS256';
@override
List<int> sign(Key key, List<int> body) {
assert(key is PrivateKey, 'key must be a PrivateKey');
final privateKey = key as PrivateKey;
final parser = RSAPKCSParser();
RSAKeyPair pair;
try {
pair = parser.parsePEM(privateKey.key, password: privateKey.passphrase);
assert(pair.private != null);
} catch (ex) {
throw JWTInvalidError('invalid private RSA key');
}
final signer = Signer('SHA-256/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);
RSASignature signature = signer.generateSignature(Uint8List.fromList(body));
return signature.bytes.toList(growable: false);
}
@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;
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('SHA-256/RSA');
final params = ParametersWithRandom(
PublicKeyParameter<RSAPublicKey>(
RSAPublicKey(
pair.public.modulus,
BigInt.from(pair.public.publicExponent),
),
),
SecureRandom('AES/CTR/PRNG'),
);
signer.init(false, params);
return signer.verifySignature(Uint8List.fromList(body), RSASignature(Uint8List.fromList(signature)));
} catch (ex) {
return false;
}
}
}

View File

@ -5,45 +5,39 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import './utils.dart';
class JWT {
static JWT verify(String token, String key) {
try {
final parts = token.split('.');
static JWT verify(String token, Key key) {
final parts = token.split('.');
final header = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[0])));
final header = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[0])));
if (header['typ'] != 'JWT') throw JWTInvalidError('not a jwt');
if (header['typ'] != 'JWT') throw JWTInvalidError('not a jwt');
final algorithm = JWTAlgorithm.fromName(header['alg']);
final algorithm = JWTAlgorithm.fromName(header['alg']);
if (parts.length < 3) throw JWTInvalidError('jwt malformated');
if (parts.length < 3) throw JWTInvalidError('jwt malformated');
final body = utf8.encode(parts[0] + '.' + parts[1]);
final signature = base64Url.decode(base64Padded(parts[2]));
final body = utf8.encode(parts[0] + '.' + parts[1]);
final signature = base64Url.decode(base64Padded(parts[2]));
if (!algorithm.verify(key, body, signature)) {
throw JWTInvalidError('invalid signature');
}
final payload = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[1])));
if (payload.containsKey('exp')) {
final exp = DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
if (exp.isBefore(DateTime.now())) {
throw JWTExpiredError();
}
}
return JWT(
payload: payload,
audience: payload.remove('aud'),
issuer: payload.remove('iss'),
subject: payload.remove('sub'),
);
} on JWTError {
rethrow;
} catch (ex) {
throw JWTInvalidError('jwt invalid');
if (!algorithm.verify(key, body, signature)) {
throw JWTInvalidError('invalid signature');
}
final payload = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[1])));
if (payload.containsKey('exp')) {
final exp = DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
if (exp.isBefore(DateTime.now())) {
throw JWTExpiredError();
}
}
return JWT(
payload: payload,
audience: payload.remove('aud'),
issuer: payload.remove('iss'),
subject: payload.remove('sub'),
);
}
JWT({
@ -59,7 +53,7 @@ class JWT {
final String issuer;
String sign(
String key, {
Key key, {
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
Duration expiresIn,
bool noTimestamp = false,

21
lib/src/keys.dart Normal file
View File

@ -0,0 +1,21 @@
abstract class Key {}
class SecretKey extends Key {
String key;
SecretKey(this.key);
}
class PrivateKey extends Key {
String key;
String passphrase;
PrivateKey(this.key, [this.passphrase = '']);
}
class PublicKey extends Key {
String key;
String passphrase;
PublicKey(this.key, [this.passphrase = '']);
}

View File

@ -1,11 +1,13 @@
name: dart_jsonwebtoken
description: A dart implementation of the famous javascript library 'jsonwebtoken'.
version: 0.2.1
version: 1.0.0
repository: https://github.com/jonasroussel/jsonwebtoken
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
environment:
sdk: '>=2.7.0 <3.0.0'
sdk: '>=2.3.0 <3.0.0'
dependencies:
crypto: ^2.1.5
rsa_pkcs: ^1.1.3
pointycastle: ^1.0.2