blob: 32ed4233a57eb26a382e1e61c377469cf0c3b32b [file] [log] [blame]
package org.bouncycastle.crypto.signers;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.SignerWithRecovery;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.util.Arrays;
/**
* ISO9796-2 - mechanism using a hash function with recovery (scheme 1)
*/
public class ISO9796d2Signer
implements SignerWithRecovery
{
/** @deprecated use ISOTrailers */
static final public int TRAILER_IMPLICIT = 0xBC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_RIPEMD160 = 0x31CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_RIPEMD128 = 0x32CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_SHA1 = 0x33CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_SHA256 = 0x34CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_SHA512 = 0x35CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_SHA384 = 0x36CC;
/** @deprecated use ISOTrailers */
static final public int TRAILER_WHIRLPOOL = 0x37CC;
private Digest digest;
private AsymmetricBlockCipher cipher;
private int trailer;
private int keyBits;
private byte[] block;
private byte[] mBuf;
private int messageLength;
private boolean fullMessage;
private byte[] recoveredMessage;
private byte[] preSig;
private byte[] preBlock;
/**
* Generate a signer with either implicit or explicit trailers for ISO9796-2.
*
* @param cipher base cipher to use for signature creation/verification
* @param digest digest to use.
* @param implicit whether or not the trailer is implicit or gives the hash.
*/
public ISO9796d2Signer(
AsymmetricBlockCipher cipher,
Digest digest,
boolean implicit)
{
this.cipher = cipher;
this.digest = digest;
if (implicit)
{
trailer = ISOTrailers.TRAILER_IMPLICIT;
}
else
{
Integer trailerObj = ISOTrailers.getTrailer(digest);
if (trailerObj != null)
{
trailer = trailerObj.intValue();
}
else
{
throw new IllegalArgumentException("no valid trailer for digest: " + digest.getAlgorithmName());
}
}
}
/**
* Constructor for a signer with an explicit digest trailer.
*
* @param cipher cipher to use.
* @param digest digest to sign with.
*/
public ISO9796d2Signer(
AsymmetricBlockCipher cipher,
Digest digest)
{
this(cipher, digest, false);
}
public void init(
boolean forSigning,
CipherParameters param)
{
RSAKeyParameters kParam = (RSAKeyParameters)param;
cipher.init(forSigning, kParam);
keyBits = kParam.getModulus().bitLength();
block = new byte[(keyBits + 7) / 8];
if (trailer == ISOTrailers.TRAILER_IMPLICIT)
{
mBuf = new byte[block.length - digest.getDigestSize() - 2];
}
else
{
mBuf = new byte[block.length - digest.getDigestSize() - 3];
}
reset();
}
/**
* compare two byte arrays - constant time
*/
private boolean isSameAs(
byte[] a,
byte[] b)
{
boolean isOkay = true;
if (messageLength > mBuf.length)
{
if (mBuf.length > b.length)
{
isOkay = false;
}
for (int i = 0; i != mBuf.length; i++)
{
if (a[i] != b[i])
{
isOkay = false;
}
}
}
else
{
if (messageLength != b.length)
{
isOkay = false;
}
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)
{
for (int i = 0; i != block.length; i++)
{
block[i] = 0;
}
}
public void updateWithRecoveredMessage(byte[] signature)
throws InvalidCipherTextException
{
byte[] block = cipher.processBlock(signature, 0, signature.length);
if (((block[0] & 0xC0) ^ 0x40) != 0)
{
throw new InvalidCipherTextException("malformed signature");
}
if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
{
throw new InvalidCipherTextException("malformed signature");
}
int delta = 0;
if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
{
delta = 1;
}
else
{
int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
Integer trailerObj = ISOTrailers.getTrailer(digest);
if (trailerObj != null)
{
if (sigTrail != trailerObj.intValue())
{
throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
}
}
else
{
throw new IllegalArgumentException("unrecognised hash in signature");
}
delta = 2;
}
//
// find out how much padding we've got
//
int mStart = 0;
for (mStart = 0; mStart != block.length; mStart++)
{
if (((block[mStart] & 0x0f) ^ 0x0a) == 0)
{
break;
}
}
mStart++;
int off = block.length - delta - digest.getDigestSize();
//
// there must be at least one byte of message string
//
if ((off - mStart) <= 0)
{
throw new InvalidCipherTextException("malformed block");
}
//
// if we contain the whole message as well, check the hash of that.
//
if ((block[0] & 0x20) == 0)
{
fullMessage = true;
recoveredMessage = new byte[off - mStart];
System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
}
else
{
fullMessage = false;
recoveredMessage = new byte[off - mStart];
System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
}
preSig = signature;
preBlock = block;
digest.update(recoveredMessage, 0, recoveredMessage.length);
messageLength = recoveredMessage.length;
System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length);
}
/**
* update the internal digest with the byte b
*/
public void update(
byte b)
{
digest.update(b);
if (messageLength < mBuf.length)
{
mBuf[messageLength] = b;
}
messageLength++;
}
/**
* update the internal digest with the byte array in
*/
public void update(
byte[] in,
int off,
int len)
{
while (len > 0 && messageLength < mBuf.length)
{
this.update(in[off]);
off++;
len--;
}
digest.update(in, off, len);
messageLength += len;
}
/**
* reset the internal state
*/
public void reset()
{
digest.reset();
messageLength = 0;
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 byte[] generateSignature()
throws CryptoException
{
int digSize = digest.getDigestSize();
int t = 0;
int delta = 0;
if (trailer == ISOTrailers.TRAILER_IMPLICIT)
{
t = 8;
delta = block.length - digSize - 1;
digest.doFinal(block, delta);
block[block.length - 1] = (byte)ISOTrailers.TRAILER_IMPLICIT;
}
else
{
t = 16;
delta = block.length - digSize - 2;
digest.doFinal(block, delta);
block[block.length - 2] = (byte)(trailer >>> 8);
block[block.length - 1] = (byte)trailer;
}
byte header = 0;
int x = (digSize + messageLength) * 8 + t + 4 - keyBits;
if (x > 0)
{
int mR = messageLength - ((x + 7) / 8);
header = 0x60;
delta -= mR;
System.arraycopy(mBuf, 0, block, delta, mR);
recoveredMessage = new byte[mR];
}
else
{
header = 0x40;
delta -= messageLength;
System.arraycopy(mBuf, 0, block, delta, messageLength);
recoveredMessage = new byte[messageLength];
}
if ((delta - 1) > 0)
{
for (int i = delta - 1; i != 0; i--)
{
block[i] = (byte)0xbb;
}
block[delta - 1] ^= (byte)0x01;
block[0] = (byte)0x0b;
block[0] |= header;
}
else
{
block[0] = (byte)0x0a;
block[0] |= header;
}
byte[] b = cipher.processBlock(block, 0, block.length);
fullMessage = (header & 0x20) == 0;
System.arraycopy(mBuf, 0, recoveredMessage, 0, recoveredMessage.length);
clearBlock(mBuf);
clearBlock(block);
return b;
}
/**
* return true if the signature represents a ISO9796-2 signature
* for the passed in message.
*/
public boolean verifySignature(
byte[] signature)
{
byte[] block = null;
if (preSig == null)
{
try
{
block = cipher.processBlock(signature, 0, signature.length);
}
catch (Exception e)
{
return false;
}
}
else
{
if (!Arrays.areEqual(preSig, signature))
{
throw new IllegalStateException("updateWithRecoveredMessage called on different signature");
}
block = preBlock;
preSig = null;
preBlock = null;
}
if (((block[0] & 0xC0) ^ 0x40) != 0)
{
return returnFalse(block);
}
if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
{
return returnFalse(block);
}
int delta = 0;
if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
{
delta = 1;
}
else
{
int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
Integer trailerObj = ISOTrailers.getTrailer(digest);
if (trailerObj != null)
{
if (sigTrail != trailerObj.intValue())
{
throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
}
}
else
{
throw new IllegalArgumentException("unrecognised hash in signature");
}
delta = 2;
}
//
// find out how much padding we've got
//
int mStart = 0;
for (mStart = 0; mStart != block.length; mStart++)
{
if (((block[mStart] & 0x0f) ^ 0x0a) == 0)
{
break;
}
}
mStart++;
//
// check the hashes
//
byte[] hash = new byte[digest.getDigestSize()];
int off = block.length - delta - hash.length;
//
// there must be at least one byte of message string
//
if ((off - mStart) <= 0)
{
return returnFalse(block);
}
//
// if we contain the whole message as well, check the hash of that.
//
if ((block[0] & 0x20) == 0)
{
fullMessage = true;
// check right number of bytes passed in.
if (messageLength > off - mStart)
{
return returnFalse(block);
}
digest.reset();
digest.update(block, mStart, off - mStart);
digest.doFinal(hash, 0);
boolean isOkay = true;
for (int i = 0; i != hash.length; i++)
{
block[off + i] ^= hash[i];
if (block[off + i] != 0)
{
isOkay = false;
}
}
if (!isOkay)
{
return returnFalse(block);
}
recoveredMessage = new byte[off - mStart];
System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
}
else
{
fullMessage = false;
digest.doFinal(hash, 0);
boolean isOkay = true;
for (int i = 0; i != hash.length; i++)
{
block[off + i] ^= hash[i];
if (block[off + i] != 0)
{
isOkay = false;
}
}
if (!isOkay)
{
return returnFalse(block);
}
recoveredMessage = new byte[off - mStart];
System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
}
//
// if they've input a message check what we've recovered against
// what was input.
//
if (messageLength != 0)
{
if (!isSameAs(mBuf, recoveredMessage))
{
return returnFalse(block);
}
}
clearBlock(mBuf);
clearBlock(block);
return true;
}
private boolean returnFalse(byte[] block)
{
clearBlock(mBuf);
clearBlock(block);
return false;
}
/**
* Return true if the full message was recoveredMessage.
*
* @return true on full message recovery, false otherwise.
* @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage()
*/
public boolean hasFullMessage()
{
return fullMessage;
}
/**
* Return a reference to the recoveredMessage message, either as it was added
* to a just generated signature, or extracted from a verified one.
*
* @return the full/partial recoveredMessage message.
* @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage()
*/
public byte[] getRecoveredMessage()
{
return recoveredMessage;
}
}