#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) #pragma warning disable using System; using System.Collections; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; using BestHTTP.SecureProtocol.Org.BouncyCastle.Security; using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Signers { /// ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). ///

/// Note: the usual length for the salt is the length of the hash /// function used in bytes.

///
public class Iso9796d2PssSigner : ISignerWithRecovery { /// /// Return a reference to the recoveredMessage message. /// /// The full/partial recoveredMessage message. /// public byte[] GetRecoveredMessage() { return recoveredMessage; } public const int TrailerImplicit = 0xBC; public const int TrailerRipeMD160 = 0x31CC; public const int TrailerRipeMD128 = 0x32CC; public const int TrailerSha1 = 0x33CC; public const int TrailerSha256 = 0x34CC; public const int TrailerSha512 = 0x35CC; public const int TrailerSha384 = 0x36CC; public const int TrailerWhirlpool = 0x37CC; private IDigest digest; private IAsymmetricBlockCipher cipher; private SecureRandom random; private byte[] standardSalt; private int hLen; private int trailer; private int keyBits; private byte[] block; private byte[] mBuf; private int messageLength; private readonly int saltLength; private bool fullMessage; private byte[] recoveredMessage; private byte[] preSig; private byte[] preBlock; private int preMStart; private int preTLength; /// /// Generate a signer with either implicit or explicit trailers for ISO9796-2, scheme 2 or 3. /// /// base cipher to use for signature creation/verification /// digest to use. /// length of salt in bytes. /// whether or not the trailer is implicit or gives the hash. public Iso9796d2PssSigner( IAsymmetricBlockCipher cipher, IDigest digest, int saltLength, bool isImplicit) { this.cipher = cipher; this.digest = digest; this.hLen = digest.GetDigestSize(); this.saltLength = saltLength; if (isImplicit) { trailer = IsoTrailers.TRAILER_IMPLICIT; } else if (IsoTrailers.NoTrailerAvailable(digest)) { throw new ArgumentException("no valid trailer", "digest"); } else { trailer = IsoTrailers.GetTrailer(digest); } } /// Constructor for a signer with an explicit digest trailer. /// /// /// cipher to use. /// /// digest to sign with. /// /// length of salt in bytes. /// public Iso9796d2PssSigner( IAsymmetricBlockCipher cipher, IDigest digest, int saltLength) : this(cipher, digest, saltLength, false) { } public virtual string AlgorithmName { get { return digest.AlgorithmName + "with" + "ISO9796-2S2"; } } /// Initialise the signer. /// true if for signing, false if for verification. /// parameters for signature generation/verification. If the /// parameters are for generation they should be a ParametersWithRandom, /// a ParametersWithSalt, or just an RsaKeyParameters object. If RsaKeyParameters /// are passed in a SecureRandom will be created. /// /// if wrong parameter type or a fixed /// salt is passed in which is the wrong length. /// public virtual void Init( bool forSigning, ICipherParameters parameters) { RsaKeyParameters kParam; if (parameters is ParametersWithRandom) { ParametersWithRandom p = (ParametersWithRandom) parameters; kParam = (RsaKeyParameters) p.Parameters; if (forSigning) { random = p.Random; } } else if (parameters is ParametersWithSalt) { if (!forSigning) throw new ArgumentException("ParametersWithSalt only valid for signing", "parameters"); ParametersWithSalt p = (ParametersWithSalt) parameters; kParam = (RsaKeyParameters) p.Parameters; standardSalt = p.GetSalt(); if (standardSalt.Length != saltLength) throw new ArgumentException("Fixed salt is of wrong length"); } else { kParam = (RsaKeyParameters) parameters; if (forSigning) { random = new SecureRandom(); } } cipher.Init(forSigning, kParam); keyBits = kParam.Modulus.BitLength; block = new byte[(keyBits + 7) / 8]; if (trailer == IsoTrailers.TRAILER_IMPLICIT) { mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 1]; } else { mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 2]; } Reset(); } /// compare two byte arrays - constant time. private bool IsSameAs(byte[] a, byte[] b) { if (messageLength != b.Length) { return false; } bool isOkay = true; for (int i = 0; i != b.Length; i++) { if (a[i] != b[i]) { isOkay = false; } } return isOkay; } /// clear possible sensitive data private void ClearBlock( byte[] block) { Array.Clear(block, 0, block.Length); } public virtual void UpdateWithRecoveredMessage( byte[] signature) { byte[] block = cipher.ProcessBlock(signature, 0, signature.Length); // // adjust block size for leading zeroes if necessary // if (block.Length < (keyBits + 7) / 8) { byte[] tmp = new byte[(keyBits + 7) / 8]; Array.Copy(block, 0, tmp, tmp.Length - block.Length, block.Length); ClearBlock(block); block = tmp; } int tLength; if (((block[block.Length - 1] & 0xFF) ^ 0xBC) == 0) { tLength = 1; } else { int sigTrail = ((block[block.Length - 2] & 0xFF) << 8) | (block[block.Length - 1] & 0xFF); if (IsoTrailers.NoTrailerAvailable(digest)) throw new ArgumentException("unrecognised hash in signature"); if (sigTrail != IsoTrailers.GetTrailer(digest)) throw new InvalidOperationException("signer initialised with wrong digest for trailer " + sigTrail); tLength = 2; } // // calculate H(m2) // byte[] m2Hash = new byte[hLen]; digest.DoFinal(m2Hash, 0); // // remove the mask // byte[] dbMask = MaskGeneratorFunction1(block, block.Length - hLen - tLength, hLen, block.Length - hLen - tLength); for (int i = 0; i != dbMask.Length; i++) { block[i] ^= dbMask[i]; } block[0] &= 0x7f; // // find out how much padding we've got // int mStart = 0; while (mStart < block.Length) { if (block[mStart++] == 0x01) break; } if (mStart >= block.Length) { ClearBlock(block); } fullMessage = (mStart > 1); recoveredMessage = new byte[dbMask.Length - mStart - saltLength]; Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); recoveredMessage.CopyTo(mBuf, 0); preSig = signature; preBlock = block; preMStart = mStart; preTLength = tLength; } /// update the internal digest with the byte b public virtual void Update( byte input) { if (preSig == null && messageLength < mBuf.Length) { mBuf[messageLength++] = input; } else { digest.Update(input); } } /// update the internal digest with the byte array in public virtual void BlockUpdate( byte[] input, int inOff, int length) { if (preSig == null) { while (length > 0 && messageLength < mBuf.Length) { this.Update(input[inOff]); inOff++; length--; } } if (length > 0) { digest.BlockUpdate(input, inOff, length); } } /// reset the internal state public virtual void Reset() { digest.Reset(); messageLength = 0; if (mBuf != null) { ClearBlock(mBuf); } if (recoveredMessage != null) { ClearBlock(recoveredMessage); recoveredMessage = null; } fullMessage = false; if (preSig != null) { preSig = null; ClearBlock(preBlock); preBlock = null; } } /// Generate a signature for the loaded message using the key we were /// initialised with. /// public virtual byte[] GenerateSignature() { int digSize = digest.GetDigestSize(); byte[] m2Hash = new byte[digSize]; digest.DoFinal(m2Hash, 0); byte[] C = new byte[8]; LtoOSP(messageLength * 8, C); digest.BlockUpdate(C, 0, C.Length); digest.BlockUpdate(mBuf, 0, messageLength); digest.BlockUpdate(m2Hash, 0, m2Hash.Length); byte[] salt; if (standardSalt != null) { salt = standardSalt; } else { salt = new byte[saltLength]; random.NextBytes(salt); } digest.BlockUpdate(salt, 0, salt.Length); byte[] hash = new byte[digest.GetDigestSize()]; digest.DoFinal(hash, 0); int tLength = 2; if (trailer == IsoTrailers.TRAILER_IMPLICIT) { tLength = 1; } int off = block.Length - messageLength - salt.Length - hLen - tLength - 1; block[off] = (byte) (0x01); Array.Copy(mBuf, 0, block, off + 1, messageLength); Array.Copy(salt, 0, block, off + 1 + messageLength, salt.Length); byte[] dbMask = MaskGeneratorFunction1(hash, 0, hash.Length, block.Length - hLen - tLength); for (int i = 0; i != dbMask.Length; i++) { block[i] ^= dbMask[i]; } Array.Copy(hash, 0, block, block.Length - hLen - tLength, hLen); if (trailer == IsoTrailers.TRAILER_IMPLICIT) { block[block.Length - 1] = (byte)IsoTrailers.TRAILER_IMPLICIT; } else { block[block.Length - 2] = (byte) ((uint)trailer >> 8); block[block.Length - 1] = (byte) trailer; } block[0] &= (byte) (0x7f); byte[] b = cipher.ProcessBlock(block, 0, block.Length); ClearBlock(mBuf); ClearBlock(block); messageLength = 0; return b; } /// return true if the signature represents a ISO9796-2 signature /// for the passed in message. /// public virtual bool VerifySignature( byte[] signature) { // // calculate H(m2) // byte[] m2Hash = new byte[hLen]; digest.DoFinal(m2Hash, 0); byte[] block; int tLength; int mStart = 0; if (preSig == null) { try { UpdateWithRecoveredMessage(signature); } catch (Exception) { return false; } } else { if (!Arrays.AreEqual(preSig, signature)) { throw new InvalidOperationException("UpdateWithRecoveredMessage called on different signature"); } } block = preBlock; mStart = preMStart; tLength = preTLength; preSig = null; preBlock = null; // // check the hashes // byte[] C = new byte[8]; LtoOSP(recoveredMessage.Length * 8, C); digest.BlockUpdate(C, 0, C.Length); if (recoveredMessage.Length != 0) { digest.BlockUpdate(recoveredMessage, 0, recoveredMessage.Length); } digest.BlockUpdate(m2Hash, 0, m2Hash.Length); // Update for the salt if (standardSalt != null) { digest.BlockUpdate(standardSalt, 0, standardSalt.Length); } else { digest.BlockUpdate(block, mStart + recoveredMessage.Length, saltLength); } byte[] hash = new byte[digest.GetDigestSize()]; digest.DoFinal(hash, 0); int off = block.Length - tLength - hash.Length; bool isOkay = true; for (int i = 0; i != hash.Length; i++) { if (hash[i] != block[off + i]) { isOkay = false; } } ClearBlock(block); ClearBlock(hash); if (!isOkay) { fullMessage = false; messageLength = 0; ClearBlock(recoveredMessage); return false; } // // if they've input a message check what we've recovered against // what was input. // if (messageLength != 0) { if (!IsSameAs(mBuf, recoveredMessage)) { messageLength = 0; ClearBlock(mBuf); return false; } } messageLength = 0; ClearBlock(mBuf); return true; } /// /// Return true if the full message was recoveredMessage. /// /// true on full message recovery, false otherwise, or if not sure. /// public virtual bool HasFullMessage() { return fullMessage; } /// int to octet string. /// int to octet string. private void ItoOSP( int i, byte[] sp) { sp[0] = (byte)((uint)i >> 24); sp[1] = (byte)((uint)i >> 16); sp[2] = (byte)((uint)i >> 8); sp[3] = (byte)((uint)i >> 0); } /// long to octet string. private void LtoOSP(long l, byte[] sp) { sp[0] = (byte)((ulong)l >> 56); sp[1] = (byte)((ulong)l >> 48); sp[2] = (byte)((ulong)l >> 40); sp[3] = (byte)((ulong)l >> 32); sp[4] = (byte)((ulong)l >> 24); sp[5] = (byte)((ulong)l >> 16); sp[6] = (byte)((ulong)l >> 8); sp[7] = (byte)((ulong)l >> 0); } /// mask generator function, as described in Pkcs1v2. private byte[] MaskGeneratorFunction1( byte[] Z, int zOff, int zLen, int length) { byte[] mask = new byte[length]; byte[] hashBuf = new byte[hLen]; byte[] C = new byte[4]; int counter = 0; digest.Reset(); do { ItoOSP(counter, C); digest.BlockUpdate(Z, zOff, zLen); digest.BlockUpdate(C, 0, C.Length); digest.DoFinal(hashBuf, 0); Array.Copy(hashBuf, 0, mask, counter * hLen, hLen); } while (++counter < (length / hLen)); if ((counter * hLen) < length) { ItoOSP(counter, C); digest.BlockUpdate(Z, zOff, zLen); digest.BlockUpdate(C, 0, C.Length); digest.DoFinal(hashBuf, 0); Array.Copy(hashBuf, 0, mask, counter * hLen, mask.Length - (counter * hLen)); } return mask; } } } #pragma warning restore #endif