blob: 49f73f8f88b62af3157aa4beb42b1a84be0545ed [file] [log] [blame]
// See file LICENSE for more information.
library impl.signer.rsa_signer;
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/pkcs1.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/src/registry/registry.dart';
// TODO: implement full ASN1 encoding (for now I will do a little ad-hoc implementation of just what is needed here)
class RSASigner implements Signer {
/// Intended for internal use.
static final FactoryConfig factoryConfig =
DynamicFactoryConfig.suffix(Signer, '/RSA', (_, Match match) {
final digestName = match.group(1);
final digestIdentifierHex = _digestIdentifierHexes[digestName!];
if (digestIdentifierHex == null) {
throw RegistryFactoryException(
'RSA signing with digest $digestName is not supported');
}
return () => RSASigner(Digest(digestName), digestIdentifierHex);
});
static final Map<String, String> _digestIdentifierHexes = {
'MD2': '06082a864886f70d0202',
'MD4': '06082a864886f70d0204',
'MD5': '06082a864886f70d0205',
'RIPEMD-128': '06052b24030202',
'RIPEMD-160': '06052b24030201',
'RIPEMD-256': '06052b24030203',
'SHA-1': '06052b0e03021a',
'SHA-224': '0609608648016503040204',
'SHA-256': '0609608648016503040201',
'SHA-384': '0609608648016503040202',
'SHA-512': '0609608648016503040203'
};
final AsymmetricBlockCipher _rsa = PKCS1Encoding(RSAEngine());
final Digest _digest;
late Uint8List
_digestIdentifier; // DER encoded with trailing tag (06)+length byte
late bool _forSigning;
RSASigner(this._digest, String digestIdentifierHex) {
_digestIdentifier = _hexStringToBytes(digestIdentifierHex);
}
@override
String get algorithmName => '${_digest.algorithmName}/RSA';
@override
void reset() {
_digest.reset();
_rsa.reset();
}
@override
void init(bool forSigning, CipherParameters params) {
_forSigning = forSigning;
AsymmetricKeyParameter akparams;
if (params is ParametersWithRandom) {
akparams = params.parameters as AsymmetricKeyParameter<AsymmetricKey>;
} else {
akparams = params as AsymmetricKeyParameter<AsymmetricKey>;
}
var k = akparams.key as RSAAsymmetricKey;
if (forSigning && (k is! RSAPrivateKey)) {
throw ArgumentError('Signing requires private key');
}
if (!forSigning && (k is! RSAPublicKey)) {
throw ArgumentError('Verification requires public key');
}
reset();
_rsa.init(forSigning, params);
}
@override
RSASignature generateSignature(Uint8List message, {bool normalize = false}) {
if (!_forSigning) {
throw StateError('Signer was not initialised for signature generation');
}
var hash = Uint8List(_digest.digestSize);
_digest.reset();
_digest.update(message, 0, message.length);
_digest.doFinal(hash, 0);
var data = _derEncode(hash);
var out = Uint8List(_rsa.outputBlockSize);
var len = _rsa.processBlock(data, 0, data.length, out, 0);
return RSASignature(out.sublist(0, len));
}
@override
bool verifySignature(Uint8List message, covariant RSASignature signature) {
if (_forSigning) {
throw StateError('Signer was not initialised for signature verification');
}
var hash = Uint8List(_digest.digestSize);
_digest.reset();
_digest.update(message, 0, message.length);
_digest.doFinal(hash, 0);
var sig = Uint8List(_rsa.outputBlockSize);
try {
final len =
_rsa.processBlock(signature.bytes, 0, signature.bytes.length, sig, 0);
sig = sig.sublist(0, len);
} on ArgumentError {
// Signature was tampered with so the RSA 'decrypted' block is totally
// different to the original, causing [PKCS1Encoding._decodeBlock] to
// throw an exception because it does not recognise it.
return false;
}
var expected = _derEncode(hash);
if (sig.length == expected.length) {
for (var i = 0; i < sig.length; i++) {
if (sig[i] != expected[i]) {
return false;
}
}
return true; //return Arrays.constantTimeAreEqual(sig, expected);
} else if (sig.length == expected.length - 2) {
// NULL left out
var sigOffset = sig.length - hash.length - 2;
var expectedOffset = expected.length - hash.length - 2;
expected[1] -= 2; // adjust lengths
expected[3] -= 2;
var nonEqual = 0;
for (var i = 0; i < hash.length; i++) {
nonEqual |= sig[sigOffset + i] ^ expected[expectedOffset + i];
}
for (var i = 0; i < sigOffset; i++) {
nonEqual |= sig[i] ^ expected[i]; // check header less NULL
}
return nonEqual == 0;
} else {
return false;
}
}
Uint8List _derEncode(Uint8List hash) {
var out = Uint8List(2 + 2 + _digestIdentifier.length + 2 + 2 + hash.length);
var i = 0;
// header
out[i++] = 48;
out[i++] = out.length - 2;
// algorithmIdentifier.header
out[i++] = 48;
out[i++] = _digestIdentifier.length + 2;
// algorithmIdentifier.bytes
out.setAll(i, _digestIdentifier);
i += _digestIdentifier.length;
// algorithmIdentifier.null
out[i++] = 5;
out[i++] = 0;
// hash.header
out[i++] = 4;
out[i++] = hash.length;
// hash.bytes
out.setAll(i, hash);
return out;
}
Uint8List _hexStringToBytes(String hex) {
var result = Uint8List(hex.length ~/ 2);
for (var i = 0; i < hex.length; i += 2) {
var num = hex.substring(i, i + 2);
var byte = int.parse(num, radix: 16);
result[i ~/ 2] = byte;
}
return result;
}
}