summary refs log tree commit diff
path: root/Crypto/src/crypto/signers/Iso9796d2PssSigner.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Crypto/src/crypto/signers/Iso9796d2PssSigner.cs')
-rw-r--r--Crypto/src/crypto/signers/Iso9796d2PssSigner.cs576
1 files changed, 576 insertions, 0 deletions
diff --git a/Crypto/src/crypto/signers/Iso9796d2PssSigner.cs b/Crypto/src/crypto/signers/Iso9796d2PssSigner.cs
new file mode 100644

index 000000000..48cd719e9 --- /dev/null +++ b/Crypto/src/crypto/signers/Iso9796d2PssSigner.cs
@@ -0,0 +1,576 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /// <summary> ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). + /// <p> + /// Note: the usual length for the salt is the length of the hash + /// function used in bytes.</p> + /// </summary> + public class Iso9796d2PssSigner + : ISignerWithRecovery + { + /// <summary> + /// Return a reference to the recoveredMessage message. + /// </summary> + /// <returns>The full/partial recoveredMessage message.</returns> + /// <seealso cref="ISignerWithRecovery.GetRecoveredMessage"/> + 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; + + 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; + + /// <summary> + /// Generate a signer for the with either implicit or explicit trailers + /// for ISO9796-2, scheme 2 or 3. + /// </summary> + /// <param name="cipher">base cipher to use for signature creation/verification</param> + /// <param name="digest">digest to use.</param> + /// <param name="saltLength">length of salt in bytes.</param> + /// <param name="isImplicit">whether or not the trailer is implicit or gives the hash.</param> + 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 = TrailerImplicit; + } + else + { + if (digest is Sha1Digest) + { + trailer = TrailerSha1; + } + else if (digest is RipeMD160Digest) + { + trailer = TrailerRipeMD160; + } + else if (digest is RipeMD128Digest) + { + trailer = TrailerRipeMD128; + } + else + { + throw new ArgumentException("no valid trailer for digest"); + } + } + } + + /// <summary> Constructor for a signer with an explicit digest trailer. + /// + /// </summary> + /// <param name="cipher">cipher to use. + /// </param> + /// <param name="digest">digest to sign with. + /// </param> + /// <param name="saltLength">length of salt in bytes. + /// </param> + public Iso9796d2PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + int saltLength) + : this(cipher, digest, saltLength, false) + { + } + + public string AlgorithmName + { + get { return digest.AlgorithmName + "with" + "ISO9796-2S2"; } + } + + /// <summary>Initialise the signer.</summary> + /// <param name="forSigning">true if for signing, false if for verification.</param> + /// <param name="parameters">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. + /// </param> + /// <exception cref="ArgumentException">if wrong parameter type or a fixed + /// salt is passed in which is the wrong length. + /// </exception> + 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 == TrailerImplicit) + { + mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 1]; + } + else + { + mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 2]; + } + + Reset(); + } + + /// <summary> compare two byte arrays - constant time.</summary> + 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; + } + + /// <summary> clear possible sensitive data</summary> + private void ClearBlock( + byte[] block) + { + Array.Clear(block, 0, block.Length); + } + + public virtual void UpdateWithRecoveredMessage( + byte[] signature) + { + // TODO + throw Platform.CreateNotImplementedException("UpdateWithRecoveredMessage"); + } + + /// <summary> update the internal digest with the byte b</summary> + public virtual void Update( + byte input) + { + if (messageLength < mBuf.Length) + { + mBuf[messageLength++] = input; + } + else + { + digest.Update(input); + } + } + + /// <summary> update the internal digest with the byte array in</summary> + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + while (length > 0 && messageLength < mBuf.Length) + { + this.Update(input[inOff]); + inOff++; + length--; + } + + if (length > 0) + { + digest.BlockUpdate(input, inOff, length); + } + } + + /// <summary> reset the internal state</summary> + public virtual void Reset() + { + digest.Reset(); + messageLength = 0; + if (mBuf != null) + { + ClearBlock(mBuf); + } + if (recoveredMessage != null) + { + ClearBlock(recoveredMessage); + recoveredMessage = null; + } + fullMessage = false; + } + + /// <summary> Generate a signature for the loaded message using the key we were + /// initialised with. + /// </summary> + public 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 == TrailerImplicit) + { + 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 == TrailerImplicit) + { + block[block.Length - 1] = (byte)TrailerImplicit; + } + 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; + } + + /// <summary> return true if the signature represents a ISO9796-2 signature + /// for the passed in message. + /// </summary> + public virtual bool VerifySignature( + byte[] signature) + { + byte[] block = cipher.ProcessBlock(signature, 0, signature.Length); + + // + // adjust block size for leading zeroes if necessary + // + int expectedSize = (keyBits + 7) / 8; + if (block.Length < expectedSize) + { + byte[] tmp = new byte[expectedSize]; + block.CopyTo(tmp, tmp.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); + + switch (sigTrail) + { + case TrailerRipeMD160: + if (!(digest is RipeMD160Digest)) + { + throw new ArgumentException("signer should be initialised with RipeMD160"); + } + break; + case TrailerSha1: + if (!(digest is Sha1Digest)) + { + throw new ArgumentException("signer should be initialised with SHA1"); + } + break; + case TrailerRipeMD128: + if (!(digest is RipeMD128Digest)) + { + throw new ArgumentException("signer should be initialised with RipeMD128"); + } + break; + default: + throw new ArgumentException("unrecognised hash in signature"); + } + + 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); + return false; + } + + fullMessage = (mStart > 1); + + // TODO Should we check if a standardSalt was set and, if so, use its length instead? + recoveredMessage = new byte[dbMask.Length - mStart - saltLength]; + + Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); + + // + // 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 + digest.BlockUpdate(block, mStart + recoveredMessage.Length, saltLength); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + int off = block.Length - tLength - hash.Length; + + // TODO ConstantTimeAreEqual with offset for one array + + 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; + 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)) + { + ClearBlock(mBuf); + return false; + } + + messageLength = 0; + } + + ClearBlock(mBuf); + return true; + } + + /// <summary> + /// Return true if the full message was recoveredMessage. + /// </summary> + /// <returns>true on full message recovery, false otherwise, or if not sure.</returns> + /// <seealso cref="ISignerWithRecovery.HasFullMessage"/> + public virtual bool HasFullMessage() + { + return fullMessage; + } + + /// <summary> int to octet string.</summary> + /// <summary> int to octet string.</summary> + 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); + } + + /// <summary> long to octet string.</summary> + 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); + } + + /// <summary> mask generator function, as described in Pkcs1v2.</summary> + 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; + } + } +}