Files
dart_jsonwebtoken/lib/src/algorithms.dart
2025-01-20 13:03:37 +01:00

406 lines
10 KiB
Dart

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 'exceptions.dart';
import 'keys.dart';
import 'helpers.dart';
abstract class JWTAlgorithm {
/// 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-PSS using SHA-256 hash algorithm
static const PS256 = RSAAlgorithm('PS256', null);
/// RSASSA-PSS using SHA-384 hash algorithm
static const PS384 = RSAAlgorithm('PS384', null);
/// RSASSA-PSS using SHA-512 hash algorithm
static const PS512 = RSAAlgorithm('PS512', null);
/// RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
static const RS256 = RSAAlgorithm('RS256', null);
/// RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
static const RS384 = RSAAlgorithm('RS384', null);
/// RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
static const RS512 = RSAAlgorithm('RS512', null);
/// 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');
/// ECDSA using secp256k1 curve and SHA-256 hash algorithm
static const ES256K = ECDSAAlgorithm('ES256K');
/// EdDSA using Ed25519 curve algorithm
static const EdDSA = EdDSAAlgorithm('EdDSA');
/// Return the `JWTAlgorithm` from his string name
static JWTAlgorithm fromName(String name) {
switch (name) {
case 'HS256':
return JWTAlgorithm.HS256;
case 'HS384':
return JWTAlgorithm.HS384;
case 'HS512':
return JWTAlgorithm.HS512;
case 'RS256':
return JWTAlgorithm.RS256;
case 'RS384':
return JWTAlgorithm.RS384;
case 'RS512':
return JWTAlgorithm.RS512;
case 'ES256':
return JWTAlgorithm.ES256;
case 'ES384':
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');
}
}
const JWTAlgorithm();
/// `JWTAlgorithm` name
String get name;
/// Create a signature of the `body` with `key`
///
/// return the signature as bytes
Uint8List sign(JWTKey key, Uint8List body);
/// Verify the `signature` of `body` with `key`
///
/// return `true` if the signature is correct `false` otherwise
bool verify(JWTKey key, Uint8List body, Uint8List signature);
}
class EdDSAAlgorithm extends JWTAlgorithm {
final String _name;
const EdDSAAlgorithm(this._name);
@override
String get name => _name;
@override
Uint8List sign(JWTKey key, Uint8List body) {
assert(key is EdDSAPrivateKey, 'key must be a EdDSAPrivateKey');
final privateKey = key as EdDSAPrivateKey;
return ed.sign(privateKey.key, body);
}
@override
bool verify(JWTKey key, Uint8List body, Uint8List signature) {
assert(key is EdDSAPublicKey, 'key must be a EdDSAPublicKey');
final publicKey = key as EdDSAPublicKey;
try {
return ed.verify(publicKey.key, body, signature);
} catch (ex) {
return false;
}
}
}
class HMACAlgorithm extends JWTAlgorithm {
final String _name;
const HMACAlgorithm(this._name);
@override
String get name => _name;
@override
Uint8List sign(JWTKey key, Uint8List body) {
assert(key is SecretKey, 'key must be a SecretKey');
final secretKey = key as SecretKey;
final hmac = Hmac(
_getHash(name),
secretKey.isBase64Encoded
? base64Decode(secretKey.key)
: utf8.encode(secretKey.key),
);
return Uint8List.fromList(hmac.convert(body).bytes);
}
@override
bool verify(JWTKey key, Uint8List body, Uint8List signature) {
assert(key is SecretKey, 'key must be a SecretKey');
final actual = sign(key, body);
if (actual.length != signature.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] != signature[i]) return false;
}
return true;
}
Hash _getHash(String name) {
switch (name) {
case 'HS256':
return sha256;
case 'HS384':
return sha384;
case 'HS512':
return sha512;
default:
throw ArgumentError.value(name, 'name', 'unknown hash name');
}
}
}
class RSAAlgorithm extends JWTAlgorithm {
final String _name;
final Random? _random;
const RSAAlgorithm(this._name, this._random);
@override
String get name => _name;
@override
Uint8List sign(JWTKey key, Uint8List body) {
assert(key is RSAPrivateKey, 'key must be a RSAPrivateKey');
final privateKey = key as RSAPrivateKey;
final algorithm = _getAlgorithm(name);
final signer = pc.Signer('${_getHash(name)}/${algorithm}');
pc.CipherParameters params = pc.PrivateKeyParameter<pc.RSAPrivateKey>(
privateKey.key,
);
if (algorithm == 'PSS') {
int byteValue = 32;
final random = _random ?? Random.secure();
switch (name) {
case 'PS256':
byteValue = 32;
break;
case 'PS384':
byteValue = 48;
break;
case 'PS512':
byteValue = 64;
break;
default:
byteValue = 32;
}
final salt = Uint8List.fromList(
List.generate(byteValue, (_) => random.nextInt(256)),
);
params = pc.ParametersWithSalt(
params,
salt,
);
}
signer.init(true, params);
final signature = signer.generateSignature(Uint8List.fromList(body));
if (signature is pc.PSSSignature) {
return signature.bytes;
} else {
return (signature as pc.RSASignature).bytes;
}
}
@override
bool verify(JWTKey key, Uint8List body, Uint8List signature) {
assert(key is RSAPublicKey, 'key must be a RSAPublicKey');
final publicKey = key as RSAPublicKey;
try {
final algorithm = _getAlgorithm(name);
final signer = pc.Signer('${_getHash(name)}/${algorithm}');
pc.CipherParameters params = pc.PublicKeyParameter<pc.RSAPublicKey>(
publicKey.key,
);
if (algorithm == 'PSS') {
int byteValue = 32;
final secureRandom = pc.SecureRandom('Fortuna');
final random = Random.secure();
switch (name) {
case 'PS256':
byteValue = 32;
break;
case 'PS384':
byteValue = 48;
break;
case 'PS512':
byteValue = 64;
break;
default:
byteValue = 32;
}
final seed = List.generate(byteValue, (_) => random.nextInt(256));
secureRandom.seed(pc.KeyParameter(Uint8List.fromList(seed)));
params = pc.ParametersWithSaltConfiguration(
params,
secureRandom,
byteValue,
);
}
signer.init(false, params);
final msg = Uint8List.fromList(body);
final sign = algorithm == 'PSS'
? pc.PSSSignature(Uint8List.fromList(signature))
: pc.RSASignature(Uint8List.fromList(signature));
return signer.verifySignature(msg, sign);
} catch (ex) {
return false;
}
}
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 {
final String _name;
const ECDSAAlgorithm(this._name);
@override
String get name => _name;
@override
Uint8List sign(JWTKey key, Uint8List body) {
assert(key is ECPrivateKey, 'key must be a ECPrivateKey');
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);
final signature = signer.generateSignature(
Uint8List.fromList(body),
) as pc.ECSignature;
final rBytes = bigIntToBytes(signature.r).toList();
while (rBytes.length < 32) {
rBytes.add(0);
}
final sBytes = bigIntToBytes(signature.s).toList();
while (sBytes.length < 32) {
sBytes.add(0);
}
final len = privateKey.size;
final bytes = Uint8List(len * 2);
bytes.setRange(len - rBytes.length, len, rBytes.reversed);
bytes.setRange((len * 2) - sBytes.length, len * 2, sBytes.reversed);
return bytes;
}
@override
bool verify(JWTKey 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':
case 'ES256K':
return 'SHA-256';
case 'ES384':
return 'SHA-384';
case 'ES512':
return 'SHA-512';
default:
throw ArgumentError.value(name, 'name', 'unknown hash name');
}
}
}