package org.bouncycastle.crypto.tls;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.digests.LongDigest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.Arrays;
A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.
/**
* A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.
*/
public class TlsMac
{
protected TlsContext context;
protected byte[] secret;
protected Mac mac;
protected int digestBlockSize;
protected int digestOverhead;
protected int macLength;
Generate a new instance of an TlsMac.
Params: - context – the TLS client context
- digest – The digest to use.
- key – A byte-array where the key for this MAC is located.
- keyOff – The number of bytes to skip, before the key starts in the buffer.
- keyLen – The length of the key.
/**
* Generate a new instance of an TlsMac.
*
* @param context the TLS client context
* @param digest The digest to use.
* @param key A byte-array where the key for this MAC is located.
* @param keyOff The number of bytes to skip, before the key starts in the buffer.
* @param keyLen The length of the key.
*/
public TlsMac(TlsContext context, Digest digest, byte[] key, int keyOff, int keyLen)
{
this.context = context;
KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen);
this.secret = Arrays.clone(keyParameter.getKey());
// TODO This should check the actual algorithm, not rely on the engine type
if (digest instanceof LongDigest)
{
this.digestBlockSize = 128;
this.digestOverhead = 16;
}
else
{
this.digestBlockSize = 64;
this.digestOverhead = 8;
}
if (TlsUtils.isSSL(context))
{
this.mac = new SSL3Mac(digest);
// TODO This should check the actual algorithm, not assume based on the digest size
if (digest.getDigestSize() == 20)
{
/*
* NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not
* digest block-aligned.
*/
this.digestOverhead = 4;
}
}
else
{
this.mac = new HMac(digest);
// NOTE: The input pad for HMAC is always a full digest block
}
this.mac.init(keyParameter);
this.macLength = mac.getMacSize();
if (context.getSecurityParameters().truncatedHMac)
{
this.macLength = Math.min(this.macLength, 10);
}
}
Returns: the MAC write secret
/**
* @return the MAC write secret
*/
public byte[] getMACSecret()
{
return this.secret;
}
Returns: The output length of this MAC.
/**
* @return The output length of this MAC.
*/
public int getSize()
{
return macLength;
}
Calculate the MAC for some given data.
Params: - type – The message type of the message.
- message – A byte-buffer containing the message.
- offset – The number of bytes to skip, before the message starts.
- length – The length of the message.
Returns: A new byte-buffer containing the MAC value.
/**
* Calculate the MAC for some given data.
*
* @param type The message type of the message.
* @param message A byte-buffer containing the message.
* @param offset The number of bytes to skip, before the message starts.
* @param length The length of the message.
* @return A new byte-buffer containing the MAC value.
*/
public byte[] calculateMac(long seqNo, short type, byte[] message, int offset, int length)
{
ProtocolVersion serverVersion = context.getServerVersion();
boolean isSSL = serverVersion.isSSL();
byte[] macHeader = new byte[isSSL ? 11 : 13];
TlsUtils.writeUint64(seqNo, macHeader, 0);
TlsUtils.writeUint8(type, macHeader, 8);
if (!isSSL)
{
TlsUtils.writeVersion(serverVersion, macHeader, 9);
}
TlsUtils.writeUint16(length, macHeader, macHeader.length - 2);
mac.update(macHeader, 0, macHeader.length);
mac.update(message, offset, length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return truncate(result);
}
public byte[] calculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length,
int fullLength, byte[] dummyData)
{
/*
* Actual MAC only calculated on 'length' bytes...
*/
byte[] result = calculateMac(seqNo, type, message, offset, length);
/*
* ...but ensure a constant number of complete digest blocks are processed (as many as would
* be needed for 'fullLength' bytes of input).
*/
int headerLength = TlsUtils.isSSL(context) ? 11 : 13;
// How many extra full blocks do we need to calculate?
int extra = getDigestBlockCount(headerLength + fullLength) - getDigestBlockCount(headerLength + length);
while (--extra >= 0)
{
mac.update(dummyData, 0, digestBlockSize);
}
// One more byte in case the implementation is "lazy" about processing blocks
mac.update(dummyData[0]);
mac.reset();
return result;
}
protected int getDigestBlockCount(int inputLength)
{
// NOTE: This calculation assumes a minimum of 1 pad byte
return (inputLength + digestOverhead) / digestBlockSize;
}
protected byte[] truncate(byte[] bs)
{
if (bs.length <= macLength)
{
return bs;
}
return Arrays.copyOf(bs, macLength);
}
}