diff options
24 files changed, 1944 insertions, 711 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index 7e0a21bda..fc577c303 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -4274,6 +4274,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\AbstractTlsCipherFactory.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\AbstractTlsContext.cs" SubType = "Code" BuildAction = "Compile" @@ -4359,6 +4364,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\Chacha20Poly1305.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\ChangeCipherSpec.cs" SubType = "Code" BuildAction = "Compile" @@ -4604,6 +4614,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\TlsAeadCipher.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\TlsAgreementCredentials.cs" SubType = "Code" BuildAction = "Compile" @@ -4719,6 +4734,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\TlsHandshakeHash.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\TlsKeyExchange.cs" SubType = "Code" BuildAction = "Compile" diff --git a/crypto/src/crypto/engines/IesEngine.cs b/crypto/src/crypto/engines/IesEngine.cs index 6520c86f8..70df3077c 100644 --- a/crypto/src/crypto/engines/IesEngine.cs +++ b/crypto/src/crypto/engines/IesEngine.cs @@ -133,7 +133,7 @@ namespace Org.BouncyCastle.Crypto.Engines inOff += inLen; - byte[] T1 = Arrays.Copy(in_enc, inOff, macBuf.Length); + byte[] T1 = Arrays.CopyOfRange(in_enc, inOff, inOff + macBuf.Length); if (!Arrays.ConstantTimeAreEqual(T1, macBuf)) throw (new InvalidCipherTextException("Invalid MAC.")); diff --git a/crypto/src/crypto/macs/Poly1305.cs b/crypto/src/crypto/macs/Poly1305.cs index 2d453b6ad..1a951ca04 100644 --- a/crypto/src/crypto/macs/Poly1305.cs +++ b/crypto/src/crypto/macs/Poly1305.cs @@ -7,266 +7,285 @@ using Org.BouncyCastle.Crypto.Utilities; namespace Org.BouncyCastle.Crypto.Macs { - /// <summary> - /// Poly1305 message authentication code, designed by D. J. Bernstein. - /// </summary> - /// <remarks> - /// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key - /// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106 - /// effective key bits) used in the authenticator. - /// - /// The polynomial calculation in this implementation is adapted from the public domain <a - /// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation - /// by Andrew M (@floodyberry). - /// </remarks> - /// <seealso cref="Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/> - public class Poly1305 - : IMac - { - private const int BLOCK_SIZE = 16; - - private readonly IBlockCipher cipher; - - private readonly byte[] singleByte = new byte[1]; - - // Initialised state - - /** Polynomial key */ - private uint r0, r1, r2, r3, r4; - - /** Precomputed 5 * r[1..4] */ - private uint s1, s2, s3, s4; - - /** Encrypted nonce */ - private uint k0, k1, k2, k3; - - // Accumulating state - - /** Current block of buffered input */ - private byte[] currentBlock = new byte[BLOCK_SIZE]; - - /** Current offset in input buffer */ - private int currentBlockOffset = 0; - - /** Polynomial accumulator */ - private uint h0, h1, h2, h3, h4; - - /** - * Constructs a Poly1305 MAC, using a 128 bit block cipher. - */ - public Poly1305(IBlockCipher cipher) - { - if (cipher.GetBlockSize() != BLOCK_SIZE) - { - throw new ArgumentException("Poly1305 requires a 128 bit block cipher."); - } - this.cipher = cipher; - } - - /// <summary> - /// Initialises the Poly1305 MAC. - /// </summary> - /// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with - /// a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param> - public void Init(ICipherParameters parameters) - { - byte[] nonce; - byte[] key; - if ((parameters is ParametersWithIV) && ((ParametersWithIV)parameters).Parameters is KeyParameter) - { - nonce = ((ParametersWithIV)parameters).GetIV(); - key = ((KeyParameter)((ParametersWithIV)parameters).Parameters).GetKey(); - } - else - { - throw new ArgumentException("Poly1305 requires a key and and IV."); - } - - setKey(key, nonce); - Reset(); - } - - private void setKey(byte[] key, byte[] nonce) - { - if (nonce.Length != BLOCK_SIZE) - { - throw new ArgumentException("Poly1305 requires a 128 bit IV."); - } - Poly1305KeyGenerator.CheckKey(key); - - // Extract r portion of key - uint t0 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 0); - uint t1 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 4); - uint t2 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 8); - uint t3 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 12); - - r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; - r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; - r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; - r3 = t2 & 0x3f03fff; t3 >>= 8; - r4 = t3 & 0x00fffff; - - // Precompute multipliers - s1 = r1 * 5; - s2 = r2 * 5; - s3 = r3 * 5; - s4 = r4 * 5; - - // Compute encrypted nonce - byte[] cipherKey = new byte[BLOCK_SIZE]; - Array.Copy(key, 0, cipherKey, 0, cipherKey.Length); - - cipher.Init(true, new KeyParameter(cipherKey)); - cipher.ProcessBlock(nonce, 0, cipherKey, 0); - - k0 = Pack.LE_To_UInt32(cipherKey, 0); - k1 = Pack.LE_To_UInt32(cipherKey, 4); - k2 = Pack.LE_To_UInt32(cipherKey, 8); - k3 = Pack.LE_To_UInt32(cipherKey, 12); - } - - public string AlgorithmName - { - get { return "Poly1305-" + cipher.AlgorithmName; } - } - - public int GetMacSize() - { - return BLOCK_SIZE; - } - - public void Update(byte input) - { - singleByte[0] = input; - BlockUpdate(singleByte, 0, 1); - } - - public void BlockUpdate(byte[] input, int inOff, int len) - { - int copied = 0; - while (len > copied) - { - if (currentBlockOffset == BLOCK_SIZE) - { - processBlock(); - currentBlockOffset = 0; - } - - int toCopy = System.Math.Min((len - copied), BLOCK_SIZE - currentBlockOffset); - Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy); - copied += toCopy; - currentBlockOffset += toCopy; - } - - } - - private void processBlock() - { - if (currentBlockOffset < BLOCK_SIZE) - { - currentBlock[currentBlockOffset] = 1; - for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++) - { - currentBlock[i] = 0; - } - } - - ulong t0 = Pack.LE_To_UInt32(currentBlock, 0); - ulong t1 = Pack.LE_To_UInt32(currentBlock, 4); - ulong t2 = Pack.LE_To_UInt32(currentBlock, 8); - ulong t3 = Pack.LE_To_UInt32(currentBlock, 12); - - h0 += (uint)(t0 & 0x3ffffffU); - h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff); - h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff); - h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff); - h4 += (uint)(t3 >> 8); - - if (currentBlockOffset == BLOCK_SIZE) - { - h4 += (1 << 24); - } - - ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); - ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); - ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); - ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); - ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); - - ulong b; - h0 = (uint)tp0 & 0x3ffffff; b = (tp0 >> 26); - tp1 += b; h1 = (uint)tp1 & 0x3ffffff; b = (tp1 >> 26); - tp2 += b; h2 = (uint)tp2 & 0x3ffffff; b = (tp2 >> 26); - tp3 += b; h3 = (uint)tp3 & 0x3ffffff; b = (tp3 >> 26); - tp4 += b; h4 = (uint)tp4 & 0x3ffffff; b = (tp4 >> 26); - h0 += (uint)(b * 5); - } - - public int DoFinal(byte[] output, int outOff) - { - if (outOff + BLOCK_SIZE > output.Length) - { - throw new DataLengthException("Output buffer is too short."); - } - - if (currentBlockOffset > 0) - { - // Process padded block - processBlock(); - } - - ulong f0, f1, f2, f3; - - uint b = h0 >> 26; - h0 = h0 & 0x3ffffff; - h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; - h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; - h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; - h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; - h0 += b * 5; - - uint g0, g1, g2, g3, g4; - g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; - g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; - g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; - g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; - g4 = h4 + b - (1 << 26); - - b = (g4 >> 31) - 1; - uint nb = ~b; - h0 = (h0 & nb) | (g0 & b); - h1 = (h1 & nb) | (g1 & b); - h2 = (h2 & nb) | (g2 & b); - h3 = (h3 & nb) | (g3 & b); - h4 = (h4 & nb) | (g4 & b); - - f0 = ((h0 ) | (h1 << 26)) + (ulong)k0; - f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1; - f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2; - f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3; - - Pack.UInt32_To_LE((uint)f0, output, outOff); - f1 += (f0 >> 32); - Pack.UInt32_To_LE((uint)f1, output, outOff + 4); - f2 += (f1 >> 32); - Pack.UInt32_To_LE((uint)f2, output, outOff + 8); - f3 += (f2 >> 32); - Pack.UInt32_To_LE((uint)f3, output, outOff + 12); - - Reset(); - return BLOCK_SIZE; - } - - public void Reset() - { - currentBlockOffset = 0; - - h0 = h1 = h2 = h3 = h4 = 0; - } - - private static ulong mul32x32_64(uint i1, uint i2) - { - return ((ulong)i1) * i2; - } - } + /// <summary> + /// Poly1305 message authentication code, designed by D. J. Bernstein. + /// </summary> + /// <remarks> + /// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key + /// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106 + /// effective key bits) used in the authenticator. + /// + /// The polynomial calculation in this implementation is adapted from the public domain <a + /// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation + /// by Andrew M (@floodyberry). + /// </remarks> + /// <seealso cref="Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/> + public class Poly1305 + : IMac + { + private const int BLOCK_SIZE = 16; + + private readonly IBlockCipher cipher; + + private readonly byte[] singleByte = new byte[1]; + + // Initialised state + + /** Polynomial key */ + private uint r0, r1, r2, r3, r4; + + /** Precomputed 5 * r[1..4] */ + private uint s1, s2, s3, s4; + + /** Encrypted nonce */ + private uint k0, k1, k2, k3; + + // Accumulating state + + /** Current block of buffered input */ + private byte[] currentBlock = new byte[BLOCK_SIZE]; + + /** Current offset in input buffer */ + private int currentBlockOffset = 0; + + /** Polynomial accumulator */ + private uint h0, h1, h2, h3, h4; + + /** + * Constructs a Poly1305 MAC, where the key passed to init() will be used directly. + */ + public Poly1305() + { + this.cipher = null; + } + + /** + * Constructs a Poly1305 MAC, using a 128 bit block cipher. + */ + public Poly1305(IBlockCipher cipher) + { + if (cipher.GetBlockSize() != BLOCK_SIZE) + { + throw new ArgumentException("Poly1305 requires a 128 bit block cipher."); + } + this.cipher = cipher; + } + + /// <summary> + /// Initialises the Poly1305 MAC. + /// </summary> + /// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with + /// a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param> + public void Init(ICipherParameters parameters) + { + byte[] nonce = null; + + if (cipher != null) + { + if (!(parameters is ParametersWithIV)) + throw new ArgumentException("Poly1305 requires an IV when used with a block cipher.", "parameters"); + + ParametersWithIV ivParams = (ParametersWithIV)parameters; + nonce = ivParams.GetIV(); + parameters = ivParams.Parameters; + } + + if (!(parameters is KeyParameter)) + throw new ArgumentException("Poly1305 requires a key."); + + KeyParameter keyParams = (KeyParameter)parameters; + + SetKey(keyParams.GetKey(), nonce); + + Reset(); + } + + private void SetKey(byte[] key, byte[] nonce) + { + if (cipher != null && (nonce == null || nonce.Length != BLOCK_SIZE)) + throw new ArgumentException("Poly1305 requires a 128 bit IV."); + + Poly1305KeyGenerator.CheckKey(key); + + // Extract r portion of key + uint t0 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 0); + uint t1 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 4); + uint t2 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 8); + uint t3 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 12); + + r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; t3 >>= 8; + r4 = t3 & 0x00fffff; + + // Precompute multipliers + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + byte[] kBytes; + if (cipher == null) + { + kBytes = key; + } + else + { + // Compute encrypted nonce + kBytes = new byte[BLOCK_SIZE]; + cipher.Init(true, new KeyParameter(key, 0, BLOCK_SIZE)); + cipher.ProcessBlock(nonce, 0, kBytes, 0); + } + + k0 = Pack.LE_To_UInt32(kBytes, 0); + k1 = Pack.LE_To_UInt32(kBytes, 4); + k2 = Pack.LE_To_UInt32(kBytes, 8); + k3 = Pack.LE_To_UInt32(kBytes, 12); + } + + public string AlgorithmName + { + get { return cipher == null ? "Poly1305" : "Poly1305-" + cipher.AlgorithmName; } + } + + public int GetMacSize() + { + return BLOCK_SIZE; + } + + public void Update(byte input) + { + singleByte[0] = input; + BlockUpdate(singleByte, 0, 1); + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + int copied = 0; + while (len > copied) + { + if (currentBlockOffset == BLOCK_SIZE) + { + processBlock(); + currentBlockOffset = 0; + } + + int toCopy = System.Math.Min((len - copied), BLOCK_SIZE - currentBlockOffset); + Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy); + copied += toCopy; + currentBlockOffset += toCopy; + } + + } + + private void processBlock() + { + if (currentBlockOffset < BLOCK_SIZE) + { + currentBlock[currentBlockOffset] = 1; + for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++) + { + currentBlock[i] = 0; + } + } + + ulong t0 = Pack.LE_To_UInt32(currentBlock, 0); + ulong t1 = Pack.LE_To_UInt32(currentBlock, 4); + ulong t2 = Pack.LE_To_UInt32(currentBlock, 8); + ulong t3 = Pack.LE_To_UInt32(currentBlock, 12); + + h0 += (uint)(t0 & 0x3ffffffU); + h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff); + h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff); + h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff); + h4 += (uint)(t3 >> 8); + + if (currentBlockOffset == BLOCK_SIZE) + { + h4 += (1 << 24); + } + + ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + ulong b; + h0 = (uint)tp0 & 0x3ffffff; b = (tp0 >> 26); + tp1 += b; h1 = (uint)tp1 & 0x3ffffff; b = (tp1 >> 26); + tp2 += b; h2 = (uint)tp2 & 0x3ffffff; b = (tp2 >> 26); + tp3 += b; h3 = (uint)tp3 & 0x3ffffff; b = (tp3 >> 26); + tp4 += b; h4 = (uint)tp4 & 0x3ffffff; b = (tp4 >> 26); + h0 += (uint)(b * 5); + } + + public int DoFinal(byte[] output, int outOff) + { + if (outOff + BLOCK_SIZE > output.Length) + { + throw new DataLengthException("Output buffer is too short."); + } + + if (currentBlockOffset > 0) + { + // Process padded block + processBlock(); + } + + ulong f0, f1, f2, f3; + + uint b = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += b * 5; + + uint g0, g1, g2, g3, g4; + g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + uint nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0 ) | (h1 << 26)) + (ulong)k0; + f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1; + f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2; + f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3; + + Pack.UInt32_To_LE((uint)f0, output, outOff); + f1 += (f0 >> 32); + Pack.UInt32_To_LE((uint)f1, output, outOff + 4); + f2 += (f1 >> 32); + Pack.UInt32_To_LE((uint)f2, output, outOff + 8); + f3 += (f2 >> 32); + Pack.UInt32_To_LE((uint)f3, output, outOff + 12); + + Reset(); + return BLOCK_SIZE; + } + + public void Reset() + { + currentBlockOffset = 0; + + h0 = h1 = h2 = h3 = h4 = 0; + } + + private static ulong mul32x32_64(uint i1, uint i2) + { + return ((ulong)i1) * i2; + } + } } diff --git a/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs b/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs new file mode 100644 index 000000000..141ee6507 --- /dev/null +++ b/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class AbstractTlsCipherFactory + : TlsCipherFactory + { + /// <exception cref="IOException"></exception> + public virtual TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/crypto/src/crypto/tls/Chacha20Poly1305.cs b/crypto/src/crypto/tls/Chacha20Poly1305.cs new file mode 100644 index 000000000..e4e4c7ee2 --- /dev/null +++ b/crypto/src/crypto/tls/Chacha20Poly1305.cs @@ -0,0 +1,153 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class Chacha20Poly1305 + : TlsCipher + { + protected readonly TlsContext context; + + protected readonly ChaChaEngine encryptCipher; + protected readonly ChaChaEngine decryptCipher; + + /// <exception cref="IOException"></exception> + public Chacha20Poly1305(TlsContext context) + { + if (!TlsUtilities.IsTlsV12(context)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.context = context; + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, 64); + + KeyParameter client_write_key = new KeyParameter(key_block, 0, 32); + KeyParameter server_write_key = new KeyParameter(key_block, 32, 32); + + this.encryptCipher = new ChaChaEngine(20); + this.decryptCipher = new ChaChaEngine(20); + + KeyParameter encryptKey, decryptKey; + if (context.IsServer) + { + encryptKey = server_write_key; + decryptKey = client_write_key; + } + else + { + encryptKey = client_write_key; + decryptKey = server_write_key; + } + + byte[] dummyNonce = new byte[8]; + + this.encryptCipher.Init(true, new ParametersWithIV(encryptKey, dummyNonce)); + this.decryptCipher.Init(false, new ParametersWithIV(decryptKey, dummyNonce)); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + return ciphertextLimit - 16; + } + + /// <exception cref="IOException"></exception> + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + int ciphertextLength = len + 16; + + KeyParameter macKey = InitRecordMac(encryptCipher, true, seqNo); + + byte[] output = new byte[ciphertextLength]; + encryptCipher.ProcessBytes(plaintext, offset, len, output, 0); + + byte[] additionalData = GetAdditionalData(seqNo, type, len); + byte[] mac = CalculateRecordMac(macKey, additionalData, output, 0, len); + Array.Copy(mac, 0, output, len, mac.Length); + + return output; + } + + /// <exception cref="IOException"></exception> + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (GetPlaintextLimit(len) < 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int plaintextLength = len - 16; + + byte[] receivedMAC = Arrays.CopyOfRange(ciphertext, offset + plaintextLength, offset + len); + + KeyParameter macKey = InitRecordMac(decryptCipher, false, seqNo); + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + byte[] calculatedMAC = CalculateRecordMac(macKey, additionalData, ciphertext, offset, plaintextLength); + + if (!Arrays.ConstantTimeAreEqual(calculatedMAC, receivedMAC)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + + byte[] output = new byte[plaintextLength]; + decryptCipher.ProcessBytes(ciphertext, offset, plaintextLength, output, 0); + + return output; + } + + protected virtual KeyParameter InitRecordMac(ChaChaEngine cipher, bool forEncryption, long seqNo) + { + byte[] nonce = new byte[8]; + TlsUtilities.WriteUint64(seqNo, nonce, 0); + + cipher.Init(forEncryption, new ParametersWithIV(null, nonce)); + + byte[] firstBlock = new byte[64]; + cipher.ProcessBytes(firstBlock, 0, firstBlock.Length, firstBlock, 0); + + // NOTE: The BC implementation puts 'r' after 'k' + Array.Copy(firstBlock, 0, firstBlock, 32, 16); + KeyParameter macKey = new KeyParameter(firstBlock, 16, 32); + Poly1305KeyGenerator.Clamp(macKey.GetKey()); + return macKey; + } + + protected virtual byte[] CalculateRecordMac(KeyParameter macKey, byte[] additionalData, byte[] buf, int off, int len) + { + IMac mac = new Poly1305(); + mac.Init(macKey); + + UpdateRecordMac(mac, additionalData, 0, additionalData.Length); + UpdateRecordMac(mac, buf, off, len); + return MacUtilities.DoFinal(mac); + } + + protected virtual void UpdateRecordMac(IMac mac, byte[] buf, int off, int len) + { + mac.BlockUpdate(buf, off, len); + + byte[] longLen = Pack.UInt64_To_LE((ulong)len); + mac.BlockUpdate(longLen, 0, longLen.Length); + } + + /// <exception cref="IOException"></exception> + protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len) + { + /* + * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + + * TLSCompressed.length + */ + byte[] additional_data = new byte[13]; + TlsUtilities.WriteUint64(seqNo, additional_data, 0); + TlsUtilities.WriteUint8(type, additional_data, 8); + TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9); + TlsUtilities.WriteUint16(len, additional_data, 11); + + return additional_data; + } + } +} diff --git a/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs b/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs index cc34b3028..7c4213c25 100644 --- a/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs +++ b/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs @@ -1,53 +1,146 @@ using System; using System.IO; -using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; namespace Org.BouncyCastle.Crypto.Tls { public class DefaultTlsCipherFactory - : TlsCipherFactory + : AbstractTlsCipherFactory { - public virtual TlsCipher CreateCipher(TlsContext context, - int encryptionAlgorithm, DigestAlgorithm digestAlgorithm) + /// <exception cref="IOException"></exception> + public override TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) { switch (encryptionAlgorithm) { - case EncryptionAlgorithm.cls_3DES_EDE_CBC: - return CreateDesEdeCipher(context, 24, digestAlgorithm); - case EncryptionAlgorithm.AES_128_CBC: - return CreateAesCipher(context, 16, digestAlgorithm); - case EncryptionAlgorithm.AES_256_CBC: - return CreateAesCipher(context, 32, digestAlgorithm); - case EncryptionAlgorithm.RC4_128: - return CreateRC4Cipher(context, 16, digestAlgorithm); - default: - throw new TlsFatalAlert(AlertDescription.internal_error); + case EncryptionAlgorithm.cls_3DES_EDE_CBC: + return CreateDesEdeCipher(context, macAlgorithm); + case EncryptionAlgorithm.AEAD_CHACHA20_POLY1305: + // NOTE: Ignores macAlgorithm + return CreateChaCha20Poly1305(context); + case EncryptionAlgorithm.AES_128_CBC: + return CreateAESCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.AES_128_CCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 16, 16); + case EncryptionAlgorithm.AES_128_CCM_8: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 16, 8); + case EncryptionAlgorithm.AES_256_CCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 32, 16); + case EncryptionAlgorithm.AES_256_CCM_8: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 32, 8); + case EncryptionAlgorithm.AES_128_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Gcm(context, 16, 16); + case EncryptionAlgorithm.AES_256_CBC: + return CreateAESCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.AES_256_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Gcm(context, 32, 16); + case EncryptionAlgorithm.CAMELLIA_128_CBC: + return CreateCamelliaCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.CAMELLIA_128_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Camellia_Gcm(context, 16, 16); + case EncryptionAlgorithm.CAMELLIA_256_CBC: + return CreateCamelliaCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.CAMELLIA_256_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Camellia_Gcm(context, 32, 16); + case EncryptionAlgorithm.ESTREAM_SALSA20: + return CreateSalsa20Cipher(context, 12, 32, macAlgorithm); + case EncryptionAlgorithm.NULL: + return CreateNullCipher(context, macAlgorithm); + case EncryptionAlgorithm.RC4_128: + return CreateRC4Cipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.SALSA20: + return CreateSalsa20Cipher(context, 20, 32, macAlgorithm); + case EncryptionAlgorithm.SEED_CBC: + return CreateSeedCipher(context, macAlgorithm); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); } } /// <exception cref="IOException"></exception> - protected virtual TlsCipher CreateRC4Cipher(TlsContext context, int cipherKeySize, DigestAlgorithm digestAlgorithm) + protected virtual TlsBlockCipher CreateAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm) { - return new TlsStreamCipher(context, CreateRC4StreamCipher(), CreateRC4StreamCipher(), CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize); + return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize); } /// <exception cref="IOException"></exception> - protected virtual TlsCipher CreateAesCipher(TlsContext context, int cipherKeySize, - DigestAlgorithm digestAlgorithm) + protected virtual TlsBlockCipher CreateCamelliaCipher(TlsContext context, int cipherKeySize, int macAlgorithm) { - return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(), - CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize); + return new TlsBlockCipher(context, CreateCamelliaBlockCipher(), + CreateCamelliaBlockCipher(), CreateHMacDigest(macAlgorithm), + CreateHMacDigest(macAlgorithm), cipherKeySize); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsCipher CreateChaCha20Poly1305(TlsContext context) + { + return new Chacha20Poly1305(context); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsAeadCipher CreateCipher_Aes_Ccm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Ccm(), + CreateAeadBlockCipher_Aes_Ccm(), cipherKeySize, macSize); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsAeadCipher CreateCipher_Aes_Gcm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Gcm(), + CreateAeadBlockCipher_Aes_Gcm(), cipherKeySize, macSize); } /// <exception cref="IOException"></exception> - protected virtual TlsCipher CreateDesEdeCipher(TlsContext context, int cipherKeySize, - DigestAlgorithm digestAlgorithm) + protected virtual TlsAeadCipher CreateCipher_Camellia_Gcm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Camellia_Gcm(), + CreateAeadBlockCipher_Camellia_Gcm(), cipherKeySize, macSize); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsBlockCipher CreateDesEdeCipher(TlsContext context, int macAlgorithm) { return new TlsBlockCipher(context, CreateDesEdeBlockCipher(), CreateDesEdeBlockCipher(), - CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize); + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 24); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsNullCipher CreateNullCipher(TlsContext context, int macAlgorithm) + { + return new TlsNullCipher(context, CreateHMacDigest(macAlgorithm), + CreateHMacDigest(macAlgorithm)); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsStreamCipher CreateRC4Cipher(TlsContext context, int cipherKeySize, int macAlgorithm) + { + return new TlsStreamCipher(context, CreateRC4StreamCipher(), CreateRC4StreamCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize, false); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsStreamCipher CreateSalsa20Cipher(TlsContext context, int rounds, int cipherKeySize, int macAlgorithm) + { + return new TlsStreamCipher(context, CreateSalsa20StreamCipher(rounds), CreateSalsa20StreamCipher(rounds), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize, true); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsBlockCipher CreateSeedCipher(TlsContext context, int macAlgorithm) + { + return new TlsBlockCipher(context, CreateSeedBlockCipher(), CreateSeedBlockCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 16); } protected virtual IBlockCipher CreateAesEngine() @@ -55,11 +148,38 @@ namespace Org.BouncyCastle.Crypto.Tls return new AesEngine(); } + protected virtual IBlockCipher CreateCamelliaEngine() + { + return new CamelliaEngine(); + } + protected virtual IBlockCipher CreateAesBlockCipher() { return new CbcBlockCipher(CreateAesEngine()); } + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ccm() + { + return new CcmBlockCipher(CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Gcm() + { + // TODO Consider allowing custom configuration of multiplier + return new GcmBlockCipher(CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Camellia_Gcm() + { + // TODO Consider allowing custom configuration of multiplier + return new GcmBlockCipher(CreateCamelliaEngine()); + } + + protected virtual IBlockCipher CreateCamelliaBlockCipher() + { + return new CbcBlockCipher(CreateCamelliaEngine()); + } + protected virtual IBlockCipher CreateDesEdeBlockCipher() { return new CbcBlockCipher(new DesEdeEngine()); @@ -70,21 +190,35 @@ namespace Org.BouncyCastle.Crypto.Tls return new RC4Engine(); } + protected virtual IStreamCipher CreateSalsa20StreamCipher(int rounds) + { + return new Salsa20Engine(rounds); + } + + protected virtual IBlockCipher CreateSeedBlockCipher() + { + return new CbcBlockCipher(new SeedEngine()); + } + /// <exception cref="IOException"></exception> - protected virtual IDigest CreateDigest(DigestAlgorithm digestAlgorithm) + protected virtual IDigest CreateHMacDigest(int macAlgorithm) { - switch (digestAlgorithm) + switch (macAlgorithm) { - case DigestAlgorithm.MD5: - return new MD5Digest(); - case DigestAlgorithm.SHA: - return new Sha1Digest(); - case DigestAlgorithm.SHA256: - return new Sha256Digest(); - case DigestAlgorithm.SHA384: - return new Sha384Digest(); - default: - throw new TlsFatalAlert(AlertDescription.internal_error); + case MacAlgorithm.cls_null: + return null; + case MacAlgorithm.hmac_md5: + return TlsUtilities.CreateHash(HashAlgorithm.md5); + case MacAlgorithm.hmac_sha1: + return TlsUtilities.CreateHash(HashAlgorithm.sha1); + case MacAlgorithm.hmac_sha256: + return TlsUtilities.CreateHash(HashAlgorithm.sha256); + case MacAlgorithm.hmac_sha384: + return TlsUtilities.CreateHash(HashAlgorithm.sha384); + case MacAlgorithm.hmac_sha512: + return TlsUtilities.CreateHash(HashAlgorithm.sha512); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); } } } diff --git a/crypto/src/crypto/tls/DefaultTlsClient.cs b/crypto/src/crypto/tls/DefaultTlsClient.cs index d59fae164..924e6ee2e 100644 --- a/crypto/src/crypto/tls/DefaultTlsClient.cs +++ b/crypto/src/crypto/tls/DefaultTlsClient.cs @@ -213,14 +213,15 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_sha1); case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: @@ -231,7 +232,8 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: @@ -242,7 +244,8 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, + MacAlgorithm.hmac_sha1); default: /* diff --git a/crypto/src/crypto/tls/PskTlsClient.cs b/crypto/src/crypto/tls/PskTlsClient.cs index e60688155..f6d83b5be 100644 --- a/crypto/src/crypto/tls/PskTlsClient.cs +++ b/crypto/src/crypto/tls/PskTlsClient.cs @@ -163,25 +163,25 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, - DigestAlgorithm.SHA); + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, - DigestAlgorithm.SHA); + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, - DigestAlgorithm.SHA); + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128, - DigestAlgorithm.SHA); + MacAlgorithm.hmac_sha1); default: /* diff --git a/crypto/src/crypto/tls/RecordStream.cs b/crypto/src/crypto/tls/RecordStream.cs index ce8882cbe..d05c6c1c2 100644 --- a/crypto/src/crypto/tls/RecordStream.cs +++ b/crypto/src/crypto/tls/RecordStream.cs @@ -14,8 +14,11 @@ namespace Org.BouncyCastle.Crypto.Tls private TlsCompression writeCompression = null; private TlsCipher readCipher = null; private TlsCipher writeCipher = null; + private long readSeqNo = 0, writeSeqNo = 0; private MemoryStream buffer = new MemoryStream(); + private TlsContext context = null; + internal RecordStream( TlsProtocolHandler handler, Stream inStr, @@ -24,23 +27,30 @@ namespace Org.BouncyCastle.Crypto.Tls this.handler = handler; this.inStr = inStr; this.outStr = outStr; - this.hash = new CombinedHash(); this.readCompression = new TlsNullCompression(); this.writeCompression = this.readCompression; - this.readCipher = new TlsNullCipher(); + } + + internal void Init(TlsContext context) + { + this.context = context; + this.readCipher = new TlsNullCipher(context); this.writeCipher = this.readCipher; + this.hash = new CombinedHash(); } internal void ClientCipherSpecDecided(TlsCompression tlsCompression, TlsCipher tlsCipher) { this.writeCompression = tlsCompression; this.writeCipher = tlsCipher; + this.writeSeqNo = 0; } internal void ServerClientSpecReceived() { this.readCompression = this.writeCompression; this.readCipher = this.writeCipher; + this.readSeqNo = 0; } public void ReadData() @@ -59,7 +69,7 @@ namespace Org.BouncyCastle.Crypto.Tls { byte[] buf = new byte[len]; TlsUtilities.ReadFully(buf, inStr); - byte[] decoded = readCipher.DecodeCiphertext(contentType, buf, 0, buf.Length); + byte[] decoded = readCipher.DecodeCiphertext(readSeqNo++, contentType, buf, 0, buf.Length); Stream cOut = readCompression.Decompress(buffer); @@ -91,13 +101,13 @@ namespace Org.BouncyCastle.Crypto.Tls byte[] ciphertext; if (cOut == buffer) { - ciphertext = writeCipher.EncodePlaintext(type, message, offset, len); + ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, message, offset, len); } else { cOut.Write(message, offset, len); cOut.Flush(); - ciphertext = writeCipher.EncodePlaintext(type, buffer.GetBuffer(), 0, (int)buffer.Position); + ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, buffer.GetBuffer(), 0, (int)buffer.Position); buffer.SetLength(0); } diff --git a/crypto/src/crypto/tls/SecurityParameters.cs b/crypto/src/crypto/tls/SecurityParameters.cs index 0707f3d40..de8c6f13a 100644 --- a/crypto/src/crypto/tls/SecurityParameters.cs +++ b/crypto/src/crypto/tls/SecurityParameters.cs @@ -6,18 +6,27 @@ namespace Org.BouncyCastle.Crypto.Tls { public class SecurityParameters { + internal int entity = -1; + internal int cipherSuite = -1; + internal byte compressionAlgorithm = CompressionMethod.NULL; internal int prfAlgorithm = -1; + internal int verifyDataLength = -1; internal byte[] masterSecret = null; internal byte[] clientRandom = null; internal byte[] serverRandom = null; + // TODO Keep these internal, since it's maybe not the ideal place for them + internal short maxFragmentLength = -1; + internal bool truncatedHMac = false; + internal bool encryptThenMac = false; + internal void CopySessionParametersFrom(SecurityParameters other) { - //this.entity = other.entity; - //this.cipherSuite = other.cipherSuite; - //this.compressionAlgorithm = other.compressionAlgorithm; + this.entity = other.entity; + this.cipherSuite = other.cipherSuite; + this.compressionAlgorithm = other.compressionAlgorithm; this.prfAlgorithm = other.prfAlgorithm; - //this.verifyDataLength = other.verifyDataLength; + this.verifyDataLength = other.verifyDataLength; this.masterSecret = Arrays.Clone(other.masterSecret); } @@ -31,6 +40,30 @@ namespace Org.BouncyCastle.Crypto.Tls } /** + * @return {@link ConnectionEnd} + */ + public virtual int Entity + { + get { return entity; } + } + + /** + * @return {@link CipherSuite} + */ + public virtual int CipherSuite + { + get { return cipherSuite; } + } + + /** + * @return {@link CompressionMethod} + */ + public byte CompressionAlgorithm + { + get { return compressionAlgorithm; } + } + + /** * @return {@link PRFAlgorithm} */ public virtual int PrfAlgorithm @@ -38,6 +71,11 @@ namespace Org.BouncyCastle.Crypto.Tls get { return prfAlgorithm; } } + public virtual int VerifyDataLength + { + get { return verifyDataLength; } + } + public virtual byte[] MasterSecret { get { return masterSecret; } diff --git a/crypto/src/crypto/tls/SrpTlsClient.cs b/crypto/src/crypto/tls/SrpTlsClient.cs index 3769fc85d..dfd7603b8 100644 --- a/crypto/src/crypto/tls/SrpTlsClient.cs +++ b/crypto/src/crypto/tls/SrpTlsClient.cs @@ -168,17 +168,20 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, + MacAlgorithm.hmac_sha1); case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: - return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA); + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, + MacAlgorithm.hmac_sha1); default: /* diff --git a/crypto/src/crypto/tls/Ssl3Mac.cs b/crypto/src/crypto/tls/Ssl3Mac.cs index b2f3f309e..8bdb342dc 100644 --- a/crypto/src/crypto/tls/Ssl3Mac.cs +++ b/crypto/src/crypto/tls/Ssl3Mac.cs @@ -6,109 +6,105 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Tls { - /** - * HMAC implementation based on original internet draft for HMAC (RFC 2104) - * - * The difference is that padding is concatentated versus XORed with the key - * - * H(K + opad, H(K + ipad, text)) - */ - public class Ssl3Mac - : IMac - { - private const byte IPAD = 0x36; - private const byte OPAD = 0x5C; - - internal static readonly byte[] MD5_IPAD = GenPad(IPAD, 48); - internal static readonly byte[] MD5_OPAD = GenPad(OPAD, 48); - internal static readonly byte[] SHA1_IPAD = GenPad(IPAD, 40); - internal static readonly byte[] SHA1_OPAD = GenPad(OPAD, 40); - - private IDigest digest; - - private byte[] secret; - private byte[] ipad, opad; - - /** - * Base constructor for one of the standard digest algorithms that the byteLength of - * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. - * - * @param digest the digest. - */ - public Ssl3Mac(IDigest digest) - { - this.digest = digest; - - if (digest.GetDigestSize() == 20) - { - this.ipad = SHA1_IPAD; - this.opad = SHA1_OPAD; - } - else - { - this.ipad = MD5_IPAD; - this.opad = MD5_OPAD; - } - } - - public virtual string AlgorithmName - { - get { return digest.AlgorithmName + "/SSL3MAC"; } - } - - public virtual void Init(ICipherParameters parameters) - { - secret = Arrays.Clone(((KeyParameter)parameters).GetKey()); - - Reset(); - } - - public virtual int GetMacSize() - { - return digest.GetDigestSize(); - } - - public virtual void Update(byte input) - { - digest.Update(input); - } - - public virtual void BlockUpdate(byte[] input, int inOff, int len) - { - digest.BlockUpdate(input, inOff, len); - } - - public virtual int DoFinal(byte[] output, int outOff) - { - byte[] tmp = new byte[digest.GetDigestSize()]; - digest.DoFinal(tmp, 0); - - digest.BlockUpdate(secret, 0, secret.Length); - digest.BlockUpdate(opad, 0, opad.Length); - digest.BlockUpdate(tmp, 0, tmp.Length); - - int len = digest.DoFinal(output, outOff); - - Reset(); - - return len; - } - - /** - * Reset the mac generator. - */ - public virtual void Reset() - { - digest.Reset(); - digest.BlockUpdate(secret, 0, secret.Length); - digest.BlockUpdate(ipad, 0, ipad.Length); - } - - private static byte[] GenPad(byte b, int count) - { - byte[] padding = new byte[count]; - Arrays.Fill(padding, b); - return padding; - } - } + /** + * HMAC implementation based on original internet draft for HMAC (RFC 2104) + * + * The difference is that padding is concatentated versus XORed with the key + * + * H(K + opad, H(K + ipad, text)) + */ + public class Ssl3Mac + : IMac + { + private const byte IPAD_BYTE = 0x36; + private const byte OPAD_BYTE = 0x5C; + + internal static readonly byte[] IPAD = GenPad(IPAD_BYTE, 48); + internal static readonly byte[] OPAD = GenPad(OPAD_BYTE, 48); + + private readonly IDigest digest; + private readonly int padLength; + + private byte[] secret; + + /** + * Base constructor for one of the standard digest algorithms that the byteLength of + * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. + * + * @param digest the digest. + */ + public Ssl3Mac(IDigest digest) + { + this.digest = digest; + + if (digest.GetDigestSize() == 20) + { + this.padLength = 40; + } + else + { + this.padLength = 48; + } + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "/SSL3MAC"; } + } + + public virtual void Init(ICipherParameters parameters) + { + secret = Arrays.Clone(((KeyParameter)parameters).GetKey()); + + Reset(); + } + + public virtual int GetMacSize() + { + return digest.GetDigestSize(); + } + + public virtual void Update(byte input) + { + digest.Update(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + digest.BlockUpdate(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + byte[] tmp = new byte[digest.GetDigestSize()]; + digest.DoFinal(tmp, 0); + + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(OPAD, 0, padLength); + digest.BlockUpdate(tmp, 0, tmp.Length); + + int len = digest.DoFinal(output, outOff); + + Reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public virtual void Reset() + { + digest.Reset(); + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(IPAD, 0, padLength); + } + + private static byte[] GenPad(byte b, int count) + { + byte[] padding = new byte[count]; + Arrays.Fill(padding, b); + return padding; + } + } } diff --git a/crypto/src/crypto/tls/TlsAeadCipher.cs b/crypto/src/crypto/tls/TlsAeadCipher.cs new file mode 100644 index 000000000..e66f92317 --- /dev/null +++ b/crypto/src/crypto/tls/TlsAeadCipher.cs @@ -0,0 +1,189 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsAeadCipher + : TlsCipher + { + protected readonly TlsContext context; + protected readonly int macSize; + protected readonly int nonce_explicit_length; + + protected readonly IAeadBlockCipher encryptCipher; + protected readonly IAeadBlockCipher decryptCipher; + + protected readonly byte[] encryptImplicitNonce, decryptImplicitNonce; + + /// <exception cref="IOException"></exception> + public TlsAeadCipher(TlsContext context, IAeadBlockCipher clientWriteCipher, IAeadBlockCipher serverWriteCipher, + int cipherKeySize, int macSize) + { + if (!TlsUtilities.IsTlsV12(context)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.context = context; + this.macSize = macSize; + + // NOTE: Valid for RFC 5288/6655 ciphers but may need review for other AEAD ciphers + this.nonce_explicit_length = 8; + + // TODO SecurityParameters.fixed_iv_length + int fixed_iv_length = 4; + + int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length); + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + byte[] client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + byte[] server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + + if (offset != key_block_size) + throw new TlsFatalAlert(AlertDescription.internal_error); + + KeyParameter encryptKey, decryptKey; + if (context.IsServer) + { + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + this.encryptImplicitNonce = server_write_IV; + this.decryptImplicitNonce = client_write_IV; + encryptKey = server_write_key; + decryptKey = client_write_key; + } + else + { + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + this.encryptImplicitNonce = client_write_IV; + this.decryptImplicitNonce = server_write_IV; + encryptKey = client_write_key; + decryptKey = server_write_key; + } + + byte[] dummyNonce = new byte[fixed_iv_length + nonce_explicit_length]; + + this.encryptCipher.Init(true, new AeadParameters(encryptKey, 8 * macSize, dummyNonce)); + this.decryptCipher.Init(false, new AeadParameters(decryptKey, 8 * macSize, dummyNonce)); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + // TODO We ought to be able to ask the decryptCipher (independently of it's current state!) + return ciphertextLimit - macSize - nonce_explicit_length; + } + + /// <exception cref="IOException"></exception> + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + byte[] nonce = new byte[this.encryptImplicitNonce.Length + nonce_explicit_length]; + Array.Copy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.Length); + + /* + * RFC 5288/6655 The nonce_explicit MAY be the 64-bit sequence number. + * + * (May need review for other AEAD ciphers). + */ + TlsUtilities.WriteUint64(seqNo, nonce, encryptImplicitNonce.Length); + + int plaintextOffset = offset; + int plaintextLength = len; + int ciphertextLength = encryptCipher.GetOutputSize(plaintextLength); + + byte[] output = new byte[nonce_explicit_length + ciphertextLength]; + Array.Copy(nonce, encryptImplicitNonce.Length, output, 0, nonce_explicit_length); + int outputPos = nonce_explicit_length; + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData); + + try + { + encryptCipher.Init(true, parameters); + outputPos += encryptCipher.ProcessBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos); + outputPos += encryptCipher.DoFinal(output, outputPos); + } + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (outputPos != output.Length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + /// <exception cref="IOException"></exception> + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (GetPlaintextLimit(len) < 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + byte[] nonce = new byte[this.decryptImplicitNonce.Length + nonce_explicit_length]; + Array.Copy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.Length); + Array.Copy(ciphertext, offset, nonce, decryptImplicitNonce.Length, nonce_explicit_length); + + int ciphertextOffset = offset + nonce_explicit_length; + int ciphertextLength = len - nonce_explicit_length; + int plaintextLength = decryptCipher.GetOutputSize(ciphertextLength); + + byte[] output = new byte[plaintextLength]; + int outputPos = 0; + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData); + + try + { + decryptCipher.Init(false, parameters); + outputPos += decryptCipher.ProcessBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos); + outputPos += decryptCipher.DoFinal(output, outputPos); + } + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + if (outputPos != output.Length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + /// <exception cref="IOException"></exception> + protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len) + { + /* + * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + + * TLSCompressed.length + */ + + byte[] additional_data = new byte[13]; + TlsUtilities.WriteUint64(seqNo, additional_data, 0); + TlsUtilities.WriteUint8(type, additional_data, 8); + TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9); + TlsUtilities.WriteUint16(len, additional_data, 11); + + return additional_data; + } + } +} diff --git a/crypto/src/crypto/tls/TlsBlockCipher.cs b/crypto/src/crypto/tls/TlsBlockCipher.cs index b2c69127c..82c0318b2 100644 --- a/crypto/src/crypto/tls/TlsBlockCipher.cs +++ b/crypto/src/crypto/tls/TlsBlockCipher.cs @@ -1,8 +1,6 @@ using System; using System.IO; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; @@ -10,169 +8,302 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Tls { /// <summary> - /// A generic TLS 1.0 block cipher. This can be used for AES or 3DES for example. + /// A generic TLS 1.0-1.2 / SSLv3 block cipher. This can be used for AES or 3DES for example. /// </summary> public class TlsBlockCipher - : TlsCipher + : TlsCipher { - protected TlsContext context; - protected byte[] randomData; + protected readonly TlsContext context; + protected readonly byte[] randomData; + protected readonly bool useExplicitIV; + protected readonly bool encryptThenMac; - protected IBlockCipher encryptCipher; - protected IBlockCipher decryptCipher; + protected readonly IBlockCipher encryptCipher; + protected readonly IBlockCipher decryptCipher; - protected TlsMac wMac; - protected TlsMac rMac; + protected readonly TlsMac mWriteMac; + protected readonly TlsMac mReadMac; public virtual TlsMac WriteMac { - get { return wMac; } + get { return mWriteMac; } } public virtual TlsMac ReadMac { - get { return rMac; } + get { return mReadMac; } } - public TlsBlockCipher(TlsContext context, IBlockCipher encryptCipher, - IBlockCipher decryptCipher, IDigest writeDigest, IDigest readDigest, int cipherKeySize) + /// <exception cref="IOException"></exception> + public TlsBlockCipher(TlsContext context, IBlockCipher clientWriteCipher, IBlockCipher serverWriteCipher, + IDigest clientWriteDigest, IDigest serverWriteDigest, int cipherKeySize) { this.context = context; this.randomData = new byte[256]; - context.SecureRandom.NextBytes(randomData); + context.NonceRandomGenerator.NextBytes(randomData); - this.encryptCipher = encryptCipher; - this.decryptCipher = decryptCipher; + this.useExplicitIV = TlsUtilities.IsTlsV11(context); + this.encryptThenMac = context.SecurityParameters.encryptThenMac; - int prfSize = (2 * cipherKeySize) + writeDigest.GetDigestSize() - + readDigest.GetDigestSize() + encryptCipher.GetBlockSize() - + decryptCipher.GetBlockSize(); + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); - SecurityParameters securityParameters = context.SecurityParameters; + // From TLS 1.1 onwards, block ciphers don't need client_write_IV + if (!useExplicitIV) + { + key_block_size += clientWriteCipher.GetBlockSize() + serverWriteCipher.GetBlockSize(); + } - byte[] keyBlock = TlsUtilities.PRF(context, securityParameters.masterSecret, ExporterLabel.key_expansion, - TlsUtilities.Concat(securityParameters.serverRandom, securityParameters.clientRandom), - prfSize); + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); int offset = 0; - // Init MACs - wMac = CreateTlsMac(writeDigest, keyBlock, ref offset); - rMac = CreateTlsMac(readDigest, keyBlock, ref offset); + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); - // Build keys - KeyParameter encryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); - KeyParameter decryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; - // Add IVs - ParametersWithIV encryptParams = CreateParametersWithIV(encryptKey, - keyBlock, ref offset, encryptCipher.GetBlockSize()); - ParametersWithIV decryptParams = CreateParametersWithIV(decryptKey, - keyBlock, ref offset, decryptCipher.GetBlockSize()); + byte[] client_write_IV, server_write_IV; + if (useExplicitIV) + { + client_write_IV = new byte[clientWriteCipher.GetBlockSize()]; + server_write_IV = new byte[serverWriteCipher.GetBlockSize()]; + } + else + { + client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + clientWriteCipher.GetBlockSize()); + offset += clientWriteCipher.GetBlockSize(); + server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + serverWriteCipher.GetBlockSize()); + offset += serverWriteCipher.GetBlockSize(); + } - if (offset != prfSize) + if (offset != key_block_size) + { throw new TlsFatalAlert(AlertDescription.internal_error); + } - // Init Ciphers - encryptCipher.Init(true, encryptParams); - decryptCipher.Init(false, decryptParams); - } + ICipherParameters encryptParams, decryptParams; + if (context.IsServer) + { + this.mWriteMac = serverWriteMac; + this.mReadMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = new ParametersWithIV(server_write_key, server_write_IV); + decryptParams = new ParametersWithIV(client_write_key, client_write_IV); + } + else + { + this.mWriteMac = clientWriteMac; + this.mReadMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = new ParametersWithIV(client_write_key, client_write_IV); + decryptParams = new ParametersWithIV(server_write_key, server_write_IV); + } - protected virtual TlsMac CreateTlsMac(IDigest digest, byte[] buf, ref int off) - { - int len = digest.GetDigestSize(); - TlsMac mac = new TlsMac(digest, buf, off, len); - off += len; - return mac; + this.encryptCipher.Init(true, encryptParams); + this.decryptCipher.Init(false, decryptParams); } - protected virtual KeyParameter CreateKeyParameter(byte[] buf, ref int off, int len) + public virtual int GetPlaintextLimit(int ciphertextLimit) { - KeyParameter key = new KeyParameter(buf, off, len); - off += len; - return key; - } + int blockSize = encryptCipher.GetBlockSize(); + int macSize = mWriteMac.Size; - protected virtual ParametersWithIV CreateParametersWithIV(KeyParameter key, - byte[] buf, ref int off, int len) - { - ParametersWithIV ivParams = new ParametersWithIV(key, buf, off, len); - off += len; - return ivParams; + int plaintextLimit = ciphertextLimit; + + // An explicit IV consumes 1 block + if (useExplicitIV) + { + plaintextLimit -= blockSize; + } + + // Leave room for the MAC, and require block-alignment + if (encryptThenMac) + { + plaintextLimit -= macSize; + plaintextLimit -= plaintextLimit % blockSize; + } + else + { + plaintextLimit -= plaintextLimit % blockSize; + plaintextLimit -= macSize; + } + + // Minimum 1 byte of padding + --plaintextLimit; + + return plaintextLimit; } - public virtual byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len) + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) { - int blocksize = encryptCipher.GetBlockSize(); - int padding_length = blocksize - 1 - ((len + wMac.Size) % blocksize); + int blockSize = encryptCipher.GetBlockSize(); + int macSize = mWriteMac.Size; + + ProtocolVersion version = context.ServerVersion; + + int enc_input_length = len; + if (!encryptThenMac) + { + enc_input_length += macSize; + } - //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion; - bool isTls = true; + int padding_length = blockSize - 1 - (enc_input_length % blockSize); - if (isTls) + // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though) + if (!version.IsDtls && !version.IsSsl) { // Add a random number of extra blocks worth of padding - int maxExtraPadBlocks = (255 - padding_length) / blocksize; + int maxExtraPadBlocks = (255 - padding_length) / blockSize; int actualExtraPadBlocks = ChooseExtraPadBlocks(context.SecureRandom, maxExtraPadBlocks); - padding_length += actualExtraPadBlocks * blocksize; + padding_length += actualExtraPadBlocks * blockSize; + } + + int totalSize = len + macSize + padding_length + 1; + if (useExplicitIV) + { + totalSize += blockSize; + } + + byte[] outBuf = new byte[totalSize]; + int outOff = 0; + + if (useExplicitIV) + { + byte[] explicitIV = new byte[blockSize]; + context.NonceRandomGenerator.NextBytes(explicitIV); + + encryptCipher.Init(true, new ParametersWithIV(null, explicitIV)); + + Array.Copy(explicitIV, 0, outBuf, outOff, blockSize); + outOff += blockSize; + } + + int blocks_start = outOff; + + Array.Copy(plaintext, offset, outBuf, outOff, len); + outOff += len; + + if (!encryptThenMac) + { + byte[] mac = mWriteMac.CalculateMac(seqNo, type, plaintext, offset, len); + Array.Copy(mac, 0, outBuf, outOff, mac.Length); + outOff += mac.Length; } - int totalsize = len + wMac.Size + padding_length + 1; - byte[] outbuf = new byte[totalsize]; - Array.Copy(plaintext, offset, outbuf, 0, len); - byte[] mac = wMac.CalculateMac(type, plaintext, offset, len); - Array.Copy(mac, 0, outbuf, len, mac.Length); - int paddoffset = len + mac.Length; for (int i = 0; i <= padding_length; i++) { - outbuf[i + paddoffset] = (byte)padding_length; + outBuf[outOff++] = (byte)padding_length; } - for (int i = 0; i < totalsize; i += blocksize) + + for (int i = blocks_start; i < outOff; i += blockSize) + { + encryptCipher.ProcessBlock(outBuf, i, outBuf, i); + } + + if (encryptThenMac) { - encryptCipher.ProcessBlock(outbuf, i, outbuf, i); + byte[] mac = mWriteMac.CalculateMac(seqNo, type, outBuf, 0, outOff); + Array.Copy(mac, 0, outBuf, outOff, mac.Length); + outOff += mac.Length; } - return outbuf; + + // assert outBuf.length == outOff; + + return outBuf; } - public virtual byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len) + /// <exception cref="IOException"></exception> + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) { int blockSize = decryptCipher.GetBlockSize(); - int macSize = rMac.Size; + int macSize = mReadMac.Size; + + int minLen = blockSize; + if (encryptThenMac) + { + minLen += macSize; + } + else + { + minLen = System.Math.Max(minLen, macSize + 1); + } - /* - * TODO[TLS 1.1] Explicit IV implies minLen = blockSize + max(blockSize, macSize + 1), - * and will need further changes to offset and plen variables below. - */ + if (useExplicitIV) + { + minLen += blockSize; + } - int minLen = System.Math.Max(blockSize, macSize + 1); if (len < minLen) throw new TlsFatalAlert(AlertDescription.decode_error); - if (len % blockSize != 0) + int blocks_length = len; + if (encryptThenMac) + { + blocks_length -= macSize; + } + + if (blocks_length % blockSize != 0) throw new TlsFatalAlert(AlertDescription.decryption_failed); - for (int i = 0; i < len; i += blockSize) + if (encryptThenMac) { - decryptCipher.ProcessBlock(ciphertext, offset + i, ciphertext, offset + i); + int end = offset + len; + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, end - macSize, end); + byte[] calculatedMac = mReadMac.CalculateMac(seqNo, type, ciphertext, offset, len - macSize); + + bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); + + if (badMac) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + if (useExplicitIV) + { + decryptCipher.Init(false, new ParametersWithIV(null, ciphertext, offset, blockSize)); + + offset += blockSize; + blocks_length -= blockSize; } - int plen = len; + for (int i = 0; i < blocks_length; i += blockSize) + { + decryptCipher.ProcessBlock(ciphertext, offset + i, ciphertext, offset + i); + } // If there's anything wrong with the padding, this will return zero - int totalPad = CheckPaddingConstantTime(ciphertext, offset, plen, blockSize, macSize); + int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMac ? 0 : macSize); - int macInputLen = plen - totalPad - macSize; + int dec_output_length = blocks_length - totalPad; - byte[] decryptedMac = Arrays.Copy(ciphertext, offset + macInputLen, macSize); - byte[] calculatedMac = rMac.CalculateMacConstantTime(type, ciphertext, offset, macInputLen, plen - macSize, randomData); + if (!encryptThenMac) + { + dec_output_length -= macSize; + int macInputLen = dec_output_length; + int macOff = offset + macInputLen; + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, macOff, macOff + macSize); + byte[] calculatedMac = mReadMac.CalculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, + blocks_length - macSize, randomData); - bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, decryptedMac); + bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); - if (badMac || totalPad == 0) - throw new TlsFatalAlert(AlertDescription.bad_record_mac); + if (badMac || totalPad == 0) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + } - return Arrays.Copy(ciphertext, offset, macInputLen); + return Arrays.CopyOfRange(ciphertext, offset, offset + dec_output_length); } protected virtual int CheckPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize) @@ -185,10 +316,7 @@ namespace Org.BouncyCastle.Crypto.Tls int dummyIndex = 0; byte padDiff = 0; - //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion; - bool isTls = true; - - if ((!isTls && totalPad > blockSize) || (macSize + totalPad > len)) + if ((TlsUtilities.IsSsl(context) && totalPad > blockSize) || (macSize + totalPad > len)) { totalPad = 0; } @@ -225,25 +353,24 @@ namespace Org.BouncyCastle.Crypto.Tls protected virtual int ChooseExtraPadBlocks(SecureRandom r, int max) { -// return r.NextInt(max + 1); + // return r.NextInt(max + 1); - uint x = (uint)r.NextInt(); + int x = r.NextInt(); int n = LowestBitSet(x); return System.Math.Min(n, max); } - private int LowestBitSet(uint x) + protected virtual int LowestBitSet(int x) { if (x == 0) - { return 32; - } + uint ux = (uint)x; int n = 0; - while ((x & 1) == 0) + while ((ux & 1U) == 0) { ++n; - x >>= 1; + ux >>= 1; } return n; } diff --git a/crypto/src/crypto/tls/TlsCipher.cs b/crypto/src/crypto/tls/TlsCipher.cs index a58f4943f..7bd8573ac 100644 --- a/crypto/src/crypto/tls/TlsCipher.cs +++ b/crypto/src/crypto/tls/TlsCipher.cs @@ -5,10 +5,12 @@ namespace Org.BouncyCastle.Crypto.Tls { public interface TlsCipher { + int GetPlaintextLimit(int ciphertextLimit); + /// <exception cref="IOException"></exception> - byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len); + byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len); /// <exception cref="IOException"></exception> - byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len); + byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len); } } diff --git a/crypto/src/crypto/tls/TlsCipherFactory.cs b/crypto/src/crypto/tls/TlsCipherFactory.cs index e5cf96479..4e1fe0eb9 100644 --- a/crypto/src/crypto/tls/TlsCipherFactory.cs +++ b/crypto/src/crypto/tls/TlsCipherFactory.cs @@ -6,7 +6,6 @@ namespace Org.BouncyCastle.Crypto.Tls public interface TlsCipherFactory { /// <exception cref="IOException"></exception> - TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, - DigestAlgorithm digestAlgorithm); + TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm); } } diff --git a/crypto/src/crypto/tls/TlsHandshakeHash.cs b/crypto/src/crypto/tls/TlsHandshakeHash.cs new file mode 100644 index 000000000..7118d9769 --- /dev/null +++ b/crypto/src/crypto/tls/TlsHandshakeHash.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsHandshakeHash + : IDigest + { + void Init(TlsContext context); + + TlsHandshakeHash NotifyPrfDetermined(); + + void TrackHashAlgorithm(byte hashAlgorithm); + + void SealHashAlgorithms(); + + TlsHandshakeHash StopTracking(); + + IDigest ForkPrfHash(); + + byte[] GetFinalHash(byte hashAlgorithm); + } +} diff --git a/crypto/src/crypto/tls/TlsMac.cs b/crypto/src/crypto/tls/TlsMac.cs index e4313617e..a80319a17 100644 --- a/crypto/src/crypto/tls/TlsMac.cs +++ b/crypto/src/crypto/tls/TlsMac.cs @@ -9,134 +9,165 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Tls { - /// <remarks> - /// A generic TLS MAC implementation, which can be used with any kind of - /// IDigest to act as an HMAC. - /// </remarks> + /// <summary> + /// A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest. + /// </summary> public class TlsMac { - protected long seqNo; - protected byte[] secret; - protected HMac mac; + protected readonly TlsContext context; + protected readonly byte[] secret; + protected readonly IMac mac; + protected readonly int digestBlockSize; + protected readonly int digestOverhead; + protected readonly int macLength; /** - * Generate a new instance of an TlsMac. - * - * @param digest The digest to use. - * @param key_block A byte-array where the key for this mac is located. - * @param offset The number of bytes to skip, before the key starts in the buffer. - * @param len The length of the key. - */ - public TlsMac( - IDigest digest, - byte[] key_block, - int offset, - int len) + * 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, IDigest digest, byte[] key, int keyOff, int keyLen) { - this.seqNo = 0; + this.context = context; - KeyParameter param = new KeyParameter(key_block, offset, len); + KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen); - this.secret = Arrays.Clone(param.GetKey()); + this.secret = Arrays.Clone(keyParameter.GetKey()); - this.mac = new HMac(digest); - this.mac.Init(param); - } + // TODO This should check the actual algorithm, not rely on the engine type + if (digest is LongDigest) + { + this.digestBlockSize = 128; + this.digestOverhead = 16; + } + else + { + this.digestBlockSize = 64; + this.digestOverhead = 8; + } - /** - * @return the MAC write secret - */ - public virtual byte[] GetMacSecret() - { - return this.secret; - } + if (TlsUtilities.IsSsl(context)) + { + this.mac = new Ssl3Mac(digest); - /** - * @return the current write sequence number - */ - public virtual long SequenceNumber - { - get { return this.seqNo; } + // 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.SecurityParameters.truncatedHMac) + { + this.macLength = System.Math.Min(this.macLength, 10); + } } /** - * Increment the current write sequence number + * @return the MAC write secret */ - public virtual void IncSequenceNumber() + public virtual byte[] MacSecret { - this.seqNo++; + get { return this.secret; } } /** - * @return The Keysize of the mac. - */ + * @return The output length of this MAC. + */ public virtual int Size { - get { return mac.GetMacSize(); } + get { return macLength; } } /** - * Calculate the mac for some given data. - * <p/> - * TlsMac will keep track of the sequence number internally. - * - * @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 len The length of the message. - * @return A new byte-buffer containing the mac value. - */ - public virtual byte[] CalculateMac(byte type, byte[] message, int offset, int len) + * 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 virtual byte[] CalculateMac(long seqNo, byte type, byte[] message, int offset, int length) { - //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion; - bool isTls = true; + ProtocolVersion serverVersion = context.ServerVersion; + bool isSsl = serverVersion.IsSsl; - byte[] macHeader = new byte[isTls ? 13 : 11]; - TlsUtilities.WriteUint64(seqNo++, macHeader, 0); + byte[] macHeader = new byte[isSsl ? 11 : 13]; + TlsUtilities.WriteUint64(seqNo, macHeader, 0); TlsUtilities.WriteUint8(type, macHeader, 8); - if (isTls) + if (!isSsl) { - TlsUtilities.WriteVersion(macHeader, 9); + TlsUtilities.WriteVersion(serverVersion, macHeader, 9); } - TlsUtilities.WriteUint16(len, macHeader, 11); + TlsUtilities.WriteUint16(length, macHeader, macHeader.Length - 2); mac.BlockUpdate(macHeader, 0, macHeader.Length); - mac.BlockUpdate(message, offset, len); - return MacUtilities.DoFinal(mac); + mac.BlockUpdate(message, offset, length); + + return Truncate(MacUtilities.DoFinal(mac)); } - public virtual byte[] CalculateMacConstantTime(byte type, byte[] message, int offset, int len, + public virtual byte[] CalculateMacConstantTime(long seqNo, byte type, byte[] message, int offset, int length, int fullLength, byte[] dummyData) { - // Actual MAC only calculated on 'len' bytes - byte[] result = CalculateMac(type, message, offset, len); + /* + * 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 = TlsUtilities.IsSsl(context) ? 11 : 13; - //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion; - bool isTls = true; + // How many extra full blocks do we need to calculate? + int extra = GetDigestBlockCount(headerLength + fullLength) - GetDigestBlockCount(headerLength + length); - // ...but ensure a constant number of complete digest blocks are processed (per 'fullLength') - if (isTls) + while (--extra >= 0) { - // TODO Currently all TLS digests use a block size of 64, a suffix (length field) of 8, and padding (1+) - int db = 64, ds = 8; + mac.BlockUpdate(dummyData, 0, digestBlockSize); + } - int L1 = 13 + fullLength; - int L2 = 13 + len; + // One more byte in case the implementation is "lazy" about processing blocks + mac.Update(dummyData[0]); + mac.Reset(); - // How many extra full blocks do we need to calculate? - int extra = ((L1 + ds) / db) - ((L2 + ds) / db); + return result; + } - while (--extra >= 0) - { - mac.BlockUpdate(dummyData, 0, db); - } + protected virtual int GetDigestBlockCount(int inputLength) + { + // NOTE: This calculation assumes a minimum of 1 pad byte + return (inputLength + digestOverhead) / digestBlockSize; + } - // One more byte in case the implementation is "lazy" about processing blocks - mac.Update(dummyData[0]); - mac.Reset(); + protected virtual byte[] Truncate(byte[] bs) + { + if (bs.Length <= macLength) + { + return bs; } - return result; + return Arrays.CopyOf(bs, macLength); } } } diff --git a/crypto/src/crypto/tls/TlsNullCipher.cs b/crypto/src/crypto/tls/TlsNullCipher.cs index 3e2bfa847..f30ace24f 100644 --- a/crypto/src/crypto/tls/TlsNullCipher.cs +++ b/crypto/src/crypto/tls/TlsNullCipher.cs @@ -1,28 +1,118 @@ using System; +using System.IO; + +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Tls { /// <summary> - /// A NULL cipher suite, for use during handshake. + /// A NULL CipherSuite, with optional MAC. /// </summary> public class TlsNullCipher - : TlsCipher + : TlsCipher { - public virtual byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len) + protected readonly TlsContext context; + + protected readonly TlsMac writeMac; + protected readonly TlsMac readMac; + + public TlsNullCipher(TlsContext context) { - return CopyData(plaintext, offset, len); + this.context = context; + this.writeMac = null; + this.readMac = null; } - public virtual byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len) + /// <exception cref="IOException"></exception> + public TlsNullCipher(TlsContext context, IDigest clientWriteDigest, IDigest serverWriteDigest) { - return CopyData(ciphertext, offset, len); + if ((clientWriteDigest == null) != (serverWriteDigest == null)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.context = context; + + TlsMac clientWriteMac = null, serverWriteMac = null; + + if (clientWriteDigest != null) + { + int key_block_size = clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + + serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + if (context.IsServer) + { + writeMac = serverWriteMac; + readMac = clientWriteMac; + } + else + { + writeMac = clientWriteMac; + readMac = serverWriteMac; + } } - protected virtual byte[] CopyData(byte[] text, int offset, int len) + public virtual int GetPlaintextLimit(int ciphertextLimit) { - byte[] result = new byte[len]; - Array.Copy(text, offset, result, 0, len); + int result = ciphertextLimit; + if (writeMac != null) + { + result -= writeMac.Size; + } return result; } + + /// <exception cref="IOException"></exception> + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + if (writeMac == null) + { + return Arrays.CopyOfRange(plaintext, offset, offset + len); + } + + byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len); + byte[] ciphertext = new byte[len + mac.Length]; + Array.Copy(plaintext, offset, ciphertext, 0, len); + Array.Copy(mac, 0, ciphertext, len, mac.Length); + return ciphertext; + } + + /// <exception cref="IOException"></exception> + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (readMac == null) + { + return Arrays.CopyOfRange(ciphertext, offset, offset + len); + } + + int macSize = readMac.Size; + if (len < macSize) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int macInputLen = len - macSize; + + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, offset + macInputLen, offset + len); + byte[] computedMac = readMac.CalculateMac(seqNo, type, ciphertext, offset, macInputLen); + + if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + + return Arrays.CopyOfRange(ciphertext, offset, offset + macInputLen); + } } } diff --git a/crypto/src/crypto/tls/TlsProtocolHandler.cs b/crypto/src/crypto/tls/TlsProtocolHandler.cs index 918640db5..3dec5ade0 100644 --- a/crypto/src/crypto/tls/TlsProtocolHandler.cs +++ b/crypto/src/crypto/tls/TlsProtocolHandler.cs @@ -54,7 +54,7 @@ namespace Org.BouncyCastle.Crypto.Tls /* * The Record Stream we use */ - private RecordStream rs; + private RecordStream recordStream; private SecureRandom random; private TlsStream tlsStream = null; @@ -116,7 +116,7 @@ namespace Org.BouncyCastle.Crypto.Tls Stream outStr, SecureRandom sr) { - this.rs = new RecordStream(this, inStr, outStr); + this.recordStream = new RecordStream(this, inStr, outStr); this.random = sr; } @@ -202,8 +202,8 @@ namespace Org.BouncyCastle.Crypto.Tls case HandshakeType.finished: break; default: - rs.UpdateHandshakeData(beginning, 0, 4); - rs.UpdateHandshakeData(buf, 0, len); + recordStream.UpdateHandshakeData(beginning, 0, 4); + recordStream.UpdateHandshakeData(buf, 0, len); break; } @@ -270,7 +270,7 @@ namespace Org.BouncyCastle.Crypto.Tls * Calculate our own checksum. */ byte[] expectedServerVerifyData = TlsUtilities.PRF(tlsClientContext, - securityParameters.masterSecret, ExporterLabel.server_finished, rs.GetCurrentHash(), 12); + securityParameters.masterSecret, ExporterLabel.server_finished, recordStream.GetCurrentHash(), 12); /* * Compare both checksums. @@ -445,6 +445,9 @@ namespace Org.BouncyCastle.Crypto.Tls tlsClient.NotifySecureRenegotiation(secure_negotiation); } + this.securityParameters.cipherSuite = selectedCipherSuite; + this.securityParameters.compressionAlgorithm = selectedCompressionMethod; + if (clientExtensions != null) { tlsClient.ProcessServerExtensions(serverExtensions); @@ -457,6 +460,13 @@ namespace Org.BouncyCastle.Crypto.Tls // TODO Just a place-holder until other TLS 1.2 changes arrive this.securityParameters.prfAlgorithm = PrfAlgorithm.tls_prf_legacy; + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify + * verify_data_length has a verify_data_length equal to 12. This includes all + * existing cipher suites. + */ + this.securityParameters.verifyDataLength = 12; + break; default: this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); @@ -527,7 +537,7 @@ namespace Org.BouncyCastle.Crypto.Tls if (clientCreds != null && clientCreds is TlsSignerCredentials) { TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds; - byte[] md5andsha1 = rs.GetCurrentHash(); + byte[] md5andsha1 = recordStream.GetCurrentHash(); byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature( md5andsha1); SendCertificateVerify(clientCertificateSignature); @@ -540,7 +550,7 @@ namespace Org.BouncyCastle.Crypto.Tls */ byte[] cmessage = new byte[1]; cmessage[0] = 1; - rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length); + recordStream.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length); connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; @@ -562,20 +572,20 @@ namespace Org.BouncyCastle.Crypto.Tls /* * Initialize our cipher suite */ - rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher()); + recordStream.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher()); /* * Send our finished message. */ byte[] clientVerifyData = TlsUtilities.PRF(tlsClientContext, securityParameters.masterSecret, - ExporterLabel.client_finished, rs.GetCurrentHash(), 12); + ExporterLabel.client_finished, recordStream.GetCurrentHash(), 12); MemoryStream bos = new MemoryStream(); TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos); TlsUtilities.WriteOpaque24(clientVerifyData, bos); byte[] message = bos.ToArray(); - rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length); this.connection_state = CS_CLIENT_FINISHED_SEND; break; @@ -703,7 +713,7 @@ namespace Org.BouncyCastle.Crypto.Tls */ try { - rs.Close(); + recordStream.Close(); } catch (Exception) { @@ -747,7 +757,7 @@ namespace Org.BouncyCastle.Crypto.Tls this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); } - rs.ServerClientSpecReceived(); + recordStream.ServerClientSpecReceived(); this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED; } @@ -767,7 +777,7 @@ namespace Org.BouncyCastle.Crypto.Tls // Patch actual length back in TlsUtilities.WriteUint24(message.Length - 4, message, 1); - rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length); } private void SendClientKeyExchange() @@ -784,7 +794,7 @@ namespace Org.BouncyCastle.Crypto.Tls // Patch actual length back in TlsUtilities.WriteUint24(message.Length - 4, message, 1); - rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length); } private void SendCertificateVerify(byte[] data) @@ -799,7 +809,7 @@ namespace Org.BouncyCastle.Crypto.Tls TlsUtilities.WriteOpaque16(data, bos); byte[] message = bos.ToArray(); - rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length); } /// <summary>Connects to the remote system.</summary> @@ -823,6 +833,7 @@ namespace Org.BouncyCastle.Crypto.Tls this.tlsClient = tlsClient; this.securityParameters = new SecurityParameters(); + this.securityParameters.entity = ConnectionEnd.client; this.tlsClientContext = new TlsClientContextImpl(random, securityParameters); @@ -830,6 +841,7 @@ namespace Org.BouncyCastle.Crypto.Tls tlsClientContext.NonceRandomGenerator); this.tlsClient.Init(tlsClientContext); + this.recordStream.Init(tlsClientContext); /* * Send Client hello @@ -965,7 +977,7 @@ namespace Org.BouncyCastle.Crypto.Tls { try { - rs.ReadData(); + recordStream.ReadData(); } catch (TlsFatalAlert e) { @@ -997,7 +1009,7 @@ namespace Org.BouncyCastle.Crypto.Tls { try { - rs.WriteMessage(type, buf, offset, len); + recordStream.WriteMessage(type, buf, offset, len); } catch (TlsFatalAlert e) { @@ -1127,7 +1139,7 @@ namespace Org.BouncyCastle.Crypto.Tls this.failedWithError = true; } SendAlert(alertLevel, alertDescription); - rs.Close(); + recordStream.Close(); if (alertLevel == AlertLevel.fatal) { throw new IOException(TLS_ERROR_MESSAGE); @@ -1143,7 +1155,7 @@ namespace Org.BouncyCastle.Crypto.Tls { byte[] error = new byte[] { alertLevel, alertDescription }; - rs.WriteMessage(ContentType.alert, error, 0, 2); + recordStream.WriteMessage(ContentType.alert, error, 0, 2); } /// <summary>Closes this connection</summary> @@ -1180,7 +1192,7 @@ namespace Org.BouncyCastle.Crypto.Tls internal void Flush() { - rs.Flush(); + recordStream.Flush(); } internal bool IsClosed diff --git a/crypto/src/crypto/tls/TlsStreamCipher.cs b/crypto/src/crypto/tls/TlsStreamCipher.cs index 24e2ce73f..d664b09dd 100644 --- a/crypto/src/crypto/tls/TlsStreamCipher.cs +++ b/crypto/src/crypto/tls/TlsStreamCipher.cs @@ -1,108 +1,173 @@ using System; +using System.IO; -using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Tls; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Tls { - public class TlsStreamCipher : TlsCipher + public class TlsStreamCipher + : TlsCipher { - protected TlsContext context; + protected readonly TlsContext context; - protected IStreamCipher encryptCipher; - protected IStreamCipher decryptCipher; + protected readonly IStreamCipher encryptCipher; + protected readonly IStreamCipher decryptCipher; - protected TlsMac writeMac; - protected TlsMac readMac; + protected readonly TlsMac writeMac; + protected readonly TlsMac readMac; - public TlsStreamCipher(TlsContext context, IStreamCipher encryptCipher, - IStreamCipher decryptCipher, IDigest writeDigest, IDigest readDigest, int cipherKeySize) + protected readonly bool usesNonce; + + /// <exception cref="IOException"></exception> + [Obsolete("Use version with additional 'usesNonce' argument")] + public TlsStreamCipher(TlsContext context, IStreamCipher clientWriteCipher, + IStreamCipher serverWriteCipher, IDigest clientWriteDigest, IDigest serverWriteDigest, + int cipherKeySize) + : this(context, clientWriteCipher, serverWriteCipher, clientWriteDigest, serverWriteDigest, cipherKeySize, false) { + } + + /// <exception cref="IOException"></exception> + public TlsStreamCipher(TlsContext context, IStreamCipher clientWriteCipher, + IStreamCipher serverWriteCipher, IDigest clientWriteDigest, IDigest serverWriteDigest, + int cipherKeySize, bool usesNonce) + { + bool isServer = context.IsServer; + this.context = context; - this.encryptCipher = encryptCipher; - this.decryptCipher = decryptCipher; + this.usesNonce = usesNonce; - int prfSize = (2 * cipherKeySize) + writeDigest.GetDigestSize() - + readDigest.GetDigestSize(); + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; - SecurityParameters securityParameters = context.SecurityParameters; + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); - byte[] keyBlock = TlsUtilities.PRF(context, securityParameters.masterSecret, ExporterLabel.key_expansion, - TlsUtilities.Concat(securityParameters.serverRandom, securityParameters.clientRandom), - prfSize); + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); int offset = 0; // Init MACs - writeMac = CreateTlsMac(writeDigest, keyBlock, ref offset); - readMac = CreateTlsMac(readDigest, keyBlock, ref offset); + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); // Build keys - KeyParameter encryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); - KeyParameter decryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); + KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; - if (offset != prfSize) + if (offset != key_block_size) + { throw new TlsFatalAlert(AlertDescription.internal_error); + } - // Init Ciphers - encryptCipher.Init(true, encryptKey); - decryptCipher.Init(false, decryptKey); - } - - public byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len) - { - byte[] mac = writeMac.CalculateMac(type, plaintext, offset, len); - int size = len + mac.Length; + ICipherParameters encryptParams, decryptParams; + if (isServer) + { + this.writeMac = serverWriteMac; + this.readMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = serverWriteKey; + decryptParams = clientWriteKey; + } + else + { + this.writeMac = clientWriteMac; + this.readMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = clientWriteKey; + decryptParams = serverWriteKey; + } - byte[] outbuf = new byte[size]; + if (usesNonce) + { + byte[] dummyNonce = new byte[8]; + encryptParams = new ParametersWithIV(encryptParams, dummyNonce); + decryptParams = new ParametersWithIV(decryptParams, dummyNonce); + } - encryptCipher.ProcessBytes(plaintext, offset, len, outbuf, 0); - encryptCipher.ProcessBytes(mac, 0, mac.Length, outbuf, len); + this.encryptCipher.Init(true, encryptParams); + this.decryptCipher.Init(false, decryptParams); + } - return outbuf; + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + return ciphertextLimit - writeMac.Size; } - public byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len) + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) { - byte[] deciphered = new byte[len]; - decryptCipher.ProcessBytes(ciphertext, offset, len, deciphered, 0); + /* + * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That + * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS + * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation + * of the 16-bit epoch with the 48-bit sequence number. + */ + if (usesNonce) + { + UpdateIV(encryptCipher, true, seqNo); + } - int plaintextSize = deciphered.Length - readMac.Size; - byte[] plainText = CopyData(deciphered, 0, plaintextSize); + byte[] outBuf = new byte[len + writeMac.Size]; - byte[] receivedMac = CopyData(deciphered, plaintextSize, readMac.Size); - byte[] computedMac = readMac.CalculateMac(type, plainText, 0, plainText.Length); + encryptCipher.ProcessBytes(plaintext, offset, len, outBuf, 0); - if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac)) - { - throw new TlsFatalAlert(AlertDescription.bad_record_mac); - } + byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len); + encryptCipher.ProcessBytes(mac, 0, mac.Length, outBuf, len); - return plainText; + return outBuf; } - protected virtual TlsMac CreateTlsMac(IDigest digest, byte[] buf, ref int off) + /// <exception cref="IOException"></exception> + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) { - int len = digest.GetDigestSize(); - TlsMac mac = new TlsMac(digest, buf, off, len); - off += len; - return mac; + /* + * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That + * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS + * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation + * of the 16-bit epoch with the 48-bit sequence number. + */ + if (usesNonce) + { + UpdateIV(decryptCipher, false, seqNo); + } + + int macSize = readMac.Size; + if (len < macSize) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int plaintextLength = len - macSize; + + byte[] deciphered = new byte[len]; + decryptCipher.ProcessBytes(ciphertext, offset, len, deciphered, 0); + CheckMac(seqNo, type, deciphered, plaintextLength, len, deciphered, 0, plaintextLength); + return Arrays.CopyOfRange(deciphered, 0, plaintextLength); } - protected virtual KeyParameter CreateKeyParameter(byte[] buf, ref int off, int len) + /// <exception cref="IOException"></exception> + protected virtual void CheckMac(long seqNo, byte type, byte[] recBuf, int recStart, int recEnd, byte[] calcBuf, int calcOff, int calcLen) { - KeyParameter key = new KeyParameter(buf, off, len); - off += len; - return key; + byte[] receivedMac = Arrays.CopyOfRange(recBuf, recStart, recEnd); + byte[] computedMac = readMac.CalculateMac(seqNo, type, calcBuf, calcOff, calcLen); + + if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); } - protected virtual byte[] CopyData(byte[] text, int offset, int len) + protected virtual void UpdateIV(IStreamCipher cipher, bool forEncryption, long seqNo) { - byte[] result = new byte[len]; - Array.Copy(text, offset, result, 0, len); - return result; + byte[] nonce = new byte[8]; + TlsUtilities.WriteUint64(seqNo, nonce, 0); + cipher.Init(forEncryption, new ParametersWithIV(null, nonce)); } } } diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs index 462ec4074..bd5362e5b 100644 --- a/crypto/src/crypto/tls/TlsUtilities.cs +++ b/crypto/src/crypto/tls/TlsUtilities.cs @@ -10,6 +10,7 @@ using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Macs; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Date; using Org.BouncyCastle.Utilities.IO; @@ -759,6 +760,105 @@ namespace Org.BouncyCastle.Crypto.Tls } } + internal static byte[] CalculateKeyBlock(TlsContext context, int size) + { + SecurityParameters securityParameters = context.SecurityParameters; + byte[] master_secret = securityParameters.MasterSecret; + byte[] seed = Concat(securityParameters.ServerRandom, securityParameters.ClientRandom); + + if (IsSsl(context)) + { + return CalculateKeyBlock_Ssl(master_secret, seed, size); + } + + return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size); + } + + internal static byte[] CalculateKeyBlock_Ssl(byte[] master_secret, byte[] random, int size) + { + IDigest md5 = CreateHash(HashAlgorithm.md5); + IDigest sha1 = CreateHash(HashAlgorithm.sha1); + int md5Size = md5.GetDigestSize(); + byte[] shatmp = new byte[sha1.GetDigestSize()]; + byte[] tmp = new byte[size + md5Size]; + + int i = 0, pos = 0; + while (pos < size) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length); + sha1.BlockUpdate(master_secret, 0, master_secret.Length); + sha1.BlockUpdate(random, 0, random.Length); + sha1.DoFinal(shatmp, 0); + + md5.BlockUpdate(master_secret, 0, master_secret.Length); + md5.BlockUpdate(shatmp, 0, shatmp.Length); + md5.DoFinal(tmp, pos); + + pos += md5Size; + ++i; + } + + return Arrays.CopyOfRange(tmp, 0, size); + } + + internal static byte[] CalculateMasterSecret(TlsContext context, byte[] pre_master_secret) + { + SecurityParameters securityParameters = context.SecurityParameters; + byte[] seed = Concat(securityParameters.ClientRandom, securityParameters.ServerRandom); + + if (IsSsl(context)) + { + return CalculateMasterSecret_Ssl(pre_master_secret, seed); + } + + return PRF(context, pre_master_secret, ExporterLabel.master_secret, seed, 48); + } + + internal static byte[] CalculateMasterSecret_Ssl(byte[] pre_master_secret, byte[] random) + { + IDigest md5 = CreateHash(HashAlgorithm.md5); + IDigest sha1 = CreateHash(HashAlgorithm.sha1); + int md5Size = md5.GetDigestSize(); + byte[] shatmp = new byte[sha1.GetDigestSize()]; + + byte[] rval = new byte[md5Size * 3]; + int pos = 0; + + for (int i = 0; i < 3; ++i) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length); + sha1.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length); + sha1.BlockUpdate(random, 0, random.Length); + sha1.DoFinal(shatmp, 0); + + md5.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length); + md5.BlockUpdate(shatmp, 0, shatmp.Length); + md5.DoFinal(rval, pos); + + pos += md5Size; + } + + return rval; + } + + internal static byte[] CalculateVerifyData(TlsContext context, string asciiLabel, byte[] handshakeHash) + { + if (IsSsl(context)) + { + return handshakeHash; + } + + SecurityParameters securityParameters = context.SecurityParameters; + byte[] master_secret = securityParameters.MasterSecret; + int verify_data_length = securityParameters.VerifyDataLength; + + return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length); + } + public static IDigest CreateHash(byte hashAlgorithm) { switch (hashAlgorithm) @@ -859,6 +959,130 @@ namespace Org.BouncyCastle.Crypto.Tls } } + internal static short GetClientCertificateType(Certificate clientCertificate, Certificate serverCertificate) + { + if (clientCertificate.IsEmpty) + return -1; + + X509CertificateStructure x509Cert = clientCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + if (publicKey.IsPrivate) + throw new TlsFatalAlert(AlertDescription.internal_error); + + /* + * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/ + * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the + * constraints on certificate-signing algorithms found in prior versions of TLS. + */ + + /* + * RFC 5246 7.4.6. Client Certificate + */ + + /* + * RSA public key; the certificate MUST allow the key to be used for signing with the + * signature scheme and hash algorithm that will be employed in the certificate verify + * message. + */ + if (publicKey is RsaKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + return ClientCertificateType.rsa_sign; + } + + /* + * DSA public key; the certificate MUST allow the key to be used for signing with the + * hash algorithm that will be employed in the certificate verify message. + */ + if (publicKey is DsaPublicKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + return ClientCertificateType.dss_sign; + } + + /* + * ECDSA-capable public key; the certificate MUST allow the key to be used for signing + * with the hash algorithm that will be employed in the certificate verify message; the + * public key MUST use a curve and point format supported by the server. + */ + if (publicKey is ECPublicKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + // TODO Check the curve and point format + return ClientCertificateType.ecdsa_sign; + } + + // TODO Add support for ClientCertificateType.*_fixed_* + } + catch (Exception) + { + } + + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + internal static void TrackHashAlgorithms(TlsHandshakeHash handshakeHash, IList supportedSignatureAlgorithms) + { + if (supportedSignatureAlgorithms != null) + { + foreach (SignatureAndHashAlgorithm signatureAndHashAlgorithm in supportedSignatureAlgorithms) + { + byte hashAlgorithm = signatureAndHashAlgorithm.Hash; + handshakeHash.TrackHashAlgorithm(hashAlgorithm); + } + } + } + + public static bool HasSigningCapability(byte clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_sign: + return true; + default: + return false; + } + } + + public static TlsSigner CreateTlsSigner(byte clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + return new TlsDssSigner(); + case ClientCertificateType.ecdsa_sign: + return new TlsECDsaSigner(); + case ClientCertificateType.rsa_sign: + return new TlsRsaSigner(); + default: + throw new ArgumentException("not a type with signing capability", "clientCertificateType"); + } + } + + internal static readonly byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54}; + internal static readonly byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52}; + + // SSL3 magic mix constants ("A", "BB", "CCC", ...) + internal static readonly byte[][] SSL3_CONST = GenSsl3Const(); + + private static byte[][] GenSsl3Const() + { + int n = 10; + byte[][] arr = new byte[n][]; + for (int i = 0; i < n; i++) + { + byte[] b = new byte[i + 1]; + Arrays.Fill(b, (byte)('A' + i)); + arr[i] = b; + } + return arr; + } + private static IList VectorOfOne(object obj) { IList v = Platform.CreateArrayList(1); diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs index e629fcf65..87bc1ba65 100644 --- a/crypto/src/util/Arrays.cs +++ b/crypto/src/util/Arrays.cs @@ -1,6 +1,8 @@ using System; using System.Text; +using Org.BouncyCastle.Math; + namespace Org.BouncyCastle.Utilities { /// <summary> General array utilities.</summary> @@ -407,11 +409,90 @@ namespace Org.BouncyCastle.Utilities } } - public static byte[] Copy(byte[] data, int off, int len) + public static byte[] CopyOf(byte[] data, int newLength) { - byte[] result = new byte[len]; - Array.Copy(data, off, result, 0, len); - return result; + byte[] tmp = new byte[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static char[] CopyOf(char[] data, int newLength) + { + char[] tmp = new char[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static int[] CopyOf(int[] data, int newLength) + { + int[] tmp = new int[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static long[] CopyOf(long[] data, int newLength) + { + long[] tmp = new long[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static BigInteger[] CopyOf(BigInteger[] data, int newLength) + { + BigInteger[] tmp = new BigInteger[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + /** + * Make a copy of a range of bytes from the passed in data array. The range can + * extend beyond the end of the input array, in which case the return array will + * be padded with zeroes. + * + * @param data the array from which the data is to be copied. + * @param from the start index at which the copying should take place. + * @param to the final index of the range (exclusive). + * + * @return a new byte array containing the range given. + */ + public static byte[] CopyOfRange(byte[] data, int from, int to) + { + int newLength = GetLength(from, to); + byte[] tmp = new byte[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static int[] CopyOfRange(int[] data, int from, int to) + { + int newLength = GetLength(from, to); + int[] tmp = new int[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static long[] CopyOfRange(long[] data, int from, int to) + { + int newLength = GetLength(from, to); + long[] tmp = new long[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static BigInteger[] CopyOfRange(BigInteger[] data, int from, int to) + { + int newLength = GetLength(from, to); + BigInteger[] tmp = new BigInteger[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + private static int GetLength(int from, int to) + { + int newLength = to - from; + if (newLength < 0) + throw new ArgumentException(from + " > " + to); + return newLength; } public static byte[] Append(byte[] a, byte b) diff --git a/crypto/test/src/crypto/test/OCBTest.cs b/crypto/test/src/crypto/test/OCBTest.cs index f2476f6d5..9f898fbe2 100644 --- a/crypto/test/src/crypto/test/OCBTest.cs +++ b/crypto/test/src/crypto/test/OCBTest.cs @@ -211,7 +211,7 @@ namespace Org.BouncyCastle.Crypto.Tests private void CheckTestCase(IAeadBlockCipher encCipher, IAeadBlockCipher decCipher, string testName, int macLengthBytes, byte[] P, byte[] C) { - byte[] tag = Arrays.Copy(C, C.Length - macLengthBytes, macLengthBytes); + byte[] tag = Arrays.CopyOfRange(C, C.Length - macLengthBytes, C.Length); { byte[] enc = new byte[encCipher.GetOutputSize(P.Length)]; |