// See file LICENSE for more information. library impl.signer.pss_signer; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/asymmetric/api.dart'; import 'package:pointycastle/asymmetric/rsa.dart'; import 'package:pointycastle/src/registry/registry.dart'; import '../src/utils.dart'; class PSSSigner implements Signer { /// Intended for internal use. static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix(Signer, '/PSS', (_, Match match) { final digestName = match.group(1); return () => PSSSigner( RSAEngine(), Digest(digestName!), Digest(digestName), ); }); static const int TRAILER_IMPLICIT = 0xBC; final Digest _contentDigest; final Digest _mgfDigest; final AsymmetricBlockCipher _cipher; final int _hLen; final int _mgfhLen; final int _trailer; late bool _sSet; late int _sLen; late Uint8List _salt; late SecureRandom _random; late int _emBits; late Uint8List _block; late Uint8List _mDash; late bool _forSigning; PSSSigner(this._cipher, this._contentDigest, this._mgfDigest, {int trailer = TRAILER_IMPLICIT}) : _hLen = _contentDigest.digestSize, _mgfhLen = _mgfDigest.digestSize, _trailer = trailer; @override String get algorithmName => '${_mgfDigest.algorithmName}/PSS'; @override void init(bool forSigning, CipherParameters params) { _forSigning = forSigning; AsymmetricKeyParameter akparams; if (params is ParametersWithSaltConfiguration) { akparams = params.parameters as AsymmetricKeyParameter; _random = params.random; _sSet = false; _sLen = params.saltLength; _salt = Uint8List(_sLen); } else if (params is ParametersWithSalt) { akparams = params.parameters as AsymmetricKeyParameter; _sSet = true; _salt = params.salt; _sLen = _salt.length; } else { throw ArgumentError( 'Unsupported parameters type ${params.runtimeType}: should be ParametersWithSaltConfiguration or ParametersWithSalt'); } 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'); } _emBits = k.modulus!.bitLength - 1; if (_emBits < (8 * _hLen + 8 * _sLen + 9)) { throw ArgumentError('Key too small for specified hash and salt lengths'); } _mDash = Uint8List(8 + _sLen + _contentDigest.digestSize); _cipher.init(forSigning, akparams); _block = Uint8List((_emBits + 7) ~/ 8); reset(); } /// Clear possibly sensitive data. void _clearBlock(Uint8List block) { for (var i = 0; i != block.length; i++) { block[i] = 0; } } @override void reset() { _contentDigest.reset(); } @override PSSSignature generateSignature(Uint8List message) { if (!_forSigning) { throw StateError('Signer was not initialised for signature generation'); } _contentDigest.reset(); _contentDigest.update(message, 0, message.length); _contentDigest.doFinal(_mDash, _mDash.length - _hLen - _sLen); if (_sLen != 0) { if (!_sSet) { _salt = _random.nextBytes(_sLen); } arrayCopy(_salt, 0, _mDash, _mDash.length - _sLen, _sLen); } var h = Uint8List(_hLen); _contentDigest.update(_mDash, 0, _mDash.length); _contentDigest.doFinal(h, 0); _block[_block.length - _sLen - 1 - _hLen - 1] = 0x01; arrayCopy(_salt, 0, _block, _block.length - _sLen - _hLen - 1, _sLen); var dbMask = _maskGeneratorFunction1(h, 0, h.length, _block.length - _hLen - 1); for (var i = 0; i != dbMask.length; i++) { _block[i] ^= dbMask[i]; } arrayCopy(h, 0, _block, _block.length - _hLen - 1, _hLen); var firstByteMask = 0xff >> ((_block.length * 8) - _emBits); _block[0] &= firstByteMask; _block[_block.length - 1] = _trailer; var b = _cipher.process(_block); _clearBlock(_block); return PSSSignature(b); } @override bool verifySignature(Uint8List message, covariant PSSSignature signature) { if (_forSigning) { throw StateError('Signer was not initialised for signature verification'); } _contentDigest.reset(); _contentDigest.update(message, 0, message.length); _contentDigest.doFinal(_mDash, _mDash.length - _hLen - _sLen); var b = _cipher.process(signature.bytes); _block.fillRange(0, _block.length - b.length, 0); arrayCopy(b, 0, _block, _block.length - b.length, b.length); var firstByteMask = 0xFF >> ((_block.length * 8) - _emBits); if (_block[0] != (_block[0] & firstByteMask) || _block[_block.length - 1] != _trailer) { _clearBlock(_block); return false; } var dbMask = _maskGeneratorFunction1( _block, _block.length - _hLen - 1, _hLen, _block.length - _hLen - 1); for (var i = 0; i != dbMask.length; i++) { _block[i] ^= dbMask[i]; } _block[0] &= firstByteMask; for (var i = 0; i != _block.length - _hLen - _sLen - 2; i++) { if (_block[i] != 0) { _clearBlock(_block); return false; } } if (_block[_block.length - _hLen - _sLen - 2] != 0x01) { _clearBlock(_block); return false; } if (_sSet) { arrayCopy(_salt, 0, _mDash, _mDash.length - _sLen, _sLen); } else { arrayCopy(_block, _block.length - _sLen - _hLen - 1, _mDash, _mDash.length - _sLen, _sLen); } _contentDigest.update(_mDash, 0, _mDash.length); _contentDigest.doFinal(_mDash, _mDash.length - _hLen); for (var i = _block.length - _hLen - 1, j = _mDash.length - _hLen; j != _mDash.length; i++, j++) { if ((_block[i] ^ _mDash[j]) != 0) { _clearBlock(_mDash); _clearBlock(_block); return false; } } _clearBlock(_mDash); _clearBlock(_block); return true; } /// Convert int to octet string. void _intToOSP(int i, Uint8List sp) { sp[0] = i >> 24; sp[1] = i >> 16; sp[2] = i >> 8; sp[3] = i >> 0; } Uint8List _maskGeneratorFunction1( Uint8List Z, int zOff, int zLen, int length) { var mask = Uint8List(length); var hashBuf = Uint8List(_mgfhLen); var C = Uint8List(4); var counter = 0; _mgfDigest.reset(); while (counter < (length ~/ _mgfhLen)) { _intToOSP(counter, C); _mgfDigest.update(Z, zOff, zLen); _mgfDigest.update(C, 0, C.length); _mgfDigest.doFinal(hashBuf, 0); arrayCopy(hashBuf, 0, mask, counter * _mgfhLen, _mgfhLen); counter++; } if ((counter * _mgfhLen) < length) { _intToOSP(counter, C); _mgfDigest.update(Z, zOff, zLen); _mgfDigest.update(C, 0, C.length); _mgfDigest.doFinal(hashBuf, 0); arrayCopy(hashBuf, 0, mask, counter * _mgfhLen, mask.length - (counter * _mgfhLen)); } return mask; } }