diff options
Diffstat (limited to 'bcprov/src/main/java/org/bouncycastle/crypto')
210 files changed, 12677 insertions, 2860 deletions
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java index bdb694d..dd056ac 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/BufferedBlockCipher.java @@ -54,7 +54,7 @@ public class BufferedBlockCipher } else { - partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", idx))); + partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("GCFB", idx) ||name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", idx))); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/DerivationFunction.java b/bcprov/src/main/java/org/bouncycastle/crypto/DerivationFunction.java index ef6e29e..0e2b4b0 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/DerivationFunction.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/DerivationFunction.java @@ -7,11 +7,6 @@ public interface DerivationFunction { public void init(DerivationParameters param); - /** - * return the message digest used as the basis for the function - */ - public Digest getDigest(); - public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException, IllegalArgumentException; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/DigestDerivationFunction.java b/bcprov/src/main/java/org/bouncycastle/crypto/DigestDerivationFunction.java new file mode 100644 index 0000000..180382d --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/DigestDerivationFunction.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto; + +/** + * base interface for general purpose Digest based byte derivation functions. + */ +public interface DigestDerivationFunction + extends DerivationFunction +{ + /** + * return the message digest used as the basis for the function + */ + public Digest getDigest(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/MacDerivationFunction.java b/bcprov/src/main/java/org/bouncycastle/crypto/MacDerivationFunction.java new file mode 100644 index 0000000..16198ba --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/MacDerivationFunction.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto; + +/** + * base interface for general purpose Mac based byte derivation functions. + */ +public interface MacDerivationFunction + extends DerivationFunction +{ + /** + * return the MAC used as the basis for the function + */ + public Mac getMac(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java index 59944e0..a491b9d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java @@ -42,10 +42,13 @@ public class ECDHBasicAgreement CipherParameters pubKey) { ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; - ECPoint P = pub.getQ().multiply(key.getD()); + ECPoint P = pub.getQ().multiply(key.getD()).normalize(); - // if (p.isInfinity()) throw new RuntimeException("d*Q == infinity"); + if (P.isInfinity()) + { + throw new IllegalStateException("Infinity is not a valid agreement value for ECDH"); + } - return P.getX().toBigInteger(); + return P.getAffineXCoord().toBigInteger(); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java index 12b8405..28140df 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java @@ -47,12 +47,18 @@ public class ECDHCBasicAgreement public BigInteger calculateAgreement( CipherParameters pubKey) { - ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; - ECDomainParameters params = pub.getParameters(); - ECPoint P = pub.getQ().multiply(params.getH().multiply(key.getD())); + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; + ECDomainParameters params = pub.getParameters(); - // if (p.isInfinity()) throw new RuntimeException("Invalid public key"); + BigInteger hd = params.getH().multiply(key.getD()).mod(params.getN()); - return P.getX().toBigInteger(); + ECPoint P = pub.getQ().multiply(hd).normalize(); + + if (P.isInfinity()) + { + throw new IllegalStateException("Infinity is not a valid agreement value for ECDHC"); + } + + return P.getAffineXCoord().toBigInteger(); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java index da88b4a..794f555 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java @@ -11,6 +11,7 @@ import org.bouncycastle.crypto.params.MQVPrivateParameters; import org.bouncycastle.crypto.params.MQVPublicParameters; import org.bouncycastle.math.ec.ECAlgorithms; import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; public class ECMQVBasicAgreement @@ -37,9 +38,14 @@ public class ECMQVBasicAgreement ECPoint agreement = calculateMqvAgreement(staticPrivateKey.getParameters(), staticPrivateKey, privParams.getEphemeralPrivateKey(), privParams.getEphemeralPublicKey(), - pubParams.getStaticPublicKey(), pubParams.getEphemeralPublicKey()); + pubParams.getStaticPublicKey(), pubParams.getEphemeralPublicKey()).normalize(); - return agreement.getX().toBigInteger(); + if (agreement.isInfinity()) + { + throw new IllegalStateException("Infinity is not a valid agreement value for MQV"); + } + + return agreement.getAffineXCoord().toBigInteger(); } // The ECMQV Primitive as described in SEC-1, 3.4 @@ -55,37 +61,31 @@ public class ECMQVBasicAgreement int e = (n.bitLength() + 1) / 2; BigInteger powE = ECConstants.ONE.shiftLeft(e); - // The Q2U public key is optional - ECPoint q; - if (Q2U == null) - { - q = parameters.getG().multiply(d2U.getD()); - } - else - { - q = Q2U.getQ(); - } + ECCurve curve = parameters.getCurve(); - BigInteger x = q.getX().toBigInteger(); + ECPoint[] points = new ECPoint[]{ + // The Q2U public key is optional + ECAlgorithms.importPoint(curve, Q2U == null ? parameters.getG().multiply(d2U.getD()) : Q2U.getQ()), + ECAlgorithms.importPoint(curve, Q1V.getQ()), + ECAlgorithms.importPoint(curve, Q2V.getQ()) + }; + + curve.normalizeAll(points); + + ECPoint q2u = points[0], q1v = points[1], q2v = points[2]; + + BigInteger x = q2u.getAffineXCoord().toBigInteger(); BigInteger xBar = x.mod(powE); BigInteger Q2UBar = xBar.setBit(e); - BigInteger s = d1U.getD().multiply(Q2UBar).mod(n).add(d2U.getD()).mod(n); + BigInteger s = d1U.getD().multiply(Q2UBar).add(d2U.getD()).mod(n); - BigInteger xPrime = Q2V.getQ().getX().toBigInteger(); + BigInteger xPrime = q2v.getAffineXCoord().toBigInteger(); BigInteger xPrimeBar = xPrime.mod(powE); BigInteger Q2VBar = xPrimeBar.setBit(e); BigInteger hs = parameters.getH().multiply(s).mod(n); -// ECPoint p = Q1V.getQ().multiply(Q2VBar).add(Q2V.getQ()).multiply(hs); - ECPoint p = ECAlgorithms.sumOfTwoMultiplies( - Q1V.getQ(), Q2VBar.multiply(hs).mod(n), Q2V.getQ(), hs); - - if (p.isInfinity()) - { - throw new IllegalStateException("Infinity is not a valid agreement value for MQV"); - } - - return p; + return ECAlgorithms.sumOfTwoMultiplies( + q1v, Q2VBar.multiply(hs).mod(n), q2v, hs); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/package.html deleted file mode 100644 index db47144..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Password Authenticated Key Exchange by Juggling (J-PAKE). -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java index 6803953..500b1dd 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java @@ -11,9 +11,9 @@ import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.crypto.DataLengthException; -import org.bouncycastle.crypto.DerivationFunction; import org.bouncycastle.crypto.DerivationParameters; import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.DigestDerivationFunction; import org.bouncycastle.crypto.generators.KDF2BytesGenerator; import org.bouncycastle.crypto.params.KDFParameters; import org.bouncycastle.crypto.util.Pack; @@ -22,9 +22,9 @@ import org.bouncycastle.crypto.util.Pack; * X9.63 based key derivation function for ECDH CMS. */ public class ECDHKEKGenerator - implements DerivationFunction + implements DigestDerivationFunction { - private DerivationFunction kdf; + private DigestDerivationFunction kdf; private ASN1ObjectIdentifier algorithm; private int keySize; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/package.html deleted file mode 100644 index 4b49331..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Basic key agreement classes. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/commitments/GeneralHashCommitter.java b/bcprov/src/main/java/org/bouncycastle/crypto/commitments/GeneralHashCommitter.java new file mode 100644 index 0000000..3969fe8 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/commitments/GeneralHashCommitter.java @@ -0,0 +1,93 @@ +package org.bouncycastle.crypto.commitments; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Commitment; +import org.bouncycastle.crypto.Committer; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + +/** + * A basic hash-committer based on the one described in "Making Mix Nets Robust for Electronic Voting by Randomized Partial Checking", + * by Jakobsson, Juels, and Rivest (11th Usenix Security Symposium, 2002). + * <p> + * The algorithm used by this class differs from the one given in that it includes the length of the message in the hash calculation. + * </p> + */ +public class GeneralHashCommitter + implements Committer +{ + private final Digest digest; + private final int byteLength; + private final SecureRandom random; + + /** + * Base Constructor. The maximum message length that can be committed to is half the length of the internal + * block size for the digest (ExtendedDigest.getBlockLength()). + * + * @param digest digest to use for creating commitments. + * @param random source of randomness for generating secrets. + */ + public GeneralHashCommitter(ExtendedDigest digest, SecureRandom random) + { + this.digest = digest; + this.byteLength = digest.getByteLength(); + this.random = random; + } + + /** + * Generate a commitment for the passed in message. + * + * @param message the message to be committed to, + * @return a Commitment + */ + public Commitment commit(byte[] message) + { + if (message.length > byteLength / 2) + { + throw new DataLengthException("Message to be committed to too large for digest."); + } + + byte[] w = new byte[byteLength - message.length]; + + random.nextBytes(w); + + return new Commitment(w, calculateCommitment(w, message)); + } + + /** + * Return true if the passed in commitment represents a commitment to the passed in message. + * + * @param commitment a commitment previously generated. + * @param message the message that was expected to have been committed to. + * @return true if commitment matches message, false otherwise. + */ + public boolean isRevealed(Commitment commitment, byte[] message) + { + if (message.length + commitment.getSecret().length != byteLength) + { + throw new DataLengthException("Message and witness secret lengths do not match."); + } + + byte[] calcCommitment = calculateCommitment(commitment.getSecret(), message); + + return Arrays.constantTimeAreEqual(commitment.getCommitment(), calcCommitment); + } + + private byte[] calculateCommitment(byte[] w, byte[] message) + { + byte[] commitment = new byte[digest.getDigestSize()]; + + digest.update(w, 0, w.length); + digest.update(message, 0, message.length); + + digest.update((byte)((message.length >>> 8))); + digest.update((byte)(message.length)); + + digest.doFinal(commitment, 0); + + return commitment; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java b/bcprov/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java index 1494c3c..b5860b5 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java @@ -12,6 +12,9 @@ import org.bouncycastle.util.Arrays; /** * A basic hash-committer as described in "Making Mix Nets Robust for Electronic Voting by Randomized Partial Checking", * by Jakobsson, Juels, and Rivest (11th Usenix Security Symposium, 2002). + * <p> + * Use this class if you can enforce fixed length for messages. If you need something more general, use the GeneralHashCommitter. + * </p> */ public class HashCommitter implements Committer @@ -55,7 +58,7 @@ public class HashCommitter } /** - * Return true if the passed in commitment represents a commitment to the passed in maessage. + * Return true if the passed in commitment represents a commitment to the passed in message. * * @param commitment a commitment previously generated. * @param message the message that was expected to have been committed to. @@ -63,6 +66,11 @@ public class HashCommitter */ public boolean isRevealed(Commitment commitment, byte[] message) { + if (message.length + commitment.getSecret().length != byteLength) + { + throw new DataLengthException("Message and witness secret lengths do not match."); + } + byte[] calcCommitment = calculateCommitment(commitment.getSecret(), message); return Arrays.constantTimeAreEqual(commitment.getCommitment(), calcCommitment); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/commitments/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/commitments/package.html deleted file mode 100644 index 302cc60..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/commitments/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Commitment algorithms. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java new file mode 100644 index 0000000..55e579e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java @@ -0,0 +1,333 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.util.Pack; +import org.bouncycastle.util.Memoable; + +/** + * Implementation of Chinese SM3 digest as described at + * http://tools.ietf.org/html/draft-shen-sm3-hash-00 + * and at .... ( Chinese PDF ) + * <p/> + * The specification says "process a bit stream", + * but this is written to process bytes in blocks of 4, + * meaning this will process 32-bit word groups. + * But so do also most other digest specifications, + * including the SHA-256 which was a origin for + * this specification. + */ +public class SM3Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 32; // bytes + private static final int BLOCK_SIZE = 64 / 4; // of 32 bit ints (16 ints) + + private int[] V = new int[DIGEST_LENGTH / 4]; // in 32 bit ints (8 ints) + private int[] inwords = new int[BLOCK_SIZE]; + private int xOff; + + // Work-bufs used within processBlock() + private int[] W = new int[68]; + private int[] W1 = new int[64]; + + // Round constant T for processBlock() which is 32 bit integer rolled left up to (63 MOD 32) bit positions. + private static final int[] T = new int[64]; + + static + { + for (int i = 0; i < 16; ++i) + { + int t = 0x79CC4519; + T[i] = (t << i) | (t >>> (32 - i)); + } + for (int i = 16; i < 64; ++i) + { + int n = i % 32; + int t = 0x7A879D8A; + T[i] = (t << n) | (t >>> (32 - n)); + } + } + + + /** + * Standard constructor + */ + public SM3Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SM3Digest(SM3Digest t) + { + super(t); + + copyIn(t); + } + + private void copyIn(SM3Digest t) + { + System.arraycopy(t.V, 0, this.V, 0, this.V.length); + System.arraycopy(t.inwords, 0, this.inwords, 0, this.inwords.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SM3"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + + public Memoable copy() + { + return new SM3Digest(this); + } + + public void reset(Memoable other) + { + SM3Digest d = (SM3Digest)other; + + super.copyIn(d); + copyIn(d); + } + + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + this.V[0] = 0x7380166F; + this.V[1] = 0x4914B2B9; + this.V[2] = 0x172442D7; + this.V[3] = 0xDA8A0600; + this.V[4] = 0xA96F30BC; + this.V[5] = 0x163138AA; + this.V[6] = 0xE38DEE4D; + this.V[7] = 0xB0FB0E4E; + + this.xOff = 0; + } + + + public int doFinal(byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(this.V[0], out, outOff + 0); + Pack.intToBigEndian(this.V[1], out, outOff + 4); + Pack.intToBigEndian(this.V[2], out, outOff + 8); + Pack.intToBigEndian(this.V[3], out, outOff + 12); + Pack.intToBigEndian(this.V[4], out, outOff + 16); + Pack.intToBigEndian(this.V[5], out, outOff + 20); + Pack.intToBigEndian(this.V[6], out, outOff + 24); + Pack.intToBigEndian(this.V[7], out, outOff + 28); + + reset(); + + return DIGEST_LENGTH; + } + + + protected void processWord(byte[] in, + int inOff) + { + // Note: Inlined for performance + // this.inwords[xOff] = Pack.bigEndianToInt(in, inOff); + int n = (((in[inOff] & 0xff) << 24) | + ((in[++inOff] & 0xff) << 16) | + ((in[++inOff] & 0xff) << 8) | + ((in[++inOff] & 0xff))); + + this.inwords[this.xOff] = n; + ++this.xOff; + + if (this.xOff >= 16) + { + processBlock(); + } + } + + protected void processLength(long bitLength) + { + if (this.xOff > (BLOCK_SIZE - 2)) + { + // xOff == 15 --> can't fit the 64 bit length field at tail.. + this.inwords[this.xOff] = 0; // fill with zero + ++this.xOff; + + processBlock(); + } + // Fill with zero words, until reach 2nd to last slot + while (this.xOff < (BLOCK_SIZE - 2)) + { + this.inwords[this.xOff] = 0; + ++this.xOff; + } + + // Store input data length in BITS + this.inwords[this.xOff++] = (int)(bitLength >>> 32); + this.inwords[this.xOff++] = (int)(bitLength); + } + +/* + +3.4.2. Constants + + + Tj = 79cc4519 when 0 < = j < = 15 + Tj = 7a879d8a when 16 < = j < = 63 + +3.4.3. Boolean function + + + FFj(X;Y;Z) = X XOR Y XOR Z when 0 < = j < = 15 + = (X AND Y) OR (X AND Z) OR (Y AND Z) when 16 < = j < = 63 + + GGj(X;Y;Z) = X XOR Y XOR Z when 0 < = j < = 15 + = (X AND Y) OR (NOT X AND Z) when 16 < = j < = 63 + + The X, Y, Z in the fomular are words!GBP + +3.4.4. Permutation function + + + P0(X) = X XOR (X <<< 9) XOR (X <<< 17) ## ROLL, not SHIFT + P1(X) = X XOR (X <<< 15) XOR (X <<< 23) ## ROLL, not SHIFT + + The X in the fomular are a word. + +---------- + +Each ROLL converted to Java expression: + +ROLL 9 : ((x << 9) | (x >>> (32-9)))) +ROLL 17 : ((x << 17) | (x >>> (32-17))) +ROLL 15 : ((x << 15) | (x >>> (32-15))) +ROLL 23 : ((x << 23) | (x >>> (32-23))) + + */ + + private int P0(final int x) + { + final int r9 = ((x << 9) | (x >>> (32 - 9))); + final int r17 = ((x << 17) | (x >>> (32 - 17))); + return (x ^ r9 ^ r17); + } + + private int P1(final int x) + { + final int r15 = ((x << 15) | (x >>> (32 - 15))); + final int r23 = ((x << 23) | (x >>> (32 - 23))); + return (x ^ r15 ^ r23); + } + + private int FF0(final int x, final int y, final int z) + { + return (x ^ y ^ z); + } + + private int FF1(final int x, final int y, final int z) + { + return ((x & y) | (x & z) | (y & z)); + } + + private int GG0(final int x, final int y, final int z) + { + return (x ^ y ^ z); + } + + private int GG1(final int x, final int y, final int z) + { + return ((x & y) | ((~x) & z)); + } + + + protected void processBlock() + { + for (int j = 0; j < 16; ++j) + { + this.W[j] = this.inwords[j]; + } + for (int j = 16; j < 68; ++j) + { + int wj3 = this.W[j - 3]; + int r15 = ((wj3 << 15) | (wj3 >>> (32 - 15))); + int wj13 = this.W[j - 13]; + int r7 = ((wj13 << 7) | (wj13 >>> (32 - 7))); + this.W[j] = P1(this.W[j - 16] ^ this.W[j - 9] ^ r15) ^ r7 ^ this.W[j - 6]; + } + for (int j = 0; j < 64; ++j) + { + this.W1[j] = this.W[j] ^ this.W[j + 4]; + } + + int A = this.V[0]; + int B = this.V[1]; + int C = this.V[2]; + int D = this.V[3]; + int E = this.V[4]; + int F = this.V[5]; + int G = this.V[6]; + int H = this.V[7]; + + + for (int j = 0; j < 16; ++j) + { + int a12 = ((A << 12) | (A >>> (32 - 12))); + int s1_ = a12 + E + T[j]; + int SS1 = ((s1_ << 7) | (s1_ >>> (32 - 7))); + int SS2 = SS1 ^ a12; + int TT1 = FF0(A, B, C) + D + SS2 + this.W1[j]; + int TT2 = GG0(E, F, G) + H + SS1 + this.W[j]; + D = C; + C = ((B << 9) | (B >>> (32 - 9))); + B = A; + A = TT1; + H = G; + G = ((F << 19) | (F >>> (32 - 19))); + F = E; + E = P0(TT2); + } + + // Different FF,GG functions on rounds 16..63 + for (int j = 16; j < 64; ++j) + { + int a12 = ((A << 12) | (A >>> (32 - 12))); + int s1_ = a12 + E + T[j]; + int SS1 = ((s1_ << 7) | (s1_ >>> (32 - 7))); + int SS2 = SS1 ^ a12; + int TT1 = FF1(A, B, C) + D + SS2 + this.W1[j]; + int TT2 = GG1(E, F, G) + H + SS1 + this.W[j]; + D = C; + C = ((B << 9) | (B >>> (32 - 9))); + B = A; + A = TT1; + H = G; + G = ((F << 19) | (F >>> (32 - 19))); + F = E; + E = P0(TT2); + } + + this.V[0] ^= A; + this.V[1] ^= B; + this.V[2] ^= C; + this.V[3] ^= D; + this.V[4] ^= E; + this.V[5] ^= F; + this.V[6] ^= G; + this.V[7] ^= H; + + this.xOff = 0; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinDigest.java new file mode 100644 index 0000000..06eaabd --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinDigest.java @@ -0,0 +1,116 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.crypto.engines.ThreefishEngine; +import org.bouncycastle.crypto.params.SkeinParameters; +import org.bouncycastle.util.Memoable; + +/** + * Implementation of the Skein parameterised hash function in 256, 512 and 1024 bit block sizes, + * based on the {@link ThreefishEngine Threefish} tweakable block cipher. + * <p/> + * This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + * competition in October 2010. + * <p/> + * Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + * Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + * <p/> + * + * @see SkeinEngine + * @see SkeinParameters + */ +public class SkeinDigest + implements ExtendedDigest, Memoable +{ + /** + * 256 bit block size - Skein-256 + */ + public static final int SKEIN_256 = SkeinEngine.SKEIN_256; + /** + * 512 bit block size - Skein-512 + */ + public static final int SKEIN_512 = SkeinEngine.SKEIN_512; + /** + * 1024 bit block size - Skein-1024 + */ + public static final int SKEIN_1024 = SkeinEngine.SKEIN_1024; + + private SkeinEngine engine; + + /** + * Constructs a Skein digest with an internal state size and output size. + * + * @param stateSizeBits the internal state size in bits - one of {@link #SKEIN_256}, {@link #SKEIN_512} or + * {@link #SKEIN_1024}. + * @param digestSizeBits the output/digest size to produce in bits, which must be an integral number of + * bytes. + */ + public SkeinDigest(int stateSizeBits, int digestSizeBits) + { + this.engine = new SkeinEngine(stateSizeBits, digestSizeBits); + init(null); + } + + public SkeinDigest(SkeinDigest digest) + { + this.engine = new SkeinEngine(digest.engine); + } + + public void reset(Memoable other) + { + SkeinDigest d = (SkeinDigest)other; + engine.reset(d.engine); + } + + public Memoable copy() + { + return new SkeinDigest(this); + } + + public String getAlgorithmName() + { + return "Skein-" + (engine.getBlockSize() * 8) + "-" + (engine.getOutputSize() * 8); + } + + public int getDigestSize() + { + return engine.getOutputSize(); + } + + public int getByteLength() + { + return engine.getBlockSize(); + } + + /** + * Optionally initialises the Skein digest with the provided parameters.<br> + * See {@link SkeinParameters} for details on the parameterisation of the Skein hash function. + * + * @param params the parameters to apply to this engine, or <code>null</code> to use no parameters. + */ + public void init(SkeinParameters params) + { + engine.init(params); + } + + public void reset() + { + engine.reset(); + } + + public void update(byte in) + { + engine.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + engine.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + return engine.doFinal(out, outOff); + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinEngine.java new file mode 100644 index 0000000..bca524e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SkeinEngine.java @@ -0,0 +1,817 @@ +package org.bouncycastle.crypto.digests; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.engines.ThreefishEngine; +import org.bouncycastle.crypto.macs.SkeinMac; +import org.bouncycastle.crypto.params.SkeinParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Memoable; + +/** + * Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block + * sizes, based on the {@link ThreefishEngine Threefish} tweakable block cipher. + * <p/> + * This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + * competition in October 2010. + * <p/> + * Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + * Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + * <p/> + * This implementation is the basis for {@link SkeinDigest} and {@link SkeinMac}, implementing the + * parameter based configuration system that allows Skein to be adapted to multiple applications. <br> + * Initialising the engine with {@link SkeinParameters} allows standard and arbitrary parameters to + * be applied during the Skein hash function. + * <p/> + * Implemented: + * <ul> + * <li>256, 512 and 1024 bit internal states.</li> + * <li>Full 96 bit input length.</li> + * <li>Parameters defined in the Skein specification, and arbitrary other pre and post message + * parameters.</li> + * <li>Arbitrary output size in 1 byte intervals.</li> + * </ul> + * <p/> + * Not implemented: + * <ul> + * <li>Sub-byte length input (bit padding).</li> + * <li>Tree hashing.</li> + * </ul> + * + * @see SkeinParameters + */ +public class SkeinEngine + implements Memoable +{ + /** + * 256 bit block size - Skein 256 + */ + public static final int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256; + /** + * 512 bit block size - Skein 512 + */ + public static final int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512; + /** + * 1024 bit block size - Skein 1024 + */ + public static final int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024; + + // Minimal at present, but more complex when tree hashing is implemented + private static class Configuration + { + private byte[] bytes = new byte[32]; + + public Configuration(long outputSizeBits) + { + // 0..3 = ASCII SHA3 + bytes[0] = (byte)'S'; + bytes[1] = (byte)'H'; + bytes[2] = (byte)'A'; + bytes[3] = (byte)'3'; + + // 4..5 = version number in LSB order + bytes[4] = 1; + bytes[5] = 0; + + // 8..15 = output length + ThreefishEngine.wordToBytes(outputSizeBits, bytes, 8); + } + + public byte[] getBytes() + { + return bytes; + } + + } + + public static class Parameter + { + private int type; + private byte[] value; + + public Parameter(int type, byte[] value) + { + this.type = type; + this.value = value; + } + + public int getType() + { + return type; + } + + public byte[] getValue() + { + return value; + } + + } + + /** + * The parameter type for the Skein key. + */ + private static final int PARAM_TYPE_KEY = 0; + + /** + * The parameter type for the Skein configuration block. + */ + private static final int PARAM_TYPE_CONFIG = 4; + + /** + * The parameter type for the message. + */ + private static final int PARAM_TYPE_MESSAGE = 48; + + /** + * The parameter type for the output transformation. + */ + private static final int PARAM_TYPE_OUTPUT = 63; + + /** + * Precalculated UBI(CFG) states for common state/output combinations without key or other + * pre-message params. + */ + private static final Hashtable INITIAL_STATES = new Hashtable(); + + static + { + // From Appendix C of the Skein 1.3 NIST submission + initialState(SKEIN_256, 128, new long[]{ + 0xe1111906964d7260L, + 0x883daaa77c8d811cL, + 0x10080df491960f7aL, + 0xccf7dde5b45bc1c2L}); + + initialState(SKEIN_256, 160, new long[]{ + 0x1420231472825e98L, + 0x2ac4e9a25a77e590L, + 0xd47a58568838d63eL, + 0x2dd2e4968586ab7dL}); + + initialState(SKEIN_256, 224, new long[]{ + 0xc6098a8c9ae5ea0bL, + 0x876d568608c5191cL, + 0x99cb88d7d7f53884L, + 0x384bddb1aeddb5deL}); + + initialState(SKEIN_256, 256, new long[]{ + 0xfc9da860d048b449L, + 0x2fca66479fa7d833L, + 0xb33bc3896656840fL, + 0x6a54e920fde8da69L}); + + initialState(SKEIN_512, 128, new long[]{ + 0xa8bc7bf36fbf9f52L, + 0x1e9872cebd1af0aaL, + 0x309b1790b32190d3L, + 0xbcfbb8543f94805cL, + 0x0da61bcd6e31b11bL, + 0x1a18ebead46a32e3L, + 0xa2cc5b18ce84aa82L, + 0x6982ab289d46982dL}); + + initialState(SKEIN_512, 160, new long[]{ + 0x28b81a2ae013bd91L, + 0xc2f11668b5bdf78fL, + 0x1760d8f3f6a56f12L, + 0x4fb747588239904fL, + 0x21ede07f7eaf5056L, + 0xd908922e63ed70b8L, + 0xb8ec76ffeccb52faL, + 0x01a47bb8a3f27a6eL}); + + initialState(SKEIN_512, 224, new long[]{ + 0xccd0616248677224L, + 0xcba65cf3a92339efL, + 0x8ccd69d652ff4b64L, + 0x398aed7b3ab890b4L, + 0x0f59d1b1457d2bd0L, + 0x6776fe6575d4eb3dL, + 0x99fbc70e997413e9L, + 0x9e2cfccfe1c41ef7L}); + + initialState(SKEIN_512, 384, new long[]{ + 0xa3f6c6bf3a75ef5fL, + 0xb0fef9ccfd84faa4L, + 0x9d77dd663d770cfeL, + 0xd798cbf3b468fddaL, + 0x1bc4a6668a0e4465L, + 0x7ed7d434e5807407L, + 0x548fc1acd4ec44d6L, + 0x266e17546aa18ff8L}); + + initialState(SKEIN_512, 512, new long[]{ + 0x4903adff749c51ceL, + 0x0d95de399746df03L, + 0x8fd1934127c79bceL, + 0x9a255629ff352cb1L, + 0x5db62599df6ca7b0L, + 0xeabe394ca9d5c3f4L, + 0x991112c71a75b523L, + 0xae18a40b660fcc33L}); + } + + private static void initialState(int blockSize, int outputSize, long[] state) + { + INITIAL_STATES.put(variantIdentifier(blockSize / 8, outputSize / 8), state); + } + + private static Integer variantIdentifier(int blockSizeBytes, int outputSizeBytes) + { + return new Integer((outputSizeBytes << 16) | blockSizeBytes); + } + + private static class UbiTweak + { + /** + * Point at which position might overflow long, so switch to add with carry logic + */ + private static final long LOW_RANGE = Long.MAX_VALUE - Integer.MAX_VALUE; + + /** + * Bit 127 = final + */ + private static final long T1_FINAL = 1L << 63; + + /** + * Bit 126 = first + */ + private static final long T1_FIRST = 1L << 62; + + /** + * UBI uses a 128 bit tweak + */ + private long tweak[] = new long[2]; + + /** + * Whether 64 bit position exceeded + */ + private boolean extendedPosition; + + public UbiTweak() + { + reset(); + } + + public void reset(UbiTweak tweak) + { + this.tweak = Arrays.clone(tweak.tweak, this.tweak); + this.extendedPosition = tweak.extendedPosition; + } + + public void reset() + { + tweak[0] = 0; + tweak[1] = 0; + extendedPosition = false; + setFirst(true); + } + + public void setType(int type) + { + // Bits 120..125 = type + tweak[1] = (tweak[1] & 0xFFFFFFC000000000L) | ((type & 0x3FL) << 56); + } + + public int getType() + { + return (int)((tweak[1] >>> 56) & 0x3FL); + } + + public void setFirst(boolean first) + { + if (first) + { + tweak[1] |= T1_FIRST; + } + else + { + tweak[1] &= ~T1_FIRST; + } + } + + public boolean isFirst() + { + return ((tweak[1] & T1_FIRST) != 0); + } + + public void setFinal(boolean last) + { + if (last) + { + tweak[1] |= T1_FINAL; + } + else + { + tweak[1] &= ~T1_FINAL; + } + } + + public boolean isFinal() + { + return ((tweak[1] & T1_FINAL) != 0); + } + + /** + * Advances the position in the tweak by the specified value. + */ + public void advancePosition(int advance) + { + // Bits 0..95 = position + if (extendedPosition) + { + long[] parts = new long[3]; + parts[0] = tweak[0] & 0xFFFFFFFFL; + parts[1] = (tweak[0] >>> 32) & 0xFFFFFFFFL; + parts[2] = tweak[1] & 0xFFFFFFFFL; + + long carry = advance; + for (int i = 0; i < parts.length; i++) + { + carry += parts[i]; + parts[i] = carry; + carry >>>= 32; + } + tweak[0] = ((parts[1] & 0xFFFFFFFFL) << 32) | (parts[0] & 0xFFFFFFFFL); + tweak[1] = (tweak[1] & 0xFFFFFFFF00000000L) | (parts[2] & 0xFFFFFFFFL); + } + else + { + long position = tweak[0]; + position += advance; + tweak[0] = position; + if (position > LOW_RANGE) + { + extendedPosition = true; + } + } + } + + public long[] getWords() + { + return tweak; + } + + public String toString() + { + return getType() + " first: " + isFirst() + ", final: " + isFinal(); + } + + } + + /** + * The Unique Block Iteration chaining mode. + */ + // TODO: This might be better as methods... + private class UBI + { + private final UbiTweak tweak = new UbiTweak(); + + /** + * Buffer for the current block of message data + */ + private byte[] currentBlock; + + /** + * Offset into the current message block + */ + private int currentOffset; + + /** + * Buffer for message words for feedback into encrypted block + */ + private long[] message; + + public UBI(int blockSize) + { + currentBlock = new byte[blockSize]; + message = new long[currentBlock.length / 8]; + } + + public void reset(UBI ubi) + { + currentBlock = Arrays.clone(ubi.currentBlock, currentBlock); + currentOffset = ubi.currentOffset; + message = Arrays.clone(ubi.message, this.message); + tweak.reset(ubi.tweak); + } + + public void reset(int type) + { + tweak.reset(); + tweak.setType(type); + currentOffset = 0; + } + + public void update(byte[] value, int offset, int len, long[] output) + { + /* + * Buffer complete blocks for the underlying Threefish cipher, only flushing when there + * are subsequent bytes (last block must be processed in doFinal() with final=true set). + */ + int copied = 0; + while (len > copied) + { + if (currentOffset == currentBlock.length) + { + processBlock(output); + tweak.setFirst(false); + currentOffset = 0; + } + + int toCopy = Math.min((len - copied), currentBlock.length - currentOffset); + System.arraycopy(value, offset + copied, currentBlock, currentOffset, toCopy); + copied += toCopy; + currentOffset += toCopy; + tweak.advancePosition(toCopy); + } + } + + private void processBlock(long[] output) + { + threefish.init(true, chain, tweak.getWords()); + for (int i = 0; i < message.length; i++) + { + message[i] = ThreefishEngine.bytesToWord(currentBlock, i * 8); + } + + threefish.processBlock(message, output); + + for (int i = 0; i < output.length; i++) + { + output[i] ^= message[i]; + } + } + + public void doFinal(long[] output) + { + // Pad remainder of current block with zeroes + for (int i = currentOffset; i < currentBlock.length; i++) + { + currentBlock[i] = 0; + } + + tweak.setFinal(true); + processBlock(output); + } + + } + + /** + * Underlying Threefish tweakable block cipher + */ + final ThreefishEngine threefish; + + /** + * Size of the digest output, in bytes + */ + private final int outputSizeBytes; + + /** + * The current chaining/state value + */ + long[] chain; + + /** + * The initial state value + */ + private long[] initialState; + + /** + * The (optional) key parameter + */ + private byte[] key; + + /** + * Parameters to apply prior to the message + */ + private Parameter[] preMessageParameters; + + /** + * Parameters to apply after the message, but prior to output + */ + private Parameter[] postMessageParameters; + + /** + * The current UBI operation + */ + private final UBI ubi; + + /** + * Buffer for single byte update method + */ + private final byte[] singleByte = new byte[1]; + + /** + * Constructs a Skein engine. + * + * @param blockSizeBits the internal state size in bits - one of {@link #SKEIN_256}, {@link #SKEIN_512} or + * {@link #SKEIN_1024}. + * @param outputSizeBits the output/digest size to produce in bits, which must be an integral number of + * bytes. + */ + public SkeinEngine(int blockSizeBits, int outputSizeBits) + { + if (outputSizeBits % 8 != 0) + { + throw new IllegalArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits); + } + // TODO: Prevent digest sizes > block size? + this.outputSizeBytes = outputSizeBits / 8; + + this.threefish = new ThreefishEngine(blockSizeBits); + this.ubi = new UBI(threefish.getBlockSize()); + } + + /** + * Creates a SkeinEngine as an exact copy of an existing instance. + */ + public SkeinEngine(SkeinEngine engine) + { + this(engine.getBlockSize() * 8, engine.getOutputSize() * 8); + copyIn(engine); + } + + private void copyIn(SkeinEngine engine) + { + this.ubi.reset(engine.ubi); + this.chain = Arrays.clone(engine.chain, this.chain); + this.initialState = Arrays.clone(engine.initialState, this.initialState); + this.key = Arrays.clone(engine.key, this.key); + this.preMessageParameters = clone(engine.preMessageParameters, this.preMessageParameters); + this.postMessageParameters = clone(engine.postMessageParameters, this.postMessageParameters); + } + + private static Parameter[] clone(Parameter[] data, Parameter[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.length != data.length)) + { + existing = new Parameter[data.length]; + } + System.arraycopy(data, 0, existing, 0, existing.length); + return existing; + } + + public Memoable copy() + { + return new SkeinEngine(this); + } + + public void reset(Memoable other) + { + SkeinEngine s = (SkeinEngine)other; + if ((getBlockSize() != s.getBlockSize()) || (outputSizeBytes != s.outputSizeBytes)) + { + throw new IllegalArgumentException("Incompatible parameters in provided SkeinEngine."); + } + copyIn(s); + } + + public int getOutputSize() + { + return outputSizeBytes; + } + + public int getBlockSize() + { + return threefish.getBlockSize(); + } + + /** + * Initialises the Skein engine with the provided parameters. See {@link SkeinParameters} for + * details on the parameterisation of the Skein hash function. + * + * @param params the parameters to apply to this engine, or <code>null</code> to use no parameters. + */ + public void init(SkeinParameters params) + { + this.chain = null; + this.key = null; + this.preMessageParameters = null; + this.postMessageParameters = null; + + if (params != null) + { + byte[] key = params.getKey(); + if (key.length < 16) + { + throw new IllegalArgumentException("Skein key must be at least 128 bits."); + } + initParams(params.getParameters()); + } + createInitialState(); + + // Initialise message block + ubiInit(PARAM_TYPE_MESSAGE); + } + + private void initParams(Hashtable parameters) + { + Enumeration keys = parameters.keys(); + final Vector pre = new Vector(); + final Vector post = new Vector(); + + while (keys.hasMoreElements()) + { + Integer type = (Integer)keys.nextElement(); + byte[] value = (byte[])parameters.get(type); + + if (type.intValue() == PARAM_TYPE_KEY) + { + this.key = value; + } + else if (type.intValue() < PARAM_TYPE_MESSAGE) + { + pre.addElement(new Parameter(type.intValue(), value)); + } + else + { + post.addElement(new Parameter(type.intValue(), value)); + } + } + preMessageParameters = new Parameter[pre.size()]; + pre.copyInto(preMessageParameters); + sort(preMessageParameters); + + postMessageParameters = new Parameter[post.size()]; + post.copyInto(postMessageParameters); + sort(postMessageParameters); + } + + private static void sort(Parameter[] params) + { + if (params == null) + { + return; + } + // Insertion sort, for Java 1.1 compatibility + for (int i = 1; i < params.length; i++) + { + Parameter param = params[i]; + int hole = i; + while (hole > 0 && param.getType() < params[hole - 1].getType()) + { + params[hole] = params[hole - 1]; + hole = hole - 1; + } + params[hole] = param; + } + } + + /** + * Calculate the initial (pre message block) chaining state. + */ + private void createInitialState() + { + long[] precalc = (long[])INITIAL_STATES.get(variantIdentifier(getBlockSize(), getOutputSize())); + if ((key == null) && (precalc != null)) + { + // Precalculated UBI(CFG) + chain = Arrays.clone(precalc); + } + else + { + // Blank initial state + chain = new long[getBlockSize() / 8]; + + // Process key block + if (key != null) + { + ubiComplete(SkeinParameters.PARAM_TYPE_KEY, key); + } + + // Process configuration block + ubiComplete(PARAM_TYPE_CONFIG, new Configuration(outputSizeBytes * 8).getBytes()); + } + + // Process additional pre-message parameters + if (preMessageParameters != null) + { + for (int i = 0; i < preMessageParameters.length; i++) + { + Parameter param = preMessageParameters[i]; + ubiComplete(param.getType(), param.getValue()); + } + } + initialState = Arrays.clone(chain); + } + + /** + * Reset the engine to the initial state (with the key and any pre-message parameters , ready to + * accept message input. + */ + public void reset() + { + System.arraycopy(initialState, 0, chain, 0, chain.length); + + ubiInit(PARAM_TYPE_MESSAGE); + } + + private void ubiComplete(int type, byte[] value) + { + ubiInit(type); + this.ubi.update(value, 0, value.length, chain); + ubiFinal(); + } + + private void ubiInit(int type) + { + this.ubi.reset(type); + } + + private void ubiFinal() + { + ubi.doFinal(chain); + } + + private void checkInitialised() + { + if (this.ubi == null) + { + throw new IllegalArgumentException("Skein engine is not initialised."); + } + } + + public void update(byte in) + { + singleByte[0] = in; + update(singleByte, 0, 1); + } + + public void update(byte[] in, int inOff, int len) + { + checkInitialised(); + ubi.update(in, inOff, len, chain); + } + + public int doFinal(byte[] out, int outOff) + { + checkInitialised(); + if (out.length < (outOff + outputSizeBytes)) + { + throw new DataLengthException("Output buffer is too short to hold output of " + outputSizeBytes + " bytes"); + } + + // Finalise message block + ubiFinal(); + + // Process additional post-message parameters + if (postMessageParameters != null) + { + for (int i = 0; i < postMessageParameters.length; i++) + { + Parameter param = postMessageParameters[i]; + ubiComplete(param.getType(), param.getValue()); + } + } + + // Perform the output transform + final int blockSize = getBlockSize(); + final int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize); + for (int i = 0; i < blocksRequired; i++) + { + final int toWrite = Math.min(blockSize, outputSizeBytes - (i * blockSize)); + output(i, out, outOff + (i * blockSize), toWrite); + } + + reset(); + + return outputSizeBytes; + } + + private void output(long outputSequence, byte[] out, int outOff, int outputBytes) + { + byte[] currentBytes = new byte[8]; + ThreefishEngine.wordToBytes(outputSequence, currentBytes, 0); + + // Output is a sequence of UBI invocations all of which use and preserve the pre-output + // state + long[] outputWords = new long[chain.length]; + ubiInit(PARAM_TYPE_OUTPUT); + this.ubi.update(currentBytes, 0, currentBytes.length, outputWords); + ubi.doFinal(outputWords); + + final int wordsRequired = ((outputBytes + 8 - 1) / 8); + for (int i = 0; i < wordsRequired; i++) + { + int toWrite = Math.min(8, outputBytes - (i * 8)); + if (toWrite == 8) + { + ThreefishEngine.wordToBytes(outputWords[i], out, outOff + (i * 8)); + } + else + { + ThreefishEngine.wordToBytes(outputWords[i], currentBytes, 0); + System.arraycopy(currentBytes, 0, out, outOff + (i * 8), toWrite); + } + } + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/digests/package.html deleted file mode 100644 index 0a0d95c..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Message digest classes. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java index c8c548e..19c0beb 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java @@ -43,6 +43,6 @@ public class ECElGamalDecryptor ECPoint tmp = pair.getX().multiply(key.getD()); - return pair.getY().add(tmp.negate()); + return pair.getY().add(tmp.negate()).normalize(); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java index e5569a8..2a0b78d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java @@ -6,7 +6,6 @@ import java.security.SecureRandom; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; -import org.bouncycastle.math.ec.ECConstants; import org.bouncycastle.math.ec.ECPoint; /** @@ -69,6 +68,6 @@ public class ECElGamalEncryptor ECPoint gamma = g.multiply(k); ECPoint phi = key.getQ().multiply(k).add(point); - return new ECPair(gamma, phi); + return new ECPair(gamma.normalize(), phi.normalize()); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECFixedTransform.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECFixedTransform.java new file mode 100644 index 0000000..e35e077 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECFixedTransform.java @@ -0,0 +1,71 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECPoint; + +/** + * this transforms the original randomness used for an ElGamal encryption by a fixed value. + */ +public class ECFixedTransform + implements ECPairFactorTransform +{ + private ECPublicKeyParameters key; + + private BigInteger k; + + public ECFixedTransform(BigInteger k) + { + this.k = k; + } + + /** + * initialise the underlying EC ElGamal engine. + * + * @param param the necessary EC key parameters. + */ + public void init( + CipherParameters param) + { + if (!(param instanceof ECPublicKeyParameters)) + { + throw new IllegalArgumentException("ECPublicKeyParameters are required for fixed transform."); + } + + this.key = (ECPublicKeyParameters)param; + } + + /** + * Transform an existing cipher test pair using the ElGamal algorithm. Note: it is assumed this + * transform has been initialised with the same public key that was used to create the original + * cipher text. + * + * @param cipherText the EC point to process. + * @return returns a new ECPair representing the result of the process. + */ + public ECPair transform(ECPair cipherText) + { + if (key == null) + { + throw new IllegalStateException("ECFixedTransform not initialised"); + } + + ECPoint g = key.getParameters().getG(); + ECPoint gamma = g.multiply(k); + ECPoint phi = key.getQ().multiply(k).add(cipherText.getY()); + + return new ECPair(cipherText.getX().add(gamma).normalize(), phi.normalize()); + } + + /** + * Return the last transform value used by the transform + * + * @return a BigInteger representing k value. + */ + public BigInteger getTransformValue() + { + return k; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java index 32ba070..74016c1 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java @@ -69,6 +69,6 @@ public class ECNewPublicKeyTransform ECPoint gamma = g.multiply(k); ECPoint phi = key.getQ().multiply(k).add(cipherText.getY()); - return new ECPair(gamma, phi); + return new ECPair(gamma.normalize(), phi.normalize()); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java index b037984..b293759 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java @@ -12,11 +12,13 @@ import org.bouncycastle.math.ec.ECPoint; * this transforms the original randomness used for an ElGamal encryption. */ public class ECNewRandomnessTransform - implements ECPairTransform + implements ECPairFactorTransform { private ECPublicKeyParameters key; private SecureRandom random; + private BigInteger lastK; + /** * initialise the underlying EC ElGamal engine. * @@ -71,6 +73,18 @@ public class ECNewRandomnessTransform ECPoint gamma = g.multiply(k); ECPoint phi = key.getQ().multiply(k).add(cipherText.getY()); - return new ECPair(cipherText.getX().add(gamma), phi); + lastK = k; + + return new ECPair(cipherText.getX().add(gamma).normalize(), phi.normalize()); + } + + /** + * Return the last random value generated for a transform + * + * @return a BigInteger representing the last random value. + */ + public BigInteger getTransformValue() + { + return lastK; } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPair.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPair.java index d910f3c..ea3b4b9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPair.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPair.java @@ -23,16 +23,18 @@ public class ECPair return y; } - public byte[] getEncoded() + public boolean equals(ECPair other) { - byte[] xEnc = x.getEncoded(); - byte[] yEnc = y.getEncoded(); - - byte[] full = new byte[xEnc.length + yEnc.length]; + return other.getX().equals(getX()) && other.getY().equals(getY()); + } - System.arraycopy(xEnc, 0, full, 0, xEnc.length); - System.arraycopy(yEnc, 0, full, xEnc.length, yEnc.length); + public boolean equals(Object other) + { + return other instanceof ECPair ? equals((ECPair)other) : false; + } - return full; + public int hashCode() + { + return x.hashCode() + 37 * y.hashCode(); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPairFactorTransform.java b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPairFactorTransform.java new file mode 100644 index 0000000..be48551 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/ec/ECPairFactorTransform.java @@ -0,0 +1,14 @@ +package org.bouncycastle.crypto.ec; + +import java.math.BigInteger; + +public interface ECPairFactorTransform + extends ECPairTransform +{ + /** + * Return the last value used to calculated a transform. + * + * @return a BigInteger representing the last transform value used. + */ + BigInteger getTransformValue(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/ec/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/ec/package.html deleted file mode 100644 index d50edcf..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/ec/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Lightweight EC point operations, such as EC ElGamal and randomness transforms. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/encodings/package.html deleted file mode 100644 index fc56f63..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Block encodings for asymmetric ciphers. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java new file mode 100644 index 0000000..2d1de39 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java @@ -0,0 +1,186 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.util.Pack; + +/** + * Implementation of Daniel J. Bernstein's ChaCha stream cipher. + */ +public class ChaChaEngine extends Salsa20Engine +{ + + /** + * Creates a 20 rounds ChaCha engine. + */ + public ChaChaEngine() + { + super(); + } + + /** + * Creates a ChaCha engine with a specific number of rounds. + * @param rounds the number of rounds (must be an even number). + */ + public ChaChaEngine(int rounds) + { + super(rounds); + } + + public String getAlgorithmName() + { + return "ChaCha" + rounds; + } + + protected void advanceCounter() + { + if (++engineState[12] == 0) + { + ++engineState[13]; + } + } + + protected void resetCounter() + { + engineState[12] = engineState[13] = 0; + } + + protected void setKey(byte[] keyBytes, byte[] ivBytes) + { + if ((keyBytes.length != 16) && (keyBytes.length != 32)) + { + throw new IllegalArgumentException(getAlgorithmName() + " requires 128 bit or 256 bit key"); + } + + int offset = 0; + byte[] constants; + + // Key + engineState[4] = Pack.littleEndianToInt(keyBytes, 0); + engineState[5] = Pack.littleEndianToInt(keyBytes, 4); + engineState[6] = Pack.littleEndianToInt(keyBytes, 8); + engineState[7] = Pack.littleEndianToInt(keyBytes, 12); + + if (keyBytes.length == 32) + { + constants = sigma; + offset = 16; + } else + { + constants = tau; + } + + engineState[8] = Pack.littleEndianToInt(keyBytes, offset); + engineState[9] = Pack.littleEndianToInt(keyBytes, offset + 4); + engineState[10] = Pack.littleEndianToInt(keyBytes, offset + 8); + engineState[11] = Pack.littleEndianToInt(keyBytes, offset + 12); + + engineState[0] = Pack.littleEndianToInt(constants, 0); + engineState[1] = Pack.littleEndianToInt(constants, 4); + engineState[2] = Pack.littleEndianToInt(constants, 8); + engineState[3] = Pack.littleEndianToInt(constants, 12); + + // Counter + engineState[12] = engineState[13] = 0; + + // IV + engineState[14] = Pack.littleEndianToInt(ivBytes, 0); + engineState[15] = Pack.littleEndianToInt(ivBytes, 4); + } + + protected void generateKeyStream(byte[] output) + { + chachaCore(rounds, engineState, x); + Pack.intToLittleEndian(x, output, 0); + } + + /** + * ChacCha function + * + * @param input input data + * + * @return keystream + */ + public static void chachaCore(int rounds, int[] input, int[] x) + { + if (input.length != 16) { + throw new IllegalArgumentException(); + } + if (x.length != 16) { + throw new IllegalArgumentException(); + } + if (rounds % 2 != 0) { + throw new IllegalArgumentException("Number of rounds must be even"); + } + + int x00 = input[ 0]; + int x01 = input[ 1]; + int x02 = input[ 2]; + int x03 = input[ 3]; + int x04 = input[ 4]; + int x05 = input[ 5]; + int x06 = input[ 6]; + int x07 = input[ 7]; + int x08 = input[ 8]; + int x09 = input[ 9]; + int x10 = input[10]; + int x11 = input[11]; + int x12 = input[12]; + int x13 = input[13]; + int x14 = input[14]; + int x15 = input[15]; + + for (int i = rounds; i > 0; i -= 2) + { + x00 += x04; x12 = rotl(x12 ^ x00, 16); + x08 += x12; x04 = rotl(x04 ^ x08, 12); + x00 += x04; x12 = rotl(x12 ^ x00, 8); + x08 += x12; x04 = rotl(x04 ^ x08, 7); + x01 += x05; x13 = rotl(x13 ^ x01, 16); + x09 += x13; x05 = rotl(x05 ^ x09, 12); + x01 += x05; x13 = rotl(x13 ^ x01, 8); + x09 += x13; x05 = rotl(x05 ^ x09, 7); + x02 += x06; x14 = rotl(x14 ^ x02, 16); + x10 += x14; x06 = rotl(x06 ^ x10, 12); + x02 += x06; x14 = rotl(x14 ^ x02, 8); + x10 += x14; x06 = rotl(x06 ^ x10, 7); + x03 += x07; x15 = rotl(x15 ^ x03, 16); + x11 += x15; x07 = rotl(x07 ^ x11, 12); + x03 += x07; x15 = rotl(x15 ^ x03, 8); + x11 += x15; x07 = rotl(x07 ^ x11, 7); + x00 += x05; x15 = rotl(x15 ^ x00, 16); + x10 += x15; x05 = rotl(x05 ^ x10, 12); + x00 += x05; x15 = rotl(x15 ^ x00, 8); + x10 += x15; x05 = rotl(x05 ^ x10, 7); + x01 += x06; x12 = rotl(x12 ^ x01, 16); + x11 += x12; x06 = rotl(x06 ^ x11, 12); + x01 += x06; x12 = rotl(x12 ^ x01, 8); + x11 += x12; x06 = rotl(x06 ^ x11, 7); + x02 += x07; x13 = rotl(x13 ^ x02, 16); + x08 += x13; x07 = rotl(x07 ^ x08, 12); + x02 += x07; x13 = rotl(x13 ^ x02, 8); + x08 += x13; x07 = rotl(x07 ^ x08, 7); + x03 += x04; x14 = rotl(x14 ^ x03, 16); + x09 += x14; x04 = rotl(x04 ^ x09, 12); + x03 += x04; x14 = rotl(x14 ^ x03, 8); + x09 += x14; x04 = rotl(x04 ^ x09, 7); + + } + + x[ 0] = x00 + input[ 0]; + x[ 1] = x01 + input[ 1]; + x[ 2] = x02 + input[ 2]; + x[ 3] = x03 + input[ 3]; + x[ 4] = x04 + input[ 4]; + x[ 5] = x05 + input[ 5]; + x[ 6] = x06 + input[ 6]; + x[ 7] = x07 + input[ 7]; + x[ 8] = x08 + input[ 8]; + x[ 9] = x09 + input[ 9]; + x[10] = x10 + input[10]; + x[11] = x11 + input[11]; + x[12] = x12 + input[12]; + x[13] = x13 + input[13]; + x[14] = x14 + input[14]; + x[15] = x15 + input[15]; + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java index 6b3da1c..89271f0 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java @@ -89,8 +89,7 @@ public class Grain128Engine System.arraycopy(iv, 0, workingIV, 0, iv.length); System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); - setKey(workingKey, workingIV); - initGrain(); + reset(); } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java index c3baaec..782a93c 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Grainv1Engine.java @@ -89,8 +89,7 @@ public class Grainv1Engine System.arraycopy(iv, 0, workingIV, 0, iv.length); System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); - setKey(workingKey, workingIV); - initGrain(); + reset(); } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java index 69da0f0..015e49e 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java @@ -118,6 +118,7 @@ public class HC128Engine "The key must be 128 bits long"); } + idx = 0; cnt = 0; int[] w = new int[1280]; @@ -246,7 +247,6 @@ public class HC128Engine public void reset() { - idx = 0; init(); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java index 538d244..8bd7e9d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java @@ -99,6 +99,7 @@ public class HC256Engine iv = newIV; } + idx = 0; cnt = 0; int[] w = new int[2560]; @@ -226,7 +227,6 @@ public class HC256Engine public void reset() { - idx = 0; init(); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java index 95a395a..1ad2c9d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NullEngine.java @@ -12,14 +12,25 @@ import org.bouncycastle.crypto.OutputLengthException; public class NullEngine implements BlockCipher { private boolean initialised; - protected static final int BLOCK_SIZE = 1; - + protected static final int DEFAULT_BLOCK_SIZE = 1; + private final int blockSize; + /** - * Standard constructor. + * Constructs a null engine with a block size of 1 byte. */ public NullEngine() { - super(); + this(DEFAULT_BLOCK_SIZE); + } + + /** + * Constructs a null engine with a specific block size. + * + * @param blockSize the block size in bytes. + */ + public NullEngine(int blockSize) + { + this.blockSize = blockSize; } /* (non-Javadoc) @@ -44,7 +55,7 @@ public class NullEngine implements BlockCipher */ public int getBlockSize() { - return BLOCK_SIZE; + return blockSize; } /* (non-Javadoc) @@ -57,22 +68,22 @@ public class NullEngine implements BlockCipher { throw new IllegalStateException("Null engine not initialised"); } - if ((inOff + BLOCK_SIZE) > in.length) - { - throw new DataLengthException("input buffer too short"); - } + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + for (int i = 0; i < blockSize; ++i) + { + out[outOff + i] = in[inOff + i]; + } - if ((outOff + BLOCK_SIZE) > out.length) - { - throw new OutputLengthException("output buffer too short"); - } - - for (int i = 0; i < BLOCK_SIZE; ++i) - { - out[outOff + i] = in[inOff + i]; - } - - return BLOCK_SIZE; + return blockSize; } /* (non-Javadoc) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java index 540bd25..cfd86fb 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java @@ -85,7 +85,7 @@ public class RFC3394WrapEngine byte[] buf = new byte[8 + iv.length]; System.arraycopy(iv, 0, block, 0, iv.length); - System.arraycopy(in, 0, block, iv.length, inLen); + System.arraycopy(in, inOff, block, iv.length, inLen); engine.init(true, param); @@ -137,8 +137,8 @@ public class RFC3394WrapEngine byte[] a = new byte[iv.length]; byte[] buf = new byte[8 + iv.length]; - System.arraycopy(in, 0, a, 0, iv.length); - System.arraycopy(in, iv.length, block, 0, inLen - iv.length); + System.arraycopy(in, inOff, a, 0, iv.length); + System.arraycopy(in, inOff + iv.length, block, 0, inLen - iv.length); engine.init(false, param); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java index e7fb943..c9765bf 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java @@ -1,5 +1,8 @@ package org.bouncycastle.crypto.engines; +import java.math.BigInteger; +import java.security.SecureRandom; + import org.bouncycastle.crypto.AsymmetricBlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; @@ -8,16 +11,13 @@ import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; import org.bouncycastle.util.BigIntegers; -import java.math.BigInteger; -import java.security.SecureRandom; - /** * this does your basic RSA algorithm with blinding */ public class RSABlindedEngine implements AsymmetricBlockCipher { - private static BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger ONE = BigInteger.valueOf(1); private RSACoreEngine core = new RSACoreEngine(); private RSAKeyParameters key; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java index 6d4210d..2d6140d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java @@ -13,27 +13,28 @@ import org.bouncycastle.util.Strings; /** * Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 */ - public class Salsa20Engine implements StreamCipher { + public final static int DEFAULT_ROUNDS = 20; + /** Constants */ private final static int STATE_SIZE = 16; // 16, 32 bit ints = 64 bytes - private final static byte[] + protected final static byte[] sigma = Strings.toByteArray("expand 32-byte k"), tau = Strings.toByteArray("expand 16-byte k"); + protected int rounds; + /* * variables to hold the state of the engine * during encryption and decryption */ private int index = 0; - private int[] engineState = new int[STATE_SIZE]; // state - private int[] x = new int[STATE_SIZE] ; // internal buffer - private byte[] keyStream = new byte[STATE_SIZE * 4], // expanded state, 64 bytes - workingKey = null, - workingIV = null; + protected int[] engineState = new int[STATE_SIZE]; // state + protected int[] x = new int[STATE_SIZE] ; // internal buffer + private byte[] keyStream = new byte[STATE_SIZE * 4]; // expanded state, 64 bytes private boolean initialised = false; /* @@ -42,6 +43,28 @@ public class Salsa20Engine private int cW0, cW1, cW2; /** + * Creates a 20 round Salsa20 engine. + */ + public Salsa20Engine() + { + this(DEFAULT_ROUNDS); + } + + /** + * Creates a Salsa20 engine with a specific number of rounds. + * @param rounds the number of rounds (must be an even number). + */ + public Salsa20Engine(int rounds) + { + if (rounds <= 0 || (rounds & 1) != 0) + { + throw new IllegalArgumentException("'rounds' must be a positive, even number"); + } + + this.rounds = rounds; + } + + /** * initialise a Salsa20 cipher. * * @param forEncryption whether or not we are for encryption. @@ -61,34 +84,43 @@ public class Salsa20Engine if (!(params instanceof ParametersWithIV)) { - throw new IllegalArgumentException("Salsa20 Init parameters must include an IV"); + throw new IllegalArgumentException(getAlgorithmName() + " Init parameters must include an IV"); } ParametersWithIV ivParams = (ParametersWithIV) params; byte[] iv = ivParams.getIV(); - - if (iv == null || iv.length != 8) + if (iv == null || iv.length != getNonceSize()) { - throw new IllegalArgumentException("Salsa20 requires exactly 8 bytes of IV"); + throw new IllegalArgumentException(getAlgorithmName() + " requires exactly " + getNonceSize() + + " bytes of IV"); } if (!(ivParams.getParameters() instanceof KeyParameter)) { - throw new IllegalArgumentException("Salsa20 Init parameters must include a key"); + throw new IllegalArgumentException(getAlgorithmName() + " Init parameters must include a key"); } KeyParameter key = (KeyParameter) ivParams.getParameters(); - workingKey = key.getKey(); - workingIV = iv; + setKey(key.getKey(), iv); + reset(); + initialised = true; + } - setKey(workingKey, workingIV); + protected int getNonceSize() + { + return 8; } public String getAlgorithmName() { - return "Salsa20"; + String name = "Salsa20"; + if (rounds != DEFAULT_ROUNDS) + { + name += "/" + rounds; + } + return name; } public byte returnByte(byte in) @@ -101,11 +133,7 @@ public class Salsa20Engine if (index == 0) { generateKeyStream(keyStream); - - if (++engineState[8] == 0) - { - ++engineState[9]; - } + advanceCounter(); } byte out = (byte)(keyStream[index]^in); @@ -114,6 +142,14 @@ public class Salsa20Engine return out; } + protected void advanceCounter() + { + if (++engineState[8] == 0) + { + ++engineState[9]; + } + } + public void processBytes( byte[] in, int inOff, @@ -123,7 +159,7 @@ public class Salsa20Engine { if (!initialised) { - throw new IllegalStateException(getAlgorithmName()+" not initialised"); + throw new IllegalStateException(getAlgorithmName() + " not initialised"); } if ((inOff + len) > in.length) @@ -146,11 +182,7 @@ public class Salsa20Engine if (index == 0) { generateKeyStream(keyStream); - - if (++engineState[8] == 0) - { - ++engineState[9]; - } + advanceCounter(); } out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]); @@ -160,28 +192,32 @@ public class Salsa20Engine public void reset() { - setKey(workingKey, workingIV); + index = 0; + resetLimitCounter(); + resetCounter(); } - // Private implementation - - private void setKey(byte[] keyBytes, byte[] ivBytes) + protected void resetCounter() { - workingKey = keyBytes; - workingIV = ivBytes; + engineState[8] = engineState[9] = 0; + } - index = 0; - resetCounter(); + protected void setKey(byte[] keyBytes, byte[] ivBytes) + { + if ((keyBytes.length != 16) && (keyBytes.length != 32)) { + throw new IllegalArgumentException(getAlgorithmName() + " requires 128 bit or 256 bit key"); + } + int offset = 0; byte[] constants; // Key - engineState[1] = Pack.littleEndianToInt(workingKey, 0); - engineState[2] = Pack.littleEndianToInt(workingKey, 4); - engineState[3] = Pack.littleEndianToInt(workingKey, 8); - engineState[4] = Pack.littleEndianToInt(workingKey, 12); + engineState[1] = Pack.littleEndianToInt(keyBytes, 0); + engineState[2] = Pack.littleEndianToInt(keyBytes, 4); + engineState[3] = Pack.littleEndianToInt(keyBytes, 8); + engineState[4] = Pack.littleEndianToInt(keyBytes, 12); - if (workingKey.length == 32) + if (keyBytes.length == 32) { constants = sigma; offset = 16; @@ -191,26 +227,25 @@ public class Salsa20Engine constants = tau; } - engineState[11] = Pack.littleEndianToInt(workingKey, offset); - engineState[12] = Pack.littleEndianToInt(workingKey, offset+4); - engineState[13] = Pack.littleEndianToInt(workingKey, offset+8); - engineState[14] = Pack.littleEndianToInt(workingKey, offset+12); + engineState[11] = Pack.littleEndianToInt(keyBytes, offset); + engineState[12] = Pack.littleEndianToInt(keyBytes, offset+4); + engineState[13] = Pack.littleEndianToInt(keyBytes, offset+8); + engineState[14] = Pack.littleEndianToInt(keyBytes, offset+12); + engineState[0 ] = Pack.littleEndianToInt(constants, 0); engineState[5 ] = Pack.littleEndianToInt(constants, 4); engineState[10] = Pack.littleEndianToInt(constants, 8); engineState[15] = Pack.littleEndianToInt(constants, 12); // IV - engineState[6] = Pack.littleEndianToInt(workingIV, 0); - engineState[7] = Pack.littleEndianToInt(workingIV, 4); - engineState[8] = engineState[9] = 0; - - initialised = true; + engineState[6] = Pack.littleEndianToInt(ivBytes, 0); + engineState[7] = Pack.littleEndianToInt(ivBytes, 4); + resetCounter(); } - private void generateKeyStream(byte[] output) + protected void generateKeyStream(byte[] output) { - salsaCore(20, engineState, x); + salsaCore(rounds, engineState, x); Pack.intToLittleEndian(x, output, 0); } @@ -223,50 +258,86 @@ public class Salsa20Engine */ public static void salsaCore(int rounds, int[] input, int[] x) { - // TODO Exception if rounds odd? + if (input.length != 16) { + throw new IllegalArgumentException(); + } + if (x.length != 16) { + throw new IllegalArgumentException(); + } + if (rounds % 2 != 0) { + throw new IllegalArgumentException("Number of rounds must be even"); + } - System.arraycopy(input, 0, x, 0, input.length); + int x00 = input[ 0]; + int x01 = input[ 1]; + int x02 = input[ 2]; + int x03 = input[ 3]; + int x04 = input[ 4]; + int x05 = input[ 5]; + int x06 = input[ 6]; + int x07 = input[ 7]; + int x08 = input[ 8]; + int x09 = input[ 9]; + int x10 = input[10]; + int x11 = input[11]; + int x12 = input[12]; + int x13 = input[13]; + int x14 = input[14]; + int x15 = input[15]; for (int i = rounds; i > 0; i -= 2) { - x[ 4] ^= rotl((x[ 0]+x[12]), 7); - x[ 8] ^= rotl((x[ 4]+x[ 0]), 9); - x[12] ^= rotl((x[ 8]+x[ 4]),13); - x[ 0] ^= rotl((x[12]+x[ 8]),18); - x[ 9] ^= rotl((x[ 5]+x[ 1]), 7); - x[13] ^= rotl((x[ 9]+x[ 5]), 9); - x[ 1] ^= rotl((x[13]+x[ 9]),13); - x[ 5] ^= rotl((x[ 1]+x[13]),18); - x[14] ^= rotl((x[10]+x[ 6]), 7); - x[ 2] ^= rotl((x[14]+x[10]), 9); - x[ 6] ^= rotl((x[ 2]+x[14]),13); - x[10] ^= rotl((x[ 6]+x[ 2]),18); - x[ 3] ^= rotl((x[15]+x[11]), 7); - x[ 7] ^= rotl((x[ 3]+x[15]), 9); - x[11] ^= rotl((x[ 7]+x[ 3]),13); - x[15] ^= rotl((x[11]+x[ 7]),18); - x[ 1] ^= rotl((x[ 0]+x[ 3]), 7); - x[ 2] ^= rotl((x[ 1]+x[ 0]), 9); - x[ 3] ^= rotl((x[ 2]+x[ 1]),13); - x[ 0] ^= rotl((x[ 3]+x[ 2]),18); - x[ 6] ^= rotl((x[ 5]+x[ 4]), 7); - x[ 7] ^= rotl((x[ 6]+x[ 5]), 9); - x[ 4] ^= rotl((x[ 7]+x[ 6]),13); - x[ 5] ^= rotl((x[ 4]+x[ 7]),18); - x[11] ^= rotl((x[10]+x[ 9]), 7); - x[ 8] ^= rotl((x[11]+x[10]), 9); - x[ 9] ^= rotl((x[ 8]+x[11]),13); - x[10] ^= rotl((x[ 9]+x[ 8]),18); - x[12] ^= rotl((x[15]+x[14]), 7); - x[13] ^= rotl((x[12]+x[15]), 9); - x[14] ^= rotl((x[13]+x[12]),13); - x[15] ^= rotl((x[14]+x[13]),18); + x04 ^= rotl((x00+x12), 7); + x08 ^= rotl((x04+x00), 9); + x12 ^= rotl((x08+x04),13); + x00 ^= rotl((x12+x08),18); + x09 ^= rotl((x05+x01), 7); + x13 ^= rotl((x09+x05), 9); + x01 ^= rotl((x13+x09),13); + x05 ^= rotl((x01+x13),18); + x14 ^= rotl((x10+x06), 7); + x02 ^= rotl((x14+x10), 9); + x06 ^= rotl((x02+x14),13); + x10 ^= rotl((x06+x02),18); + x03 ^= rotl((x15+x11), 7); + x07 ^= rotl((x03+x15), 9); + x11 ^= rotl((x07+x03),13); + x15 ^= rotl((x11+x07),18); + + x01 ^= rotl((x00+x03), 7); + x02 ^= rotl((x01+x00), 9); + x03 ^= rotl((x02+x01),13); + x00 ^= rotl((x03+x02),18); + x06 ^= rotl((x05+x04), 7); + x07 ^= rotl((x06+x05), 9); + x04 ^= rotl((x07+x06),13); + x05 ^= rotl((x04+x07),18); + x11 ^= rotl((x10+x09), 7); + x08 ^= rotl((x11+x10), 9); + x09 ^= rotl((x08+x11),13); + x10 ^= rotl((x09+x08),18); + x12 ^= rotl((x15+x14), 7); + x13 ^= rotl((x12+x15), 9); + x14 ^= rotl((x13+x12),13); + x15 ^= rotl((x14+x13),18); } - for (int i = 0; i < STATE_SIZE; ++i) - { - x[i] += input[i]; - } + x[ 0] = x00 + input[ 0]; + x[ 1] = x01 + input[ 1]; + x[ 2] = x02 + input[ 2]; + x[ 3] = x03 + input[ 3]; + x[ 4] = x04 + input[ 4]; + x[ 5] = x05 + input[ 5]; + x[ 6] = x06 + input[ 6]; + x[ 7] = x07 + input[ 7]; + x[ 8] = x08 + input[ 8]; + x[ 9] = x09 + input[ 9]; + x[10] = x10 + input[10]; + x[11] = x11 + input[11]; + x[12] = x12 + input[12]; + x[13] = x13 + input[13]; + x[14] = x14 + input[14]; + x[15] = x15 + input[15]; } /** @@ -277,12 +348,12 @@ public class Salsa20Engine * * @return rotated x */ - private static int rotl(int x, int y) + protected static int rotl(int x, int y) { return (x << y) | (x >>> -y); } - private void resetCounter() + private void resetLimitCounter() { cW0 = 0; cW1 = 0; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Shacal2Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Shacal2Engine.java new file mode 100644 index 0000000..b59205d --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Shacal2Engine.java @@ -0,0 +1,201 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * Block cipher Shacal2, designed by Helena Handschuh and David Naccache, + * based on hash function SHA-256, + * using SHA-256-Initialization-Values as data and SHA-256-Data as key. + * <p> + * A description of Shacal can be found at: + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.3.4066 + * Best known cryptanalytic (Wikipedia 11.2013): + * Related-key rectangle attack on 44-rounds (Jiqiang Lu, Jongsung Kim). + * Comments are related to SHA-256-Naming as described in FIPS PUB 180-2 + * </p> + */ +public class Shacal2Engine + implements BlockCipher +{ + private final static int[] K = { // SHA-256-Constants + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + private static final int BLOCK_SIZE = 32; + private boolean forEncryption = false; + private static final int ROUNDS = 64; + + private int[] workingKey = null; // expanded key: corresponds to the message block W in FIPS PUB 180-2 + + public Shacal2Engine() + { + } + + public void reset() + { + } + + public String getAlgorithmName() + { + return "Shacal2"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public void init(boolean _forEncryption, CipherParameters params) + throws IllegalArgumentException + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("only simple KeyParameter expected."); + } + this.forEncryption = _forEncryption; + workingKey = new int[64]; + setKey( ((KeyParameter)params).getKey() ); + } + + public void setKey(byte[] kb) + { + if (kb.length == 0 || kb.length > 64 || kb.length < 16 || kb.length % 8 != 0) + { + throw new IllegalArgumentException("Shacal2-key must be 16 - 64 bytes and multiple of 8"); + } + + bytes2ints(kb, workingKey, 0, 0); + + for ( int i = 16; i < 64; i++) + { // Key-Expansion, implicitly Zero-Padding for 16 > i > kb.length/4 + workingKey[i] = + ( (workingKey[i-2] >>> 17 | workingKey[i-2] << -17) // corresponds to ROTL n(x) of FIPS PUB 180-2 + ^ (workingKey[i-2] >>> 19 | workingKey[i-2] << -19) + ^ (workingKey[i-2] >>> 10) ) // corresponds to sigma1(x)-Function of FIPS PUB 180-2 + + workingKey[i-7] + + ( (workingKey[i-15] >>> 7 | workingKey[i-15] << -7) + ^ (workingKey[i-15] >>> 18 | workingKey[i-15] << -18) + ^ (workingKey[i-15] >>> 3) ) // corresponds to sigma0(x)-Function of FIPS PUB 180-2 + + workingKey[i-16]; + } + } + + public void encryptBlock(byte[] in, int inOffset, byte[] out, int outOffset) + { + int[] block = new int[BLOCK_SIZE / 4];// corresponds to working variables a,b,c,d,e,f,g,h of FIPS PUB 180-2 + bytes2ints(in, block, inOffset, 0); + + for (int i = 0; i < ROUNDS; i++) + { + int tmp = + (((block[4] >>> 6) | (block[4] << -6)) + ^ ((block[4] >>> 11) | (block[4] << -11)) + ^ ((block[4] >>> 25) | (block[4] << -25))) + + ((block[4] & block[5]) ^ ((~block[4]) & block[6])) + + block[7] + K[i] + workingKey[i]; // corresponds to T1 of FIPS PUB 180-2 + block[7] = block[6]; + block[6] = block[5]; + block[5] = block[4]; + block[4] = block[3] + tmp; + block[3] = block[2]; + block[2] = block[1]; + block[1] = block[0]; + block[0] = tmp + + (((block[0] >>> 2) | (block[0] << -2)) + ^ ((block[0] >>> 13) | (block[0] << -13)) + ^ ((block[0] >>> 22) | (block[0] << -22))) + + ((block[0] & block[2]) ^ (block[0] & block[3]) ^ (block[2] & block[3])); + //corresponds to T2 of FIPS PUB 180-2, block[1] and block[2] replaced + } + ints2bytes(block, out, outOffset); + } + + public void decryptBlock(byte[] in, int inOffset, byte[] out, int outOffset) + { + int[] block = new int[BLOCK_SIZE / 4]; + bytes2ints(in, block, inOffset, 0); + for (int i = ROUNDS - 1; i >-1; i--) + { + int tmp = block[0] - (((block[1] >>> 2) | (block[1] << -2)) + ^ ((block[1] >>> 13) | (block[1] << -13)) + ^ ((block[1] >>> 22) | (block[1] << -22))) + - ((block[1] & block[2]) ^ (block[1] & block[3]) ^ (block[2] & block[3])); // T2 + block[0] = block[1]; + block[1] = block[2]; + block[2] = block[3]; + block[3] = block[4] - tmp; + block[4] = block[5]; + block[5] = block[6]; + block[6] = block[7]; + block[7] = tmp - K[i] - workingKey[i] + - (((block[4] >>> 6) | (block[4] << -6)) + ^ ((block[4] >>> 11) | (block[4] << -11)) + ^ ((block[4] >>> 25) | (block[4] << -25))) + - ((block[4] & block[5]) ^ ((~block[4]) & block[6])); // T1 + } + ints2bytes(block, out, outOffset); + } + + public int processBlock(byte[] in, int inOffset, byte[] out, int outOffset) + throws DataLengthException, IllegalStateException + { + if (workingKey == null) + { + throw new IllegalStateException("Shacal2 not initialised"); + } + + if ((inOffset + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOffset + BLOCK_SIZE) > out.length) + { + throw new OutputLengthException("output buffer too short"); + } + + if (forEncryption) + { + encryptBlock(in, inOffset, out, outOffset); + } + else + { + decryptBlock(in, inOffset, out, outOffset); + } + + return BLOCK_SIZE; + } + + private void bytes2ints(byte[] bytes, int[] block, int bytesPos, int blockPos) + { + for (int i = blockPos; i < bytes.length / 4; i++) + { + block[i] = ((bytes[bytesPos++] & 0xFF) << 24) + | ((bytes[bytesPos++] & 0xFF) << 16) + | ((bytes[bytesPos++] & 0xFF) << 8) + | (bytes[bytesPos++] & 0xFF); + } + } + + private void ints2bytes(int[] block, byte[] out, int pos) + { + for (int i = 0; i < block.length; i++) + { + out[pos++] = (byte)(block[i] >>> 24); + out[pos++] = (byte)(block[i] >>> 16); + out[pos++] = (byte)(block[i] >>> 8); + out[pos++] = (byte)block[i]; + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java index b09f189..ac65443 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TEAEngine.java @@ -105,6 +105,11 @@ public class TEAEngine private void setKey( byte[] key) { + if (key.length != 16) + { + throw new IllegalArgumentException("Key size must be 128 bits."); + } + _a = bytesToInt(key, 0); _b = bytesToInt(key, 4); _c = bytesToInt(key, 8); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ThreefishEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ThreefishEngine.java new file mode 100644 index 0000000..74bccfe --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ThreefishEngine.java @@ -0,0 +1,1494 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.TweakableBlockCipherParameters; + +/** + * Implementation of the Threefish tweakable large block cipher in 256, 512 and 1024 bit block + * sizes. + * <p/> + * This is the 1.3 version of Threefish defined in the Skein hash function submission to the NIST + * SHA-3 competition in October 2010. + * <p/> + * Threefish was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + * Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + * <p/> + * This implementation inlines all round functions, unrolls 8 rounds, and uses 1.2k of static tables + * to speed up key schedule injection. <br> + * 2 x block size state is retained by each cipher instance. + */ +public class ThreefishEngine + implements BlockCipher +{ + /** + * 256 bit block size - Threefish-256 + */ + public static final int BLOCKSIZE_256 = 256; + /** + * 512 bit block size - Threefish-512 + */ + public static final int BLOCKSIZE_512 = 512; + /** + * 1024 bit block size - Threefish-1024 + */ + public static final int BLOCKSIZE_1024 = 1024; + + /** + * Size of the tweak in bytes (always 128 bit/16 bytes) + */ + private static final int TWEAK_SIZE_BYTES = 16; + private static final int TWEAK_SIZE_WORDS = TWEAK_SIZE_BYTES / 8; + + /** + * Rounds in Threefish-256 + */ + private static final int ROUNDS_256 = 72; + /** + * Rounds in Threefish-512 + */ + private static final int ROUNDS_512 = 72; + /** + * Rounds in Threefish-1024 + */ + private static final int ROUNDS_1024 = 80; + + /** + * Max rounds of any of the variants + */ + private static final int MAX_ROUNDS = ROUNDS_1024; + + /** + * Key schedule parity constant + */ + private static final long C_240 = 0x1BD11BDAA9FC1A22L; + + /* Pre-calculated modulo arithmetic tables for key schedule lookups */ + private static int[] MOD9 = new int[MAX_ROUNDS]; + private static int[] MOD17 = new int[MOD9.length]; + private static int[] MOD5 = new int[MOD9.length]; + private static int[] MOD3 = new int[MOD9.length]; + + static + { + for (int i = 0; i < MOD9.length; i++) + { + MOD17[i] = i % 17; + MOD9[i] = i % 9; + MOD5[i] = i % 5; + MOD3[i] = i % 3; + } + } + + /** + * Block size in bytes + */ + private int blocksizeBytes; + + /** + * Block size in 64 bit words + */ + private int blocksizeWords; + + /** + * Buffer for byte oriented processBytes to call internal word API + */ + private long[] currentBlock; + + /** + * Tweak bytes (2 byte t1,t2, calculated t3 and repeat of t1,t2 for modulo free lookup + */ + private long[] t = new long[5]; + + /** + * Key schedule words + */ + private long[] kw; + + /** + * The internal cipher implementation (varies by blocksize) + */ + private ThreefishCipher cipher; + + private boolean forEncryption; + + /** + * Constructs a new Threefish cipher, with a specified block size. + * + * @param blocksizeBits the block size in bits, one of {@link #BLOCKSIZE_256}, {@link #BLOCKSIZE_512}, + * {@link #BLOCKSIZE_1024}. + */ + public ThreefishEngine(final int blocksizeBits) + { + this.blocksizeBytes = (blocksizeBits / 8); + this.blocksizeWords = (this.blocksizeBytes / 8); + this.currentBlock = new long[blocksizeWords]; + + /* + * Provide room for original key words, extended key word and repeat of key words for modulo + * free lookup of key schedule words. + */ + this.kw = new long[2 * blocksizeWords + 1]; + + switch (blocksizeBits) + { + case BLOCKSIZE_256: + cipher = new Threefish256Cipher(kw, t); + break; + case BLOCKSIZE_512: + cipher = new Threefish512Cipher(kw, t); + break; + case BLOCKSIZE_1024: + cipher = new Threefish1024Cipher(kw, t); + break; + default: + throw new IllegalArgumentException( + "Invalid blocksize - Threefish is defined with block size of 256, 512, or 1024 bits"); + } + } + + /** + * Initialise the engine. + * + * @param params an instance of {@link TweakableBlockCipherParameters}, or {@link KeyParameter} (to + * use a 0 tweak) + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + final byte[] keyBytes; + final byte[] tweakBytes; + + if (params instanceof TweakableBlockCipherParameters) + { + TweakableBlockCipherParameters tParams = (TweakableBlockCipherParameters)params; + keyBytes = tParams.getKey().getKey(); + tweakBytes = tParams.getTweak(); + } + else if (params instanceof KeyParameter) + { + keyBytes = ((KeyParameter)params).getKey(); + tweakBytes = null; + } + else + { + throw new IllegalArgumentException("Invalid parameter passed to Threefish init - " + + params.getClass().getName()); + } + + long[] keyWords = null; + long[] tweakWords = null; + + if (keyBytes != null) + { + if (keyBytes.length != this.blocksizeBytes) + { + throw new IllegalArgumentException("Threefish key must be same size as block (" + blocksizeBytes + + " bytes)"); + } + keyWords = new long[blocksizeWords]; + for (int i = 0; i < keyWords.length; i++) + { + keyWords[i] = bytesToWord(keyBytes, i * 8); + } + } + if (tweakBytes != null) + { + if (tweakBytes.length != TWEAK_SIZE_BYTES) + { + throw new IllegalArgumentException("Threefish tweak must be " + TWEAK_SIZE_BYTES + " bytes"); + } + tweakWords = new long[]{bytesToWord(tweakBytes, 0), bytesToWord(tweakBytes, 8)}; + } + init(forEncryption, keyWords, tweakWords); + } + + /** + * Initialise the engine, specifying the key and tweak directly. + * + * @param forEncryption the cipher mode. + * @param key the words of the key, or <code>null</code> to use the current key. + * @param tweak the 2 word (128 bit) tweak, or <code>null</code> to use the current tweak. + */ + public void init(boolean forEncryption, final long[] key, final long[] tweak) + { + this.forEncryption = forEncryption; + if (key != null) + { + setKey(key); + } + if (tweak != null) + { + setTweak(tweak); + } + } + + private void setKey(long[] key) + { + if (key.length != this.blocksizeWords) + { + throw new IllegalArgumentException("Threefish key must be same size as block (" + blocksizeWords + + " words)"); + } + + /* + * Full subkey schedule is deferred to execution to avoid per cipher overhead (10k for 512, + * 20k for 1024). + * + * Key and tweak word sequences are repeated, and static MOD17/MOD9/MOD5/MOD3 calculations + * used, to avoid expensive mod computations during cipher operation. + */ + + long knw = C_240; + for (int i = 0; i < blocksizeWords; i++) + { + kw[i] = key[i]; + knw = knw ^ kw[i]; + } + kw[blocksizeWords] = knw; + System.arraycopy(kw, 0, kw, blocksizeWords + 1, blocksizeWords); + } + + private void setTweak(long[] tweak) + { + if (tweak.length != TWEAK_SIZE_WORDS) + { + throw new IllegalArgumentException("Tweak must be " + TWEAK_SIZE_WORDS + " words."); + } + + /* + * Tweak schedule partially repeated to avoid mod computations during cipher operation + */ + t[0] = tweak[0]; + t[1] = tweak[1]; + t[2] = t[0] ^ t[1]; + t[3] = t[0]; + t[4] = t[1]; + } + + public String getAlgorithmName() + { + return "Threefish-" + (blocksizeBytes * 8); + } + + public int getBlockSize() + { + return blocksizeBytes; + } + + public void reset() + { + } + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, + IllegalStateException + { + if ((outOff + blocksizeBytes) > out.length) + { + throw new DataLengthException("Output buffer too short"); + } + + if ((inOff + blocksizeBytes) > in.length) + { + throw new DataLengthException("Input buffer too short"); + } + + for (int i = 0; i < blocksizeBytes; i += 8) + { + currentBlock[i >> 3] = bytesToWord(in, inOff + i); + } + processBlock(this.currentBlock, this.currentBlock); + for (int i = 0; i < blocksizeBytes; i += 8) + { + wordToBytes(this.currentBlock[i >> 3], out, outOff + i); + } + + return blocksizeBytes; + } + + /** + * Process a block of data represented as 64 bit words. + * + * @param in a block sized buffer of words to process. + * @param out a block sized buffer of words to receive the output of the operation. + * @return the number of 8 byte words processed (which will be the same as the block size). + * @throws DataLengthException if either the input or output is not block sized. + * @throws IllegalStateException if this engine is not initialised. + */ + public int processBlock(long[] in, long[] out) + throws DataLengthException, IllegalStateException + { + if (kw[blocksizeWords] == 0) + { + throw new IllegalStateException("Threefish engine not initialised"); + } + + if (in.length != blocksizeWords) + { + throw new DataLengthException("Input buffer too short"); + } + if (out.length != blocksizeWords) + { + throw new DataLengthException("Output buffer too short"); + } + + if (forEncryption) + { + cipher.encryptBlock(in, out); + } + else + { + cipher.decryptBlock(in, out); + } + + return blocksizeWords; + } + + /** + * Read a single 64 bit word from input in LSB first order. + */ + // At least package protected for efficient access from inner class + public static long bytesToWord(final byte[] bytes, final int off) + { + if ((off + 8) > bytes.length) + { + // Help the JIT avoid index checks + throw new IllegalArgumentException(); + } + + long word = 0; + int index = off; + + word = (bytes[index++] & 0xffL); + word |= (bytes[index++] & 0xffL) << 8; + word |= (bytes[index++] & 0xffL) << 16; + word |= (bytes[index++] & 0xffL) << 24; + word |= (bytes[index++] & 0xffL) << 32; + word |= (bytes[index++] & 0xffL) << 40; + word |= (bytes[index++] & 0xffL) << 48; + word |= (bytes[index++] & 0xffL) << 56; + + return word; + } + + /** + * Write a 64 bit word to output in LSB first order. + */ + // At least package protected for efficient access from inner class + public static void wordToBytes(final long word, final byte[] bytes, final int off) + { + if ((off + 8) > bytes.length) + { + // Help the JIT avoid index checks + throw new IllegalArgumentException(); + } + int index = off; + + bytes[index++] = (byte)word; + bytes[index++] = (byte)(word >> 8); + bytes[index++] = (byte)(word >> 16); + bytes[index++] = (byte)(word >> 24); + bytes[index++] = (byte)(word >> 32); + bytes[index++] = (byte)(word >> 40); + bytes[index++] = (byte)(word >> 48); + bytes[index++] = (byte)(word >> 56); + } + + /** + * Rotate left + xor part of the mix operation. + */ + // Package protected for efficient access from inner class + static long rotlXor(long x, int n, long xor) + { + return ((x << n) | (x >>> -n)) ^ xor; + } + + /** + * Rotate xor + rotate right part of the unmix operation. + */ + // Package protected for efficient access from inner class + static long xorRotr(long x, int n, long xor) + { + long xored = x ^ xor; + return (xored >>> n) | (xored << -n); + } + + private static abstract class ThreefishCipher + { + /** + * The extended + repeated tweak words + */ + protected final long[] t; + /** + * The extended + repeated key words + */ + protected final long[] kw; + + protected ThreefishCipher(final long[] kw, final long[] t) + { + this.kw = kw; + this.t = t; + } + + abstract void encryptBlock(long[] block, long[] out); + + abstract void decryptBlock(long[] block, long[] out); + + } + + private static final class Threefish256Cipher + extends ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private static final int ROTATION_0_0 = 14, ROTATION_0_1 = 16; + private static final int ROTATION_1_0 = 52, ROTATION_1_1 = 57; + private static final int ROTATION_2_0 = 23, ROTATION_2_1 = 40; + private static final int ROTATION_3_0 = 5, ROTATION_3_1 = 37; + + private static final int ROTATION_4_0 = 25, ROTATION_4_1 = 33; + private static final int ROTATION_5_0 = 46, ROTATION_5_1 = 12; + private static final int ROTATION_6_0 = 58, ROTATION_6_1 = 22; + private static final int ROTATION_7_0 = 32, ROTATION_7_1 = 32; + + public Threefish256Cipher(long[] kw, long[] t) + { + super(kw, t); + } + + void encryptBlock(long[] block, long[] out) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod5 = MOD5; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 9) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + /* + * Read 4 words of plaintext data, not using arrays for cipher state + */ + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1] + t[0]; + b2 += kw[2] + t[1]; + b3 += kw[3]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 2 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_256 / 4); d += 2) + { + final int dm5 = mod5[d]; + final int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 2 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = rotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_0_1, b2 += b3); + + b3 = rotlXor(b3, ROTATION_1_0, b0 += b3); + b1 = rotlXor(b1, ROTATION_1_1, b2 += b1); + + b1 = rotlXor(b1, ROTATION_2_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_2_1, b2 += b3); + + b3 = rotlXor(b3, ROTATION_3_0, b0 += b3); + b1 = rotlXor(b1, ROTATION_3_1, b2 += b1); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm5]; + b1 += kw[dm5 + 1] + t[dm3]; + b2 += kw[dm5 + 2] + t[dm3 + 1]; + b3 += kw[dm5 + 3] + d; + + /* + * 4 more rounds of mix/permute + */ + b1 = rotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_4_1, b2 += b3); + + b3 = rotlXor(b3, ROTATION_5_0, b0 += b3); + b1 = rotlXor(b1, ROTATION_5_1, b2 += b1); + + b1 = rotlXor(b1, ROTATION_6_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_6_1, b2 += b3); + + b3 = rotlXor(b3, ROTATION_7_0, b0 += b3); + b1 = rotlXor(b1, ROTATION_7_1, b2 += b1); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm5 + 1]; + b1 += kw[dm5 + 2] + t[dm3 + 1]; + b2 += kw[dm5 + 3] + t[dm3 + 2]; + b3 += kw[dm5 + 4] + d + 1; + } + + /* + * Output cipher state. + */ + out[0] = b0; + out[1] = b1; + out[2] = b2; + out[3] = b3; + } + + void decryptBlock(long[] block, long[] state) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod5 = MOD5; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 9) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + + for (int d = (ROUNDS_256 / 4) - 1; d >= 1; d -= 2) + { + final int dm5 = mod5[d]; + final int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm5 + 1]; + b1 -= kw[dm5 + 2] + t[dm3 + 1]; + b2 -= kw[dm5 + 3] + t[dm3 + 2]; + b3 -= kw[dm5 + 4] + d + 1; + + /* Reverse second 4 mix/permute rounds */ + + b3 = xorRotr(b3, ROTATION_7_0, b0); + b0 -= b3; + b1 = xorRotr(b1, ROTATION_7_1, b2); + b2 -= b1; + + b1 = xorRotr(b1, ROTATION_6_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_6_1, b2); + b2 -= b3; + + b3 = xorRotr(b3, ROTATION_5_0, b0); + b0 -= b3; + b1 = xorRotr(b1, ROTATION_5_1, b2); + b2 -= b1; + + b1 = xorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm5]; + b1 -= kw[dm5 + 1] + t[dm3]; + b2 -= kw[dm5 + 2] + t[dm3 + 1]; + b3 -= kw[dm5 + 3] + d; + + /* Reverse first 4 mix/permute rounds */ + b3 = xorRotr(b3, ROTATION_3_0, b0); + b0 -= b3; + b1 = xorRotr(b1, ROTATION_3_1, b2); + b2 -= b1; + + b1 = xorRotr(b1, ROTATION_2_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_2_1, b2); + b2 -= b3; + + b3 = xorRotr(b3, ROTATION_1_0, b0); + b0 -= b3; + b1 = xorRotr(b1, ROTATION_1_1, b2); + b2 -= b1; + + b1 = xorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1] + t[0]; + b2 -= kw[2] + t[1]; + b3 -= kw[3]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + } + + } + + private static final class Threefish512Cipher + extends ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private static final int ROTATION_0_0 = 46, ROTATION_0_1 = 36, ROTATION_0_2 = 19, ROTATION_0_3 = 37; + private static final int ROTATION_1_0 = 33, ROTATION_1_1 = 27, ROTATION_1_2 = 14, ROTATION_1_3 = 42; + private static final int ROTATION_2_0 = 17, ROTATION_2_1 = 49, ROTATION_2_2 = 36, ROTATION_2_3 = 39; + private static final int ROTATION_3_0 = 44, ROTATION_3_1 = 9, ROTATION_3_2 = 54, ROTATION_3_3 = 56; + + private static final int ROTATION_4_0 = 39, ROTATION_4_1 = 30, ROTATION_4_2 = 34, ROTATION_4_3 = 24; + private static final int ROTATION_5_0 = 13, ROTATION_5_1 = 50, ROTATION_5_2 = 10, ROTATION_5_3 = 17; + private static final int ROTATION_6_0 = 25, ROTATION_6_1 = 29, ROTATION_6_2 = 39, ROTATION_6_3 = 43; + private static final int ROTATION_7_0 = 8, ROTATION_7_1 = 35, ROTATION_7_2 = 56, ROTATION_7_3 = 22; + + protected Threefish512Cipher(long[] kw, long[] t) + { + super(kw, t); + } + + public void encryptBlock(long[] block, long[] out) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod9 = MOD9; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 17) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + /* + * Read 8 words of plaintext data, not using arrays for cipher state + */ + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + long b4 = block[4]; + long b5 = block[5]; + long b6 = block[6]; + long b7 = block[7]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1]; + b2 += kw[2]; + b3 += kw[3]; + b4 += kw[4]; + b5 += kw[5] + t[0]; + b6 += kw[6] + t[1]; + b7 += kw[7]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 4 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_512 / 4); d += 2) + { + final int dm9 = mod9[d]; + final int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 4 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = rotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_0_1, b2 += b3); + b5 = rotlXor(b5, ROTATION_0_2, b4 += b5); + b7 = rotlXor(b7, ROTATION_0_3, b6 += b7); + + b1 = rotlXor(b1, ROTATION_1_0, b2 += b1); + b7 = rotlXor(b7, ROTATION_1_1, b4 += b7); + b5 = rotlXor(b5, ROTATION_1_2, b6 += b5); + b3 = rotlXor(b3, ROTATION_1_3, b0 += b3); + + b1 = rotlXor(b1, ROTATION_2_0, b4 += b1); + b3 = rotlXor(b3, ROTATION_2_1, b6 += b3); + b5 = rotlXor(b5, ROTATION_2_2, b0 += b5); + b7 = rotlXor(b7, ROTATION_2_3, b2 += b7); + + b1 = rotlXor(b1, ROTATION_3_0, b6 += b1); + b7 = rotlXor(b7, ROTATION_3_1, b0 += b7); + b5 = rotlXor(b5, ROTATION_3_2, b2 += b5); + b3 = rotlXor(b3, ROTATION_3_3, b4 += b3); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm9]; + b1 += kw[dm9 + 1]; + b2 += kw[dm9 + 2]; + b3 += kw[dm9 + 3]; + b4 += kw[dm9 + 4]; + b5 += kw[dm9 + 5] + t[dm3]; + b6 += kw[dm9 + 6] + t[dm3 + 1]; + b7 += kw[dm9 + 7] + d; + + /* + * 4 more rounds of mix/permute + */ + b1 = rotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_4_1, b2 += b3); + b5 = rotlXor(b5, ROTATION_4_2, b4 += b5); + b7 = rotlXor(b7, ROTATION_4_3, b6 += b7); + + b1 = rotlXor(b1, ROTATION_5_0, b2 += b1); + b7 = rotlXor(b7, ROTATION_5_1, b4 += b7); + b5 = rotlXor(b5, ROTATION_5_2, b6 += b5); + b3 = rotlXor(b3, ROTATION_5_3, b0 += b3); + + b1 = rotlXor(b1, ROTATION_6_0, b4 += b1); + b3 = rotlXor(b3, ROTATION_6_1, b6 += b3); + b5 = rotlXor(b5, ROTATION_6_2, b0 += b5); + b7 = rotlXor(b7, ROTATION_6_3, b2 += b7); + + b1 = rotlXor(b1, ROTATION_7_0, b6 += b1); + b7 = rotlXor(b7, ROTATION_7_1, b0 += b7); + b5 = rotlXor(b5, ROTATION_7_2, b2 += b5); + b3 = rotlXor(b3, ROTATION_7_3, b4 += b3); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm9 + 1]; + b1 += kw[dm9 + 2]; + b2 += kw[dm9 + 3]; + b3 += kw[dm9 + 4]; + b4 += kw[dm9 + 5]; + b5 += kw[dm9 + 6] + t[dm3 + 1]; + b6 += kw[dm9 + 7] + t[dm3 + 2]; + b7 += kw[dm9 + 8] + d + 1; + } + + /* + * Output cipher state. + */ + out[0] = b0; + out[1] = b1; + out[2] = b2; + out[3] = b3; + out[4] = b4; + out[5] = b5; + out[6] = b6; + out[7] = b7; + } + + public void decryptBlock(long[] block, long[] state) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod9 = MOD9; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 17) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + long b4 = block[4]; + long b5 = block[5]; + long b6 = block[6]; + long b7 = block[7]; + + for (int d = (ROUNDS_512 / 4) - 1; d >= 1; d -= 2) + { + final int dm9 = mod9[d]; + final int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm9 + 1]; + b1 -= kw[dm9 + 2]; + b2 -= kw[dm9 + 3]; + b3 -= kw[dm9 + 4]; + b4 -= kw[dm9 + 5]; + b5 -= kw[dm9 + 6] + t[dm3 + 1]; + b6 -= kw[dm9 + 7] + t[dm3 + 2]; + b7 -= kw[dm9 + 8] + d + 1; + + /* Reverse second 4 mix/permute rounds */ + + b1 = xorRotr(b1, ROTATION_7_0, b6); + b6 -= b1; + b7 = xorRotr(b7, ROTATION_7_1, b0); + b0 -= b7; + b5 = xorRotr(b5, ROTATION_7_2, b2); + b2 -= b5; + b3 = xorRotr(b3, ROTATION_7_3, b4); + b4 -= b3; + + b1 = xorRotr(b1, ROTATION_6_0, b4); + b4 -= b1; + b3 = xorRotr(b3, ROTATION_6_1, b6); + b6 -= b3; + b5 = xorRotr(b5, ROTATION_6_2, b0); + b0 -= b5; + b7 = xorRotr(b7, ROTATION_6_3, b2); + b2 -= b7; + + b1 = xorRotr(b1, ROTATION_5_0, b2); + b2 -= b1; + b7 = xorRotr(b7, ROTATION_5_1, b4); + b4 -= b7; + b5 = xorRotr(b5, ROTATION_5_2, b6); + b6 -= b5; + b3 = xorRotr(b3, ROTATION_5_3, b0); + b0 -= b3; + + b1 = xorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + b5 = xorRotr(b5, ROTATION_4_2, b4); + b4 -= b5; + b7 = xorRotr(b7, ROTATION_4_3, b6); + b6 -= b7; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm9]; + b1 -= kw[dm9 + 1]; + b2 -= kw[dm9 + 2]; + b3 -= kw[dm9 + 3]; + b4 -= kw[dm9 + 4]; + b5 -= kw[dm9 + 5] + t[dm3]; + b6 -= kw[dm9 + 6] + t[dm3 + 1]; + b7 -= kw[dm9 + 7] + d; + + /* Reverse first 4 mix/permute rounds */ + b1 = xorRotr(b1, ROTATION_3_0, b6); + b6 -= b1; + b7 = xorRotr(b7, ROTATION_3_1, b0); + b0 -= b7; + b5 = xorRotr(b5, ROTATION_3_2, b2); + b2 -= b5; + b3 = xorRotr(b3, ROTATION_3_3, b4); + b4 -= b3; + + b1 = xorRotr(b1, ROTATION_2_0, b4); + b4 -= b1; + b3 = xorRotr(b3, ROTATION_2_1, b6); + b6 -= b3; + b5 = xorRotr(b5, ROTATION_2_2, b0); + b0 -= b5; + b7 = xorRotr(b7, ROTATION_2_3, b2); + b2 -= b7; + + b1 = xorRotr(b1, ROTATION_1_0, b2); + b2 -= b1; + b7 = xorRotr(b7, ROTATION_1_1, b4); + b4 -= b7; + b5 = xorRotr(b5, ROTATION_1_2, b6); + b6 -= b5; + b3 = xorRotr(b3, ROTATION_1_3, b0); + b0 -= b3; + + b1 = xorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + b5 = xorRotr(b5, ROTATION_0_2, b4); + b4 -= b5; + b7 = xorRotr(b7, ROTATION_0_3, b6); + b6 -= b7; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1]; + b2 -= kw[2]; + b3 -= kw[3]; + b4 -= kw[4]; + b5 -= kw[5] + t[0]; + b6 -= kw[6] + t[1]; + b7 -= kw[7]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + state[4] = b4; + state[5] = b5; + state[6] = b6; + state[7] = b7; + } + } + + private static final class Threefish1024Cipher + extends ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private static final int ROTATION_0_0 = 24, ROTATION_0_1 = 13, ROTATION_0_2 = 8, ROTATION_0_3 = 47; + private static final int ROTATION_0_4 = 8, ROTATION_0_5 = 17, ROTATION_0_6 = 22, ROTATION_0_7 = 37; + private static final int ROTATION_1_0 = 38, ROTATION_1_1 = 19, ROTATION_1_2 = 10, ROTATION_1_3 = 55; + private static final int ROTATION_1_4 = 49, ROTATION_1_5 = 18, ROTATION_1_6 = 23, ROTATION_1_7 = 52; + private static final int ROTATION_2_0 = 33, ROTATION_2_1 = 4, ROTATION_2_2 = 51, ROTATION_2_3 = 13; + private static final int ROTATION_2_4 = 34, ROTATION_2_5 = 41, ROTATION_2_6 = 59, ROTATION_2_7 = 17; + private static final int ROTATION_3_0 = 5, ROTATION_3_1 = 20, ROTATION_3_2 = 48, ROTATION_3_3 = 41; + private static final int ROTATION_3_4 = 47, ROTATION_3_5 = 28, ROTATION_3_6 = 16, ROTATION_3_7 = 25; + + private static final int ROTATION_4_0 = 41, ROTATION_4_1 = 9, ROTATION_4_2 = 37, ROTATION_4_3 = 31; + private static final int ROTATION_4_4 = 12, ROTATION_4_5 = 47, ROTATION_4_6 = 44, ROTATION_4_7 = 30; + private static final int ROTATION_5_0 = 16, ROTATION_5_1 = 34, ROTATION_5_2 = 56, ROTATION_5_3 = 51; + private static final int ROTATION_5_4 = 4, ROTATION_5_5 = 53, ROTATION_5_6 = 42, ROTATION_5_7 = 41; + private static final int ROTATION_6_0 = 31, ROTATION_6_1 = 44, ROTATION_6_2 = 47, ROTATION_6_3 = 46; + private static final int ROTATION_6_4 = 19, ROTATION_6_5 = 42, ROTATION_6_6 = 44, ROTATION_6_7 = 25; + private static final int ROTATION_7_0 = 9, ROTATION_7_1 = 48, ROTATION_7_2 = 35, ROTATION_7_3 = 52; + private static final int ROTATION_7_4 = 23, ROTATION_7_5 = 31, ROTATION_7_6 = 37, ROTATION_7_7 = 20; + + public Threefish1024Cipher(long[] kw, long[] t) + { + super(kw, t); + } + + void encryptBlock(long[] block, long[] out) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod17 = MOD17; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 33) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + /* + * Read 16 words of plaintext data, not using arrays for cipher state + */ + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + long b4 = block[4]; + long b5 = block[5]; + long b6 = block[6]; + long b7 = block[7]; + long b8 = block[8]; + long b9 = block[9]; + long b10 = block[10]; + long b11 = block[11]; + long b12 = block[12]; + long b13 = block[13]; + long b14 = block[14]; + long b15 = block[15]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1]; + b2 += kw[2]; + b3 += kw[3]; + b4 += kw[4]; + b5 += kw[5]; + b6 += kw[6]; + b7 += kw[7]; + b8 += kw[8]; + b9 += kw[9]; + b10 += kw[10]; + b11 += kw[11]; + b12 += kw[12]; + b13 += kw[13] + t[0]; + b14 += kw[14] + t[1]; + b15 += kw[15]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 4 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_1024 / 4); d += 2) + { + final int dm17 = mod17[d]; + final int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 4 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = rotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_0_1, b2 += b3); + b5 = rotlXor(b5, ROTATION_0_2, b4 += b5); + b7 = rotlXor(b7, ROTATION_0_3, b6 += b7); + b9 = rotlXor(b9, ROTATION_0_4, b8 += b9); + b11 = rotlXor(b11, ROTATION_0_5, b10 += b11); + b13 = rotlXor(b13, ROTATION_0_6, b12 += b13); + b15 = rotlXor(b15, ROTATION_0_7, b14 += b15); + + b9 = rotlXor(b9, ROTATION_1_0, b0 += b9); + b13 = rotlXor(b13, ROTATION_1_1, b2 += b13); + b11 = rotlXor(b11, ROTATION_1_2, b6 += b11); + b15 = rotlXor(b15, ROTATION_1_3, b4 += b15); + b7 = rotlXor(b7, ROTATION_1_4, b10 += b7); + b3 = rotlXor(b3, ROTATION_1_5, b12 += b3); + b5 = rotlXor(b5, ROTATION_1_6, b14 += b5); + b1 = rotlXor(b1, ROTATION_1_7, b8 += b1); + + b7 = rotlXor(b7, ROTATION_2_0, b0 += b7); + b5 = rotlXor(b5, ROTATION_2_1, b2 += b5); + b3 = rotlXor(b3, ROTATION_2_2, b4 += b3); + b1 = rotlXor(b1, ROTATION_2_3, b6 += b1); + b15 = rotlXor(b15, ROTATION_2_4, b12 += b15); + b13 = rotlXor(b13, ROTATION_2_5, b14 += b13); + b11 = rotlXor(b11, ROTATION_2_6, b8 += b11); + b9 = rotlXor(b9, ROTATION_2_7, b10 += b9); + + b15 = rotlXor(b15, ROTATION_3_0, b0 += b15); + b11 = rotlXor(b11, ROTATION_3_1, b2 += b11); + b13 = rotlXor(b13, ROTATION_3_2, b6 += b13); + b9 = rotlXor(b9, ROTATION_3_3, b4 += b9); + b1 = rotlXor(b1, ROTATION_3_4, b14 += b1); + b5 = rotlXor(b5, ROTATION_3_5, b8 += b5); + b3 = rotlXor(b3, ROTATION_3_6, b10 += b3); + b7 = rotlXor(b7, ROTATION_3_7, b12 += b7); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm17]; + b1 += kw[dm17 + 1]; + b2 += kw[dm17 + 2]; + b3 += kw[dm17 + 3]; + b4 += kw[dm17 + 4]; + b5 += kw[dm17 + 5]; + b6 += kw[dm17 + 6]; + b7 += kw[dm17 + 7]; + b8 += kw[dm17 + 8]; + b9 += kw[dm17 + 9]; + b10 += kw[dm17 + 10]; + b11 += kw[dm17 + 11]; + b12 += kw[dm17 + 12]; + b13 += kw[dm17 + 13] + t[dm3]; + b14 += kw[dm17 + 14] + t[dm3 + 1]; + b15 += kw[dm17 + 15] + d; + + /* + * 4 more rounds of mix/permute + */ + b1 = rotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = rotlXor(b3, ROTATION_4_1, b2 += b3); + b5 = rotlXor(b5, ROTATION_4_2, b4 += b5); + b7 = rotlXor(b7, ROTATION_4_3, b6 += b7); + b9 = rotlXor(b9, ROTATION_4_4, b8 += b9); + b11 = rotlXor(b11, ROTATION_4_5, b10 += b11); + b13 = rotlXor(b13, ROTATION_4_6, b12 += b13); + b15 = rotlXor(b15, ROTATION_4_7, b14 += b15); + + b9 = rotlXor(b9, ROTATION_5_0, b0 += b9); + b13 = rotlXor(b13, ROTATION_5_1, b2 += b13); + b11 = rotlXor(b11, ROTATION_5_2, b6 += b11); + b15 = rotlXor(b15, ROTATION_5_3, b4 += b15); + b7 = rotlXor(b7, ROTATION_5_4, b10 += b7); + b3 = rotlXor(b3, ROTATION_5_5, b12 += b3); + b5 = rotlXor(b5, ROTATION_5_6, b14 += b5); + b1 = rotlXor(b1, ROTATION_5_7, b8 += b1); + + b7 = rotlXor(b7, ROTATION_6_0, b0 += b7); + b5 = rotlXor(b5, ROTATION_6_1, b2 += b5); + b3 = rotlXor(b3, ROTATION_6_2, b4 += b3); + b1 = rotlXor(b1, ROTATION_6_3, b6 += b1); + b15 = rotlXor(b15, ROTATION_6_4, b12 += b15); + b13 = rotlXor(b13, ROTATION_6_5, b14 += b13); + b11 = rotlXor(b11, ROTATION_6_6, b8 += b11); + b9 = rotlXor(b9, ROTATION_6_7, b10 += b9); + + b15 = rotlXor(b15, ROTATION_7_0, b0 += b15); + b11 = rotlXor(b11, ROTATION_7_1, b2 += b11); + b13 = rotlXor(b13, ROTATION_7_2, b6 += b13); + b9 = rotlXor(b9, ROTATION_7_3, b4 += b9); + b1 = rotlXor(b1, ROTATION_7_4, b14 += b1); + b5 = rotlXor(b5, ROTATION_7_5, b8 += b5); + b3 = rotlXor(b3, ROTATION_7_6, b10 += b3); + b7 = rotlXor(b7, ROTATION_7_7, b12 += b7); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm17 + 1]; + b1 += kw[dm17 + 2]; + b2 += kw[dm17 + 3]; + b3 += kw[dm17 + 4]; + b4 += kw[dm17 + 5]; + b5 += kw[dm17 + 6]; + b6 += kw[dm17 + 7]; + b7 += kw[dm17 + 8]; + b8 += kw[dm17 + 9]; + b9 += kw[dm17 + 10]; + b10 += kw[dm17 + 11]; + b11 += kw[dm17 + 12]; + b12 += kw[dm17 + 13]; + b13 += kw[dm17 + 14] + t[dm3 + 1]; + b14 += kw[dm17 + 15] + t[dm3 + 2]; + b15 += kw[dm17 + 16] + d + 1; + + } + + /* + * Output cipher state. + */ + out[0] = b0; + out[1] = b1; + out[2] = b2; + out[3] = b3; + out[4] = b4; + out[5] = b5; + out[6] = b6; + out[7] = b7; + out[8] = b8; + out[9] = b9; + out[10] = b10; + out[11] = b11; + out[12] = b12; + out[13] = b13; + out[14] = b14; + out[15] = b15; + } + + void decryptBlock(long[] block, long[] state) + { + final long[] kw = this.kw; + final long[] t = this.t; + final int[] mod17 = MOD17; + final int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.length != 33) + { + throw new IllegalArgumentException(); + } + if (t.length != 5) + { + throw new IllegalArgumentException(); + } + + long b0 = block[0]; + long b1 = block[1]; + long b2 = block[2]; + long b3 = block[3]; + long b4 = block[4]; + long b5 = block[5]; + long b6 = block[6]; + long b7 = block[7]; + long b8 = block[8]; + long b9 = block[9]; + long b10 = block[10]; + long b11 = block[11]; + long b12 = block[12]; + long b13 = block[13]; + long b14 = block[14]; + long b15 = block[15]; + + for (int d = (ROUNDS_1024 / 4) - 1; d >= 1; d -= 2) + { + final int dm17 = mod17[d]; + final int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm17 + 1]; + b1 -= kw[dm17 + 2]; + b2 -= kw[dm17 + 3]; + b3 -= kw[dm17 + 4]; + b4 -= kw[dm17 + 5]; + b5 -= kw[dm17 + 6]; + b6 -= kw[dm17 + 7]; + b7 -= kw[dm17 + 8]; + b8 -= kw[dm17 + 9]; + b9 -= kw[dm17 + 10]; + b10 -= kw[dm17 + 11]; + b11 -= kw[dm17 + 12]; + b12 -= kw[dm17 + 13]; + b13 -= kw[dm17 + 14] + t[dm3 + 1]; + b14 -= kw[dm17 + 15] + t[dm3 + 2]; + b15 -= kw[dm17 + 16] + d + 1; + + /* Reverse second 4 mix/permute rounds */ + b15 = xorRotr(b15, ROTATION_7_0, b0); + b0 -= b15; + b11 = xorRotr(b11, ROTATION_7_1, b2); + b2 -= b11; + b13 = xorRotr(b13, ROTATION_7_2, b6); + b6 -= b13; + b9 = xorRotr(b9, ROTATION_7_3, b4); + b4 -= b9; + b1 = xorRotr(b1, ROTATION_7_4, b14); + b14 -= b1; + b5 = xorRotr(b5, ROTATION_7_5, b8); + b8 -= b5; + b3 = xorRotr(b3, ROTATION_7_6, b10); + b10 -= b3; + b7 = xorRotr(b7, ROTATION_7_7, b12); + b12 -= b7; + + b7 = xorRotr(b7, ROTATION_6_0, b0); + b0 -= b7; + b5 = xorRotr(b5, ROTATION_6_1, b2); + b2 -= b5; + b3 = xorRotr(b3, ROTATION_6_2, b4); + b4 -= b3; + b1 = xorRotr(b1, ROTATION_6_3, b6); + b6 -= b1; + b15 = xorRotr(b15, ROTATION_6_4, b12); + b12 -= b15; + b13 = xorRotr(b13, ROTATION_6_5, b14); + b14 -= b13; + b11 = xorRotr(b11, ROTATION_6_6, b8); + b8 -= b11; + b9 = xorRotr(b9, ROTATION_6_7, b10); + b10 -= b9; + + b9 = xorRotr(b9, ROTATION_5_0, b0); + b0 -= b9; + b13 = xorRotr(b13, ROTATION_5_1, b2); + b2 -= b13; + b11 = xorRotr(b11, ROTATION_5_2, b6); + b6 -= b11; + b15 = xorRotr(b15, ROTATION_5_3, b4); + b4 -= b15; + b7 = xorRotr(b7, ROTATION_5_4, b10); + b10 -= b7; + b3 = xorRotr(b3, ROTATION_5_5, b12); + b12 -= b3; + b5 = xorRotr(b5, ROTATION_5_6, b14); + b14 -= b5; + b1 = xorRotr(b1, ROTATION_5_7, b8); + b8 -= b1; + + b1 = xorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + b5 = xorRotr(b5, ROTATION_4_2, b4); + b4 -= b5; + b7 = xorRotr(b7, ROTATION_4_3, b6); + b6 -= b7; + b9 = xorRotr(b9, ROTATION_4_4, b8); + b8 -= b9; + b11 = xorRotr(b11, ROTATION_4_5, b10); + b10 -= b11; + b13 = xorRotr(b13, ROTATION_4_6, b12); + b12 -= b13; + b15 = xorRotr(b15, ROTATION_4_7, b14); + b14 -= b15; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm17]; + b1 -= kw[dm17 + 1]; + b2 -= kw[dm17 + 2]; + b3 -= kw[dm17 + 3]; + b4 -= kw[dm17 + 4]; + b5 -= kw[dm17 + 5]; + b6 -= kw[dm17 + 6]; + b7 -= kw[dm17 + 7]; + b8 -= kw[dm17 + 8]; + b9 -= kw[dm17 + 9]; + b10 -= kw[dm17 + 10]; + b11 -= kw[dm17 + 11]; + b12 -= kw[dm17 + 12]; + b13 -= kw[dm17 + 13] + t[dm3]; + b14 -= kw[dm17 + 14] + t[dm3 + 1]; + b15 -= kw[dm17 + 15] + d; + + /* Reverse first 4 mix/permute rounds */ + b15 = xorRotr(b15, ROTATION_3_0, b0); + b0 -= b15; + b11 = xorRotr(b11, ROTATION_3_1, b2); + b2 -= b11; + b13 = xorRotr(b13, ROTATION_3_2, b6); + b6 -= b13; + b9 = xorRotr(b9, ROTATION_3_3, b4); + b4 -= b9; + b1 = xorRotr(b1, ROTATION_3_4, b14); + b14 -= b1; + b5 = xorRotr(b5, ROTATION_3_5, b8); + b8 -= b5; + b3 = xorRotr(b3, ROTATION_3_6, b10); + b10 -= b3; + b7 = xorRotr(b7, ROTATION_3_7, b12); + b12 -= b7; + + b7 = xorRotr(b7, ROTATION_2_0, b0); + b0 -= b7; + b5 = xorRotr(b5, ROTATION_2_1, b2); + b2 -= b5; + b3 = xorRotr(b3, ROTATION_2_2, b4); + b4 -= b3; + b1 = xorRotr(b1, ROTATION_2_3, b6); + b6 -= b1; + b15 = xorRotr(b15, ROTATION_2_4, b12); + b12 -= b15; + b13 = xorRotr(b13, ROTATION_2_5, b14); + b14 -= b13; + b11 = xorRotr(b11, ROTATION_2_6, b8); + b8 -= b11; + b9 = xorRotr(b9, ROTATION_2_7, b10); + b10 -= b9; + + b9 = xorRotr(b9, ROTATION_1_0, b0); + b0 -= b9; + b13 = xorRotr(b13, ROTATION_1_1, b2); + b2 -= b13; + b11 = xorRotr(b11, ROTATION_1_2, b6); + b6 -= b11; + b15 = xorRotr(b15, ROTATION_1_3, b4); + b4 -= b15; + b7 = xorRotr(b7, ROTATION_1_4, b10); + b10 -= b7; + b3 = xorRotr(b3, ROTATION_1_5, b12); + b12 -= b3; + b5 = xorRotr(b5, ROTATION_1_6, b14); + b14 -= b5; + b1 = xorRotr(b1, ROTATION_1_7, b8); + b8 -= b1; + + b1 = xorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = xorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + b5 = xorRotr(b5, ROTATION_0_2, b4); + b4 -= b5; + b7 = xorRotr(b7, ROTATION_0_3, b6); + b6 -= b7; + b9 = xorRotr(b9, ROTATION_0_4, b8); + b8 -= b9; + b11 = xorRotr(b11, ROTATION_0_5, b10); + b10 -= b11; + b13 = xorRotr(b13, ROTATION_0_6, b12); + b12 -= b13; + b15 = xorRotr(b15, ROTATION_0_7, b14); + b14 -= b15; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1]; + b2 -= kw[2]; + b3 -= kw[3]; + b4 -= kw[4]; + b5 -= kw[5]; + b6 -= kw[6]; + b7 -= kw[7]; + b8 -= kw[8]; + b9 -= kw[9]; + b10 -= kw[10]; + b11 -= kw[11]; + b12 -= kw[12]; + b13 -= kw[13] + t[0]; + b14 -= kw[14] + t[1]; + b15 -= kw[15]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + state[4] = b4; + state[5] = b5; + state[6] = b6; + state[7] = b7; + state[8] = b8; + state[9] = b9; + state[10] = b10; + state[11] = b11; + state[12] = b12; + state[13] = b13; + state[14] = b14; + state[15] = b15; + } + + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java index 0703fd6..f16f6d4 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/VMPCEngine.java @@ -44,7 +44,6 @@ public class VMPCEngine implements StreamCipher } ParametersWithIV ivParams = (ParametersWithIV) params; - KeyParameter key = (KeyParameter) ivParams.getParameters(); if (!(ivParams.getParameters() instanceof KeyParameter)) { @@ -52,6 +51,8 @@ public class VMPCEngine implements StreamCipher "VMPC init parameters must include a key"); } + KeyParameter key = (KeyParameter) ivParams.getParameters(); + this.workingIV = ivParams.getIV(); if (workingIV == null || workingIV.length < 1 || workingIV.length > 768) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/XSalsa20Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/XSalsa20Engine.java new file mode 100644 index 0000000..5b2181d --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/XSalsa20Engine.java @@ -0,0 +1,65 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.util.Pack; + +/** + * Implementation of Daniel J. Bernstein's XSalsa20 stream cipher - Salsa20 with an extended nonce. + * <p> + * XSalsa20 requires a 256 bit key, and a 192 bit nonce. + */ +public class XSalsa20Engine extends Salsa20Engine +{ + + public String getAlgorithmName() + { + return "XSalsa20"; + } + + protected int getNonceSize() + { + return 24; + } + + /** + * XSalsa20 key generation: process 256 bit input key and 128 bits of the input nonce + * using a core Salsa20 function without input addition to produce 256 bit working key + * and use that with the remaining 64 bits of nonce to initialize a standard Salsa20 engine state. + */ + protected void setKey(byte[] keyBytes, byte[] ivBytes) + { + if (keyBytes.length != 32) + { + throw new IllegalArgumentException(getAlgorithmName() + " requires a 256 bit key"); + } + + // Set key for HSalsa20 + super.setKey(keyBytes, ivBytes); + + // Pack next 64 bits of IV into engine state instead of counter + engineState[8] = Pack.littleEndianToInt(ivBytes, 8); + engineState[9] = Pack.littleEndianToInt(ivBytes, 12); + + // Process engine state to generate Salsa20 key + int[] hsalsa20Out = new int[engineState.length]; + salsaCore(20, engineState, hsalsa20Out); + + // Set new key, removing addition in last round of salsaCore + engineState[1] = hsalsa20Out[0] - engineState[0]; + engineState[2] = hsalsa20Out[5] - engineState[5]; + engineState[3] = hsalsa20Out[10] - engineState[10]; + engineState[4] = hsalsa20Out[15] - engineState[15]; + + engineState[11] = hsalsa20Out[6] - engineState[6]; + engineState[12] = hsalsa20Out[7] - engineState[7]; + engineState[13] = hsalsa20Out[8] - engineState[8]; + engineState[14] = hsalsa20Out[9] - engineState[9]; + + // Last 64 bits of input IV + engineState[6] = Pack.littleEndianToInt(ivBytes, 16); + engineState[7] = Pack.littleEndianToInt(ivBytes, 20); + + // Counter reset + resetCounter(); + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java index f037da4..fb21bbc 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/XTEAEngine.java @@ -107,6 +107,11 @@ public class XTEAEngine private void setKey( byte[] key) { + if (key.length != 16) + { + throw new IllegalArgumentException("Key size must be 128 bits."); + } + int i, j; for (i = j = 0; i < 4; i++,j+=4) { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/engines/package.html deleted file mode 100644 index e945dac..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Basic cipher classes. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/examples/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/examples/package.html deleted file mode 100644 index 390a540..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/examples/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Simple examples of light weight API usage. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java index 2ef8dd2..16c8b91 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java @@ -1,9 +1,9 @@ package org.bouncycastle.crypto.generators; import org.bouncycastle.crypto.DataLengthException; -import org.bouncycastle.crypto.DerivationFunction; import org.bouncycastle.crypto.DerivationParameters; import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.DigestDerivationFunction; import org.bouncycastle.crypto.params.ISO18033KDFParameters; import org.bouncycastle.crypto.params.KDFParameters; import org.bouncycastle.crypto.util.Pack; @@ -13,7 +13,8 @@ import org.bouncycastle.crypto.util.Pack; * 18033 <br> * This implementation is based on ISO 18033/P1363a. */ -public class BaseKDFBytesGenerator implements DerivationFunction +public class BaseKDFBytesGenerator + implements DigestDerivationFunction { private int counterStart; private Digest digest; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java index 749b0cc..f7a3df2 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/DSAParametersGenerator.java @@ -226,10 +226,10 @@ public class DSAParametersGenerator int seedlen = N; byte[] seed = new byte[seedlen / 8]; -// 3. n = ceiling(L ⁄ outlen) – 1. +// 3. n = ceiling(L / outlen) - 1. int n = (L - 1) / outlen; -// 4. b = L – 1 – (n ∗ outlen). +// 4. b = L - 1 - (n * outlen). int b = (L - 1) % outlen; byte[] output = new byte[d.getDigestSize()]; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java index d77bd74..d5f5fc8 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java @@ -26,6 +26,11 @@ public class ECKeyPairGenerator this.random = ecP.getRandom(); this.params = ecP.getDomainParameters(); + + if (this.random == null) + { + this.random = new SecureRandom(); + } } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFCounterBytesGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFCounterBytesGenerator.java new file mode 100644 index 0000000..306530e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFCounterBytesGenerator.java @@ -0,0 +1,152 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.MacDerivationFunction; +import org.bouncycastle.crypto.params.KDFCounterParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * This KDF has been defined by the publicly available NIST SP 800-108 specification. + */ +public class KDFCounterBytesGenerator + implements MacDerivationFunction +{ + + private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE); + private static final BigInteger TWO = BigInteger.valueOf(2); + + // please refer to the standard for the meaning of the variable names + // all field lengths are in bytes, not in bits as specified by the standard + + // fields set by the constructor + private final Mac prf; + private final int h; + + // fields set by init + private byte[] fixedInputData; + private int maxSizeExcl; + // ios is i defined as an octet string (the binary representation) + private byte[] ios; + + // operational + private int generatedBytes; + // k is used as buffer for all K(i) values + private byte[] k; + + + public KDFCounterBytesGenerator(Mac prf) + { + this.prf = prf; + this.h = prf.getMacSize(); + this.k = new byte[h]; + } + + + public void init(DerivationParameters param) + { + if (!(param instanceof KDFCounterParameters)) + { + throw new IllegalArgumentException("Wrong type of arguments given"); + } + + KDFCounterParameters kdfParams = (KDFCounterParameters)param; + + // --- init mac based PRF --- + + this.prf.init(new KeyParameter(kdfParams.getKI())); + + // --- set arguments --- + + this.fixedInputData = kdfParams.getFixedInputData(); + + int r = kdfParams.getR(); + this.ios = new byte[r / 8]; + + BigInteger maxSize = TWO.pow(r).multiply(BigInteger.valueOf(h)); + this.maxSizeExcl = maxSize.compareTo(INTEGER_MAX) == 1 ? + Integer.MAX_VALUE : maxSize.intValue(); + + // --- set operational state --- + + generatedBytes = 0; + } + + + public Mac getMac() + { + return prf; + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + + int generatedBytesAfter = generatedBytes + len; + if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl) + { + throw new DataLengthException( + "Current KDFCTR may only be used for " + maxSizeExcl + " bytes"); + } + + if (generatedBytes % h == 0) + { + generateNext(); + } + + // copy what is left in the currentT (1..hash + int toGenerate = len; + int posInK = generatedBytes % h; + int leftInK = h - generatedBytes % h; + int toCopy = Math.min(leftInK, toGenerate); + System.arraycopy(k, posInK, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + + while (toGenerate > 0) + { + generateNext(); + toCopy = Math.min(h, toGenerate); + System.arraycopy(k, 0, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + } + + return len; + } + + private void generateNext() + { + int i = generatedBytes / h + 1; + + // encode i into counter buffer + switch (ios.length) + { + case 4: + ios[0] = (byte)(i >>> 24); + // fall through + case 3: + ios[ios.length - 3] = (byte)(i >>> 16); + // fall through + case 2: + ios[ios.length - 2] = (byte)(i >>> 8); + // fall through + case 1: + ios[ios.length - 1] = (byte)i; + break; + default: + throw new IllegalStateException("Unsupported size of counter i"); + } + + + // special case for K(0): K(0) is empty, so no update + prf.update(ios, 0, ios.length); + prf.update(fixedInputData, 0, fixedInputData.length); + prf.doFinal(k, 0); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFDoublePipelineIterationBytesGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFDoublePipelineIterationBytesGenerator.java new file mode 100644 index 0000000..6115a1a --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFDoublePipelineIterationBytesGenerator.java @@ -0,0 +1,181 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.MacDerivationFunction; +import org.bouncycastle.crypto.params.KDFDoublePipelineIterationParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * This KDF has been defined by the publicly available NIST SP 800-108 specification. + */ +public class KDFDoublePipelineIterationBytesGenerator + implements MacDerivationFunction +{ + + private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE); + private static final BigInteger TWO = BigInteger.valueOf(2); + + // please refer to the standard for the meaning of the variable names + // all field lengths are in bytes, not in bits as specified by the standard + + // fields set by the constructor + private final Mac prf; + private final int h; + + // fields set by init + private byte[] fixedInputData; + private int maxSizeExcl; + // ios is i defined as an octet string (the binary representation) + private byte[] ios; + private boolean useCounter; + + // operational + private int generatedBytes; + // k is used as buffer for all K(i) values + private byte[] a; + private byte[] k; + + + public KDFDoublePipelineIterationBytesGenerator(Mac prf) + { + this.prf = prf; + this.h = prf.getMacSize(); + this.a = new byte[h]; + this.k = new byte[h]; + } + + public void init(DerivationParameters params) + { + if (!(params instanceof KDFDoublePipelineIterationParameters)) + { + throw new IllegalArgumentException("Wrong type of arguments given"); + } + + KDFDoublePipelineIterationParameters dpiParams = (KDFDoublePipelineIterationParameters)params; + + // --- init mac based PRF --- + + this.prf.init(new KeyParameter(dpiParams.getKI())); + + // --- set arguments --- + + this.fixedInputData = dpiParams.getFixedInputData(); + + int r = dpiParams.getR(); + this.ios = new byte[r / 8]; + + if (dpiParams.useCounter()) + { + // this is more conservative than the spec + BigInteger maxSize = TWO.pow(r).multiply(BigInteger.valueOf(h)); + this.maxSizeExcl = maxSize.compareTo(INTEGER_MAX) == 1 ? + Integer.MAX_VALUE : maxSize.intValue(); + } + else + { + this.maxSizeExcl = Integer.MAX_VALUE; + } + + this.useCounter = dpiParams.useCounter(); + + // --- set operational state --- + + generatedBytes = 0; + } + + public Mac getMac() + { + return prf; + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + + int generatedBytesAfter = generatedBytes + len; + if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl) + { + throw new DataLengthException( + "Current KDFCTR may only be used for " + maxSizeExcl + " bytes"); + } + + if (generatedBytes % h == 0) + { + generateNext(); + } + + // copy what is left in the currentT (1..hash + int toGenerate = len; + int posInK = generatedBytes % h; + int leftInK = h - generatedBytes % h; + int toCopy = Math.min(leftInK, toGenerate); + System.arraycopy(k, posInK, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + + while (toGenerate > 0) + { + generateNext(); + toCopy = Math.min(h, toGenerate); + System.arraycopy(k, 0, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + } + + return len; + } + + private void generateNext() + { + + if (generatedBytes == 0) + { + // --- step 4 --- + prf.update(fixedInputData, 0, fixedInputData.length); + prf.doFinal(a, 0); + } + else + { + // --- step 5a --- + prf.update(a, 0, a.length); + prf.doFinal(a, 0); + } + + // --- step 5b --- + prf.update(a, 0, a.length); + + if (useCounter) + { + int i = generatedBytes / h + 1; + + // encode i into counter buffer + switch (ios.length) + { + case 4: + ios[0] = (byte)(i >>> 24); + // fall through + case 3: + ios[ios.length - 3] = (byte)(i >>> 16); + // fall through + case 2: + ios[ios.length - 2] = (byte)(i >>> 8); + // fall through + case 1: + ios[ios.length - 1] = (byte)i; + break; + default: + throw new IllegalStateException("Unsupported size of counter i"); + } + prf.update(ios, 0, ios.length); + } + + prf.update(fixedInputData, 0, fixedInputData.length); + prf.doFinal(k, 0); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFFeedbackBytesGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFFeedbackBytesGenerator.java new file mode 100644 index 0000000..6003037 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/KDFFeedbackBytesGenerator.java @@ -0,0 +1,175 @@ +package org.bouncycastle.crypto.generators; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.MacDerivationFunction; +import org.bouncycastle.crypto.params.KDFFeedbackParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * This KDF has been defined by the publicly available NIST SP 800-108 specification. + */ +public class KDFFeedbackBytesGenerator + implements MacDerivationFunction +{ + + private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE); + private static final BigInteger TWO = BigInteger.valueOf(2); + + // please refer to the standard for the meaning of the variable names + // all field lengths are in bytes, not in bits as specified by the standard + + // fields set by the constructor + private final Mac prf; + private final int h; + + // fields set by init + private byte[] fixedInputData; + private int maxSizeExcl; + // ios is i defined as an octet string (the binary representation) + private byte[] ios; + private byte[] iv; + private boolean useCounter; + + // operational + private int generatedBytes; + // k is used as buffer for all K(i) values + private byte[] k; + + + public KDFFeedbackBytesGenerator(Mac prf) + { + this.prf = prf; + this.h = prf.getMacSize(); + this.k = new byte[h]; + } + + public void init(DerivationParameters params) + { + if (!(params instanceof KDFFeedbackParameters)) + { + throw new IllegalArgumentException("Wrong type of arguments given"); + } + + KDFFeedbackParameters feedbackParams = (KDFFeedbackParameters)params; + + // --- init mac based PRF --- + + this.prf.init(new KeyParameter(feedbackParams.getKI())); + + // --- set arguments --- + + this.fixedInputData = feedbackParams.getFixedInputData(); + + int r = feedbackParams.getR(); + this.ios = new byte[r / 8]; + + if (feedbackParams.useCounter()) + { + // this is more conservative than the spec + BigInteger maxSize = TWO.pow(r).multiply(BigInteger.valueOf(h)); + this.maxSizeExcl = maxSize.compareTo(INTEGER_MAX) == 1 ? + Integer.MAX_VALUE : maxSize.intValue(); + } + else + { + this.maxSizeExcl = Integer.MAX_VALUE; + } + + this.iv = feedbackParams.getIV(); + this.useCounter = feedbackParams.useCounter(); + + // --- set operational state --- + + generatedBytes = 0; + } + + public Mac getMac() + { + return prf; + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + + int generatedBytesAfter = generatedBytes + len; + if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl) + { + throw new DataLengthException( + "Current KDFCTR may only be used for " + maxSizeExcl + " bytes"); + } + + if (generatedBytes % h == 0) + { + generateNext(); + } + + // copy what is left in the currentT (1..hash + int toGenerate = len; + int posInK = generatedBytes % h; + int leftInK = h - generatedBytes % h; + int toCopy = Math.min(leftInK, toGenerate); + System.arraycopy(k, posInK, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + + while (toGenerate > 0) + { + generateNext(); + toCopy = Math.min(h, toGenerate); + System.arraycopy(k, 0, out, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + } + + return len; + } + + private void generateNext() + { + + // TODO enable IV + if (generatedBytes == 0) + { + prf.update(iv, 0, iv.length); + } + else + { + prf.update(k, 0, k.length); + } + + if (useCounter) + { + int i = generatedBytes / h + 1; + + // encode i into counter buffer + switch (ios.length) + { + case 4: + ios[0] = (byte)(i >>> 24); + // fall through + case 3: + ios[ios.length - 3] = (byte)(i >>> 16); + // fall through + case 2: + ios[ios.length - 2] = (byte)(i >>> 8); + // fall through + case 1: + ios[ios.length - 1] = (byte)i; + break; + default: + throw new IllegalStateException("Unsupported size of counter i"); + } + prf.update(ios, 0, ios.length); + } + + prf.update(fixedInputData, 0, fixedInputData.length); + prf.doFinal(k, 0); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java index 640ead4..0954d48 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java @@ -58,7 +58,7 @@ public class PKCS5S2ParametersGenerator hMac.doFinal(state, 0); System.arraycopy(state, 0, out, outOff, state.length); - + for (int count = 1; count < c; count++) { hMac.update(state, 0, state.length); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/Poly1305KeyGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/Poly1305KeyGenerator.java new file mode 100644 index 0000000..9165973 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/Poly1305KeyGenerator.java @@ -0,0 +1,117 @@ +package org.bouncycastle.crypto.generators; + +import org.bouncycastle.crypto.CipherKeyGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.macs.Poly1305; + +/** + * Generates keys for the Poly1305 MAC. + * <p> + * Poly1305 keys are 256 bit keys consisting of a 128 bit secret key used for the underlying block + * cipher followed by a 128 bit {@code r} value used for the polynomial portion of the Mac. <br> + * The {@code r} value has a specific format with some bits required to be cleared, resulting in an + * effective 106 bit key. <br> + * A separately generated 256 bit key can be modified to fit the Poly1305 key format by using the + * {@link #clamp(byte[])} method to clear the required bits. + * + * @see Poly1305 + */ +public class Poly1305KeyGenerator + extends CipherKeyGenerator +{ + private static final byte R_MASK_LOW_2 = (byte)0xFC; + private static final byte R_MASK_HIGH_4 = (byte)0x0F; + + /** + * Initialises the key generator.<br> + * Poly1305 keys are always 256 bits, so the key length in the provided parameters is ignored. + */ + public void init(KeyGenerationParameters param) + { + // Poly1305 keys are always 256 bits + super.init(new KeyGenerationParameters(param.getRandom(), 256)); + } + + /** + * Generates a 256 bit key in the format required for Poly1305 - e.g. + * <code>k[0] ... k[15], r[0] ... r[15]</code> with the required bits in <code>r</code> cleared + * as per {@link #clamp(byte[])}. + */ + public byte[] generateKey() + { + final byte[] key = super.generateKey(); + clamp(key); + return key; + } + + /** + * Modifies an existing 32 byte key value to comply with the requirements of the Poly1305 key by + * clearing required bits in the <code>r</code> (second 16 bytes) portion of the key.<br> + * Specifically: + * <ul> + * <li>r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15})</li> + * <li>r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252})</li> + * </ul> + * + * @param a 32 byte key value <code>k[0] ... k[15], r[0] ... r[15]</code> + */ + public static void clamp(byte[] key) + { + /* + * Key is k[0] ... k[15], r[0] ... r[15] as per poly1305_aes_clamp in ref impl. + */ + if (key.length != 32) + { + throw new IllegalArgumentException("Poly1305 key must be 256 bits."); + } + + /* + * r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15}) + */ + key[19] &= R_MASK_HIGH_4; + key[23] &= R_MASK_HIGH_4; + key[27] &= R_MASK_HIGH_4; + key[31] &= R_MASK_HIGH_4; + + /* + * r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252}). + */ + key[20] &= R_MASK_LOW_2; + key[24] &= R_MASK_LOW_2; + key[28] &= R_MASK_LOW_2; + } + + /** + * Checks a 32 byte key for compliance with the Poly1305 key requirements, e.g. + * <code>k[0] ... k[15], r[0] ... r[15]</code> with the required bits in <code>r</code> cleared + * as per {@link #clamp(byte[])}. + * + * @throws IllegalArgumentException if the key is of the wrong length, or has invalid bits set + * in the <code>r</code> portion of the key. + */ + public static void checkKey(byte[] key) + { + if (key.length != 32) + { + throw new IllegalArgumentException("Poly1305 key must be 256 bits."); + } + + checkMask(key[19], R_MASK_HIGH_4); + checkMask(key[23], R_MASK_HIGH_4); + checkMask(key[27], R_MASK_HIGH_4); + checkMask(key[31], R_MASK_HIGH_4); + + checkMask(key[20], R_MASK_LOW_2); + checkMask(key[24], R_MASK_LOW_2); + checkMask(key[28], R_MASK_LOW_2); + } + + private static void checkMask(byte b, byte mask) + { + if ((b & (~mask)) != 0) + { + throw new IllegalArgumentException("Invalid format for r portion of Poly1305 key."); + } + } + +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/generators/package.html deleted file mode 100644 index 9d73ce3..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Generators for keys, key pairs and password based encryption algorithms. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java b/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java index bb09a76..93b04e9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java @@ -5,15 +5,15 @@ import java.io.IOException; import java.io.InputStream; import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.modes.AEADBlockCipher; /** - * A CipherInputStream is composed of an InputStream and a BufferedBlockCipher so - * that read() methods return data that are read in from the - * underlying InputStream but have been additionally processed by the - * Cipher. The Cipher must be fully initialized before being used by - * a CipherInputStream. - * <p> + * A CipherInputStream is composed of an InputStream and a cipher so that read() methods return data + * that are read in from the underlying InputStream but have been additionally processed by the + * Cipher. The cipher must be fully initialized before being used by a CipherInputStream. + * <p/> * For example, if the Cipher is initialized for decryption, the * CipherInputStream will attempt to read in data and decrypt them, * before returning the decrypted data. @@ -23,9 +23,10 @@ public class CipherInputStream { private BufferedBlockCipher bufferedBlockCipher; private StreamCipher streamCipher; + private AEADBlockCipher aeadBlockCipher; - private byte[] buf; - private byte[] inBuf; + private final byte[] buf; + private final byte[] inBuf; private int bufOff; private int maxBuf; @@ -62,95 +63,116 @@ public class CipherInputStream } /** - * grab the next chunk of input from the underlying input stream + * Constructs a CipherInputStream from an InputStream and an AEADBlockCipher. + */ + public CipherInputStream(InputStream is, AEADBlockCipher cipher) + { + super(is); + + this.aeadBlockCipher = cipher; + + buf = new byte[cipher.getOutputSize(INPUT_BUF_SIZE)]; + inBuf = new byte[INPUT_BUF_SIZE]; + } + + /** + * Read data from underlying stream and process with cipher until end of stream or some data is + * available after cipher processing. + * + * @return -1 to indicate end of stream, or the number of bytes (> 0) available. */ private int nextChunk() throws IOException { - int available = super.available(); - - // must always try to read 1 byte! - // some buggy InputStreams return < 0! - if (available <= 0) + if (finalized) { - available = 1; + return -1; } - if (available > inBuf.length) - { - available = super.read(inBuf, 0, inBuf.length); - } - else - { - available = super.read(inBuf, 0, available); - } + bufOff = 0; + maxBuf = 0; - if (available < 0) + // Keep reading until EOF or cipher processing produces data + while (maxBuf == 0) { - if (finalized) + int read = in.read(inBuf); + if (read == -1) { - return -1; + finaliseCipher(); + if (maxBuf == 0) + { + return -1; + } + return maxBuf; } try { if (bufferedBlockCipher != null) { - maxBuf = bufferedBlockCipher.doFinal(buf, 0); + maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, read, buf, 0); + } + else if (aeadBlockCipher != null) + { + maxBuf = aeadBlockCipher.processBytes(inBuf, 0, read, buf, 0); } else { - maxBuf = 0; // a stream cipher + streamCipher.processBytes(inBuf, 0, read, buf, 0); + maxBuf = read; } } catch (Exception e) { - throw new IOException("error processing stream: " + e.toString()); + throw new IOException("Error processing stream " + e); } + } + return maxBuf; + } - bufOff = 0; - + private void finaliseCipher() + throws IOException + { + try + { finalized = true; - - if (bufOff == maxBuf) + if (bufferedBlockCipher != null) { - return -1; + maxBuf = bufferedBlockCipher.doFinal(buf, 0); } - } - else - { - bufOff = 0; - - try + else if (aeadBlockCipher != null) { - if (bufferedBlockCipher != null) - { - maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, available, buf, 0); - } - else - { - streamCipher.processBytes(inBuf, 0, available, buf, 0); - maxBuf = available; - } + maxBuf = aeadBlockCipher.doFinal(buf, 0); } - catch (Exception e) + else { - throw new IOException("error processing stream: " + e.toString()); - } - - if (maxBuf == 0) // not enough bytes read for first block... - { - return nextChunk(); + maxBuf = 0; // a stream cipher } } - - return maxBuf; + catch (final InvalidCipherTextException e) + { + throw new InvalidCipherTextIOException("Error finalising cipher", e); + } + catch (Exception e) + { + throw new IOException("Error finalising cipher " + e); + } } + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and returns the next available byte. + * <p/> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ public int read() throws IOException { - if (bufOff == maxBuf) + if (bufOff >= maxBuf) { if (nextChunk() < 0) { @@ -161,6 +183,19 @@ public class CipherInputStream return buf[bufOff++] & 0xff; } + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and then returns up to <code>b.length</code> bytes in the provided array. + * <p/> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or <code>-1</code> if there is no + * more data because the end of the stream has been reached. + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ public int read( byte[] b) throws IOException @@ -168,13 +203,28 @@ public class CipherInputStream return read(b, 0, b.length); } + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and then returns up to <code>len</code> bytes in the provided array. + * <p/> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array <code>b</code> + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or <code>-1</code> if there is no + * more data because the end of the stream has been reached. + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ public int read( byte[] b, int off, int len) throws IOException { - if (bufOff == maxBuf) + if (bufOff >= maxBuf) { if (nextChunk() < 0) { @@ -182,22 +232,10 @@ public class CipherInputStream } } - int available = maxBuf - bufOff; - - if (len > available) - { - System.arraycopy(buf, bufOff, b, off, available); - bufOff = maxBuf; - - return available; - } - else - { - System.arraycopy(buf, bufOff, b, off, len); - bufOff += len; - - return len; - } + int toSupply = Math.min(len, available()); + System.arraycopy(buf, bufOff, b, off, toSupply); + bufOff += toSupply; + return toSupply; } public long skip( @@ -209,36 +247,55 @@ public class CipherInputStream return 0; } - int available = maxBuf - bufOff; + int skip = (int)Math.min(n, available()); + bufOff += skip; + return skip; + } - if (n > available) - { - bufOff = maxBuf; + public int available() + throws IOException + { + return maxBuf - bufOff; + } - return available; + /** + * Closes the underlying input stream and finalises the processing of the data by the cipher. + * + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public void close() + throws IOException + { + try + { + in.close(); } - else + finally { - bufOff += (int)n; - - return (int)n; + if (!finalized) + { + // Reset the cipher, discarding any data buffered in it + // Errors in cipher finalisation trump I/O error closing input + finaliseCipher(); + } } + maxBuf = bufOff = 0; } - public int available() - throws IOException + public void mark(int readlimit) { - return maxBuf - bufOff; } - public void close() + public void reset() throws IOException { - super.close(); } public boolean markSupported() { return false; } + } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java b/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java index 17a7b6d..9beb5b9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/io/CipherOutputStream.java @@ -5,15 +5,27 @@ import java.io.IOException; import java.io.OutputStream; import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +/** + * A CipherOutputStream is composed of an OutputStream and a cipher so that write() methods process + * the written data with the cipher, and the output of the cipher is in turn written to the + * underlying OutputStream. The cipher must be fully initialized before being used by a + * CipherInputStream. + * <p/> + * For example, if the cipher is initialized for encryption, the CipherOutputStream will encrypt the + * data before writing the encrypted data to the underlying stream. + */ public class CipherOutputStream extends FilterOutputStream { private BufferedBlockCipher bufferedBlockCipher; private StreamCipher streamCipher; + private AEADBlockCipher aeadBlockCipher; - private byte[] oneByte = new byte[1]; + private final byte[] oneByte = new byte[1]; private byte[] buf; /** @@ -26,7 +38,6 @@ public class CipherOutputStream { super(os); this.bufferedBlockCipher = cipher; - this.buf = new byte[cipher.getBlockSize()]; } /** @@ -42,10 +53,19 @@ public class CipherOutputStream } /** + * Constructs a CipherOutputStream from an OutputStream and a AEADBlockCipher. + */ + public CipherOutputStream(OutputStream os, AEADBlockCipher cipher) + { + super(os); + this.aeadBlockCipher = cipher; + } + + /** * Writes the specified byte to this output stream. * * @param b the <code>byte</code>. - * @exception java.io.IOException if an I/O error occurs. + * @throws java.io.IOException if an I/O error occurs. */ public void write( int b) @@ -53,32 +73,27 @@ public class CipherOutputStream { oneByte[0] = (byte)b; - if (bufferedBlockCipher != null) + if (streamCipher != null) { - int len = bufferedBlockCipher.processBytes(oneByte, 0, 1, buf, 0); - - if (len != 0) - { - out.write(buf, 0, len); - } + out.write(streamCipher.returnByte((byte)b)); } else { - out.write(streamCipher.returnByte((byte)b)); + write(oneByte, 0, 1); } } /** * Writes <code>b.length</code> bytes from the specified byte array * to this output stream. - * <p> + * <p/> * The <code>write</code> method of * <code>CipherOutputStream</code> calls the <code>write</code> * method of three arguments with the three arguments * <code>b</code>, <code>0</code>, and <code>b.length</code>. * * @param b the data. - * @exception java.io.IOException if an I/O error occurs. + * @throws java.io.IOException if an I/O error occurs. * @see #write(byte[], int, int) */ public void write( @@ -92,10 +107,10 @@ public class CipherOutputStream * Writes <code>len</code> bytes from the specified byte array * starting at offset <code>off</code> to this output stream. * - * @param b the data. + * @param b the data. * @param off the start offset in the data. * @param len the number of bytes to write. - * @exception java.io.IOException if an I/O error occurs. + * @throws java.io.IOException if an I/O error occurs. */ public void write( byte[] b, @@ -103,10 +118,10 @@ public class CipherOutputStream int len) throws IOException { + ensureCapacity(len); + if (bufferedBlockCipher != null) { - byte[] buf = new byte[bufferedBlockCipher.getOutputSize(len)]; - int outLen = bufferedBlockCipher.processBytes(b, off, len, buf, 0); if (outLen != 0) @@ -114,10 +129,17 @@ public class CipherOutputStream out.write(buf, 0, outLen); } } - else + else if (aeadBlockCipher != null) { - byte[] buf = new byte[len]; + int outLen = aeadBlockCipher.processBytes(b, off, len, buf, 0); + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + else + { streamCipher.processBytes(b, off, len, buf, 0); out.write(buf, 0, len); @@ -125,49 +147,78 @@ public class CipherOutputStream } /** + * Ensure the ciphertext buffer has space sufficient to accept an upcoming output. + * + * @param outputSize the size of the pending update. + */ + private void ensureCapacity(int outputSize) + { + // This overestimates buffer on updates for AEAD/padded, but keeps it simple. + int bufLen; + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getOutputSize(outputSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getOutputSize(outputSize); + } + else + { + bufLen = outputSize; + } + if ((buf == null) || (buf.length < bufLen)) + { + buf = new byte[bufLen]; + } + } + + /** * Flushes this output stream by forcing any buffered output bytes * that have already been processed by the encapsulated cipher object * to be written out. - * - * <p> + * <p/> + * <p/> * Any bytes buffered by the encapsulated cipher * and waiting to be processed by it will not be written out. For example, * if the encapsulated cipher is a block cipher, and the total number of * bytes written using one of the <code>write</code> methods is less than * the cipher's block size, no bytes will be written out. * - * @exception java.io.IOException if an I/O error occurs. + * @throws java.io.IOException if an I/O error occurs. */ public void flush() throws IOException { - super.flush(); + out.flush(); } /** * Closes this output stream and releases any system resources * associated with this stream. - * <p> + * <p/> * This method invokes the <code>doFinal</code> method of the encapsulated * cipher object, which causes any bytes buffered by the encapsulated * cipher to be processed. The result is written out by calling the * <code>flush</code> method of this output stream. - * <p> + * <p/> * This method resets the encapsulated cipher object to its initial state * and calls the <code>close</code> method of the underlying output * stream. * - * @exception java.io.IOException if an I/O error occurs. + * @throws java.io.IOException if an I/O error occurs. + * @throws InvalidCipherTextIOException if the data written to this stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). */ public void close() throws IOException { + ensureCapacity(0); + IOException error = null; try { if (bufferedBlockCipher != null) { - byte[] buf = new byte[bufferedBlockCipher.getOutputSize(0)]; - int outLen = bufferedBlockCipher.doFinal(buf, 0); if (outLen != 0) @@ -175,14 +226,41 @@ public class CipherOutputStream out.write(buf, 0, outLen); } } + else if (aeadBlockCipher != null) + { + int outLen = aeadBlockCipher.doFinal(buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + } + catch (final InvalidCipherTextException e) + { + error = new InvalidCipherTextIOException("Error finalising cipher data", e); } catch (Exception e) { - throw new IOException("Error closing stream: " + e.toString()); + error = new IOException("Error closing stream: " + e); } - flush(); - - super.close(); + try + { + flush(); + out.close(); + } + catch (IOException e) + { + // Invalid ciphertext takes precedence over close error + if (error == null) + { + error = e; + } + } + if (error != null) + { + throw error; + } } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/io/InvalidCipherTextIOException.java b/bcprov/src/main/java/org/bouncycastle/crypto/io/InvalidCipherTextIOException.java new file mode 100644 index 0000000..b601d4c --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/io/InvalidCipherTextIOException.java @@ -0,0 +1,28 @@ +package org.bouncycastle.crypto.io; + +import java.io.IOException; + +/** + * {@link IOException} wrapper around an exception indicating an invalid ciphertext, such as in + * authentication failure during finalisation of an AEAD cipher. For use in streams that need to + * expose invalid ciphertext errors. + */ +public class InvalidCipherTextIOException + extends IOException +{ + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public InvalidCipherTextIOException(String message, Throwable cause) + { + super(message); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/io/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/io/package.html deleted file mode 100644 index f2c9e40..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/io/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Classes for doing "enhanced" I/O with Digests and MACs. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java b/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java index f4dfc6e..82ac20c 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java @@ -6,11 +6,13 @@ import java.security.SecureRandom; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DerivationFunction; import org.bouncycastle.crypto.KeyEncapsulation; +import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.KDFParameters; import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.BigIntegers; @@ -106,33 +108,34 @@ public class ECIESKeyEncapsulation throw new IllegalArgumentException("Public key required for encryption"); } - BigInteger n = key.getParameters().getN(); - BigInteger h = key.getParameters().getH(); + ECPublicKeyParameters ecPubKey = (ECPublicKeyParameters)key; + ECDomainParameters ecParams = ecPubKey.getParameters(); + ECCurve curve = ecParams.getCurve(); + BigInteger n = ecParams.getN(); + BigInteger h = ecParams.getH(); // Generate the ephemeral key pair BigInteger r = BigIntegers.createRandomInRange(ONE, n, rnd); - ECPoint gTilde = key.getParameters().getG().multiply(r); + + // Compute the static-ephemeral key agreement + BigInteger rPrime = CofactorMode ? r.multiply(h).mod(n) : r; + + ECPoint[] ghTilde = new ECPoint[]{ + ecParams.getG().multiply(r), + ecPubKey.getQ().multiply(rPrime) + }; + + // NOTE: More efficient than normalizing each individually + curve.normalizeAll(ghTilde); + + ECPoint gTilde = ghTilde[0], hTilde = ghTilde[1]; // Encode the ephemeral public key byte[] C = gTilde.getEncoded(); System.arraycopy(C, 0, out, outOff, C.length); - // Compute the static-ephemeral key agreement - BigInteger rPrime; - if (CofactorMode) - { - rPrime = r.multiply(h).mod(n); - } - else - { - rPrime = r; - } - - ECPoint hTilde = ((ECPublicKeyParameters)key).getQ().multiply(rPrime); - // Encode the shared secret value - int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8; - byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger()); + byte[] PEH = hTilde.getAffineXCoord().getEncoded(); // Initialise the KDF byte[] kdfInput; @@ -186,40 +189,36 @@ public class ECIESKeyEncapsulation throw new IllegalArgumentException("Private key required for encryption"); } - BigInteger n = key.getParameters().getN(); - BigInteger h = key.getParameters().getH(); + ECPrivateKeyParameters ecPrivKey = (ECPrivateKeyParameters)key; + ECDomainParameters ecParams = ecPrivKey.getParameters(); + ECCurve curve = ecParams.getCurve(); + BigInteger n = ecParams.getN(); + BigInteger h = ecParams.getH(); // Decode the ephemeral public key byte[] C = new byte[inLen]; System.arraycopy(in, inOff, C, 0, inLen); - ECPoint gTilde = key.getParameters().getCurve().decodePoint(C); + + // NOTE: Decoded points are already normalized (i.e in affine form) + ECPoint gTilde = curve.decodePoint(C); // Compute the static-ephemeral key agreement - ECPoint gHat; + ECPoint gHat = gTilde; if ((CofactorMode) || (OldCofactorMode)) { - gHat = gTilde.multiply(h); - } - else - { - gHat = gTilde; + gHat = gHat.multiply(h); } - BigInteger xHat; + BigInteger xHat = ecPrivKey.getD(); if (CofactorMode) { - xHat = ((ECPrivateKeyParameters)key).getD().multiply(h.modInverse(n)).mod(n); - } - else - { - xHat = ((ECPrivateKeyParameters)key).getD(); + xHat = xHat.multiply(h.modInverse(n)).mod(n); } - ECPoint hTilde = gHat.multiply(xHat); + ECPoint hTilde = gHat.multiply(xHat).normalize(); // Encode the shared secret value - int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8; - byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger()); + byte[] PEH = hTilde.getAffineXCoord().getEncoded(); // Initialise the KDF byte[] kdfInput; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/kems/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/kems/package.html deleted file mode 100644 index a5174b3..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/kems/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -The Key Encapsulation Mechanisms (KEMs) from ISO 18033-2. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/CMac.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/CMac.java index 8a3b5bb..1aa8ede 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/macs/CMac.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/CMac.java @@ -5,6 +5,7 @@ import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.ISO7816d4Padding; +import org.bouncycastle.crypto.params.KeyParameter; /** * CMAC - as specified at www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html @@ -103,25 +104,36 @@ public class CMac implements Mac return cipher.getAlgorithmName(); } - private static byte[] doubleLu(byte[] in) + private static int shiftLeft(byte[] block, byte[] output) { - int FirstBit = (in[0] & 0xFF) >> 7; - byte[] ret = new byte[in.length]; - for (int i = 0; i < in.length - 1; i++) - { - ret[i] = (byte)((in[i] << 1) + ((in[i + 1] & 0xFF) >> 7)); - } - ret[in.length - 1] = (byte)(in[in.length - 1] << 1); - if (FirstBit == 1) + int i = block.length; + int bit = 0; + while (--i >= 0) { - ret[in.length - 1] ^= in.length == 16 ? CONSTANT_128 : CONSTANT_64; + int b = block[i] & 0xff; + output[i] = (byte)((b << 1) | bit); + bit = (b >>> 7) & 1; } + return bit; + } + + private static byte[] doubleLu(byte[] in) + { + byte[] ret = new byte[in.length]; + int carry = shiftLeft(in, ret); + int xor = 0xff & (in.length == 16 ? CONSTANT_128 : CONSTANT_64); + + /* + * NOTE: This construction is an attempt at a constant-time implementation. + */ + ret[in.length - 1] ^= (xor >>> ((1 - carry) << 3)); + return ret; } public void init(CipherParameters params) { - if (params != null) + if (params instanceof KeyParameter) { cipher.init(true, params); @@ -130,6 +142,10 @@ public class CMac implements Mac cipher.processBlock(ZEROES, 0, L, 0); Lu = doubleLu(L); Lu2 = doubleLu(Lu); + } else if (params != null) + { + // CMAC mode does not permit IV to underlying CBC mode + throw new IllegalArgumentException("CMac mode only permits key to be set."); } reset(); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/Poly1305.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Poly1305.java new file mode 100644 index 0000000..150eb61 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Poly1305.java @@ -0,0 +1,279 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.generators.Poly1305KeyGenerator; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.util.Pack; + +/** + * Poly1305 message authentication code, designed by D. J. Bernstein. + * <p> + * 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. + * <p> + * 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). + * @see Poly1305KeyGenerator + */ +public class Poly1305 + implements Mac +{ + private static final int BLOCK_SIZE = 16; + + private final BlockCipher cipher; + + private final byte[] singleByte = new byte[1]; + + // Initialised state + + /** Polynomial key */ + private int r0, r1, r2, r3, r4; + + /** Precomputed 5 * r[1..4] */ + private int s1, s2, s3, s4; + + /** Encrypted nonce */ + private int k0, k1, k2, k3; + + // Accumulating state + + /** Current block of buffered input */ + private final byte[] currentBlock = new byte[BLOCK_SIZE]; + + /** Current offset in input buffer */ + private int currentBlockOffset = 0; + + /** Polynomial accumulator */ + private int h0, h1, h2, h3, h4; + + /** + * Constructs a Poly1305 MAC, using a 128 bit block cipher. + */ + public Poly1305(final BlockCipher cipher) + { + if (cipher.getBlockSize() != BLOCK_SIZE) + { + throw new IllegalArgumentException("Poly1305 requires a 128 bit block cipher."); + } + this.cipher = cipher; + } + + /** + * Initialises the Poly1305 MAC. + * + * @param 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}. + */ + public void init(final CipherParameters params) + throws IllegalArgumentException + { + final byte[] nonce; + final byte[] key; + if ((params instanceof ParametersWithIV) && ((ParametersWithIV)params).getParameters() instanceof KeyParameter) + { + nonce = ((ParametersWithIV)params).getIV(); + key = ((KeyParameter)((ParametersWithIV)params).getParameters()).getKey(); + } + else + { + throw new IllegalArgumentException("Poly1305 requires a key and and IV."); + } + + setKey(key, nonce); + reset(); + } + + private void setKey(final byte[] key, final byte[] nonce) + { + if (nonce.length != BLOCK_SIZE) + { + throw new IllegalArgumentException("Poly1305 requires a 128 bit IV."); + } + Poly1305KeyGenerator.checkKey(key); + + // Extract r portion of key + int t0 = Pack.littleEndianToInt(key, BLOCK_SIZE + 0); + int t1 = Pack.littleEndianToInt(key, BLOCK_SIZE + 4); + int t2 = Pack.littleEndianToInt(key, BLOCK_SIZE + 8); + int t3 = Pack.littleEndianToInt(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 + final byte[] cipherKey = new byte[BLOCK_SIZE]; + System.arraycopy(key, 0, cipherKey, 0, cipherKey.length); + + cipher.init(true, new KeyParameter(cipherKey)); + cipher.processBlock(nonce, 0, cipherKey, 0); + + k0 = Pack.littleEndianToInt(cipherKey, 0); + k1 = Pack.littleEndianToInt(cipherKey, 4); + k2 = Pack.littleEndianToInt(cipherKey, 8); + k3 = Pack.littleEndianToInt(cipherKey, 12); + } + + public String getAlgorithmName() + { + return "Poly1305-" + cipher.getAlgorithmName(); + } + + public int getMacSize() + { + return BLOCK_SIZE; + } + + public void update(final byte in) + throws IllegalStateException + { + singleByte[0] = in; + update(singleByte, 0, 1); + } + + public void update(final byte[] in, final int inOff, final int len) + throws DataLengthException, + IllegalStateException + { + int copied = 0; + while (len > copied) + { + if (currentBlockOffset == BLOCK_SIZE) + { + processBlock(); + currentBlockOffset = 0; + } + + int toCopy = Math.min((len - copied), BLOCK_SIZE - currentBlockOffset); + System.arraycopy(in, 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; + } + } + + final long t0 = 0xffffffffL & Pack.littleEndianToInt(currentBlock, 0); + final long t1 = 0xffffffffL & Pack.littleEndianToInt(currentBlock, 4); + final long t2 = 0xffffffffL & Pack.littleEndianToInt(currentBlock, 8); + final long t3 = 0xffffffffL & Pack.littleEndianToInt(currentBlock, 12); + + h0 += t0 & 0x3ffffff; + h1 += (((t1 << 32) | t0) >>> 26) & 0x3ffffff; + h2 += (((t2 << 32) | t1) >>> 20) & 0x3ffffff; + h3 += (((t3 << 32) | t2) >>> 14) & 0x3ffffff; + h4 += (t3 >>> 8); + + if (currentBlockOffset == BLOCK_SIZE) + { + h4 += (1 << 24); + } + + long tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + long tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + long tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + long tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + long tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + long b; + h0 = (int)tp0 & 0x3ffffff; b = (tp0 >>> 26); + tp1 += b; h1 = (int)tp1 & 0x3ffffff; b = ((tp1 >>> 26) & 0xffffffff); + tp2 += b; h2 = (int)tp2 & 0x3ffffff; b = ((tp2 >>> 26) & 0xffffffff); + tp3 += b; h3 = (int)tp3 & 0x3ffffff; b = (tp3 >>> 26); + tp4 += b; h4 = (int)tp4 & 0x3ffffff; b = (tp4 >>> 26); + h0 += b * 5; + } + + public int doFinal(final byte[] out, final int outOff) + throws DataLengthException, + IllegalStateException + { + if (outOff + BLOCK_SIZE > out.length) + { + throw new DataLengthException("Output buffer is too short."); + } + + if (currentBlockOffset > 0) + { + // Process padded final block + processBlock(); + } + + long f0, f1, f2, f3; + + int 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; + + int 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; + int 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)) & 0xffffffffl) + (0xffffffffL & k0); + f1 = (((h1 >>> 6 ) | (h2 << 20)) & 0xffffffffl) + (0xffffffffL & k1); + f2 = (((h2 >>> 12) | (h3 << 14)) & 0xffffffffl) + (0xffffffffL & k2); + f3 = (((h3 >>> 18) | (h4 << 8 )) & 0xffffffffl) + (0xffffffffL & k3); + + Pack.intToLittleEndian((int)f0, out, outOff); + f1 += (f0 >>> 32); + Pack.intToLittleEndian((int)f1, out, outOff + 4); + f2 += (f1 >>> 32); + Pack.intToLittleEndian((int)f2, out, outOff + 8); + f3 += (f2 >>> 32); + Pack.intToLittleEndian((int)f3, out, outOff + 12); + + reset(); + return BLOCK_SIZE; + } + + public void reset() + { + currentBlockOffset = 0; + + h0 = h1 = h2 = h3 = h4 = 0; + } + + private static final long mul32x32_64(int i1, int i2) + { + return ((long)i1) * i2; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/SkeinMac.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/SkeinMac.java new file mode 100644 index 0000000..6097247 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/SkeinMac.java @@ -0,0 +1,119 @@ +package org.bouncycastle.crypto.macs; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.digests.SkeinEngine; +import org.bouncycastle.crypto.engines.ThreefishEngine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.SkeinParameters; + +/** + * Implementation of the Skein parameterised MAC function in 256, 512 and 1024 bit block sizes, + * based on the {@link ThreefishEngine Threefish} tweakable block cipher. + * <p/> + * This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + * competition in October 2010. + * <p/> + * Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + * Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + * <p/> + * + * @see SkeinEngine + * @see SkeinParameters + */ +public class SkeinMac + implements Mac +{ + /** + * 256 bit block size - Skein MAC-256 + */ + public static final int SKEIN_256 = SkeinEngine.SKEIN_256; + /** + * 512 bit block size - Skein MAC-512 + */ + public static final int SKEIN_512 = SkeinEngine.SKEIN_512; + /** + * 1024 bit block size - Skein MAC-1024 + */ + public static final int SKEIN_1024 = SkeinEngine.SKEIN_1024; + + private SkeinEngine engine; + + /** + * Constructs a Skein MAC with an internal state size and output size. + * + * @param stateSizeBits the internal state size in bits - one of {@link #SKEIN_256}, {@link #SKEIN_512} or + * {@link #SKEIN_1024}. + * @param digestSizeBits the output/MAC size to produce in bits, which must be an integral number of bytes. + */ + public SkeinMac(int stateSizeBits, int digestSizeBits) + { + this.engine = new SkeinEngine(stateSizeBits, digestSizeBits); + } + + public SkeinMac(SkeinMac mac) + { + this.engine = new SkeinEngine(mac.engine); + } + + public String getAlgorithmName() + { + return "Skein-MAC-" + (engine.getBlockSize() * 8) + "-" + (engine.getOutputSize() * 8); + } + + /** + * Initialises the Skein digest with the provided parameters.<br> + * See {@link SkeinParameters} for details on the parameterisation of the Skein hash function. + * + * @param params an instance of {@link SkeinParameters} or {@link KeyParameter}. + */ + public void init(CipherParameters params) + throws IllegalArgumentException + { + SkeinParameters skeinParameters; + if (params instanceof SkeinParameters) + { + skeinParameters = (SkeinParameters)params; + } + else if (params instanceof KeyParameter) + { + skeinParameters = new SkeinParameters.Builder().setKey(((KeyParameter)params).getKey()).build(); + } + else + { + throw new IllegalArgumentException("Invalid parameter passed to Skein MAC init - " + + params.getClass().getName()); + } + if (skeinParameters.getKey() == null) + { + throw new IllegalArgumentException("Skein MAC requires a key parameter."); + } + engine.init(skeinParameters); + } + + public int getMacSize() + { + return engine.getOutputSize(); + } + + public void reset() + { + engine.reset(); + } + + public void update(byte in) + { + engine.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + engine.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + return engine.doFinal(out, outOff); + } + +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/macs/package.html deleted file mode 100644 index 0b1f86d..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/macs/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Classes for creating MACs and HMACs. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java index 9a6e2e0..fef51fd 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java @@ -29,8 +29,8 @@ public class CCMBlockCipher private int macSize; private CipherParameters keyParam; private byte[] macBlock; - private ByteArrayOutputStream associatedText = new ByteArrayOutputStream(); - private ByteArrayOutputStream data = new ByteArrayOutputStream(); + private ExposedByteArrayOutputStream associatedText = new ExposedByteArrayOutputStream(); + private ExposedByteArrayOutputStream data = new ExposedByteArrayOutputStream(); /** * Basic constructor. @@ -65,6 +65,7 @@ public class CCMBlockCipher { this.forEncryption = forEncryption; + CipherParameters cipherParameters; if (params instanceof AEADParameters) { AEADParameters param = (AEADParameters)params; @@ -72,7 +73,7 @@ public class CCMBlockCipher nonce = param.getNonce(); initialAssociatedText = param.getAssociatedText(); macSize = param.getMacSize() / 8; - keyParam = param.getKey(); + cipherParameters = param.getKey(); } else if (params instanceof ParametersWithIV) { @@ -81,17 +82,25 @@ public class CCMBlockCipher nonce = param.getIV(); initialAssociatedText = null; macSize = macBlock.length / 2; - keyParam = param.getParameters(); + cipherParameters = param.getParameters(); } else { throw new IllegalArgumentException("invalid parameters passed to CCM"); } + // NOTE: Very basic support for key re-use, but no performance gain from it + if (cipherParameters != null) + { + keyParam = cipherParameters; + } + if (nonce == null || nonce.length < 7 || nonce.length > 13) { throw new IllegalArgumentException("nonce must have length from 7 to 13 octets"); } + + reset(); } public String getAlgorithmName() @@ -129,14 +138,11 @@ public class CCMBlockCipher public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { - byte[] text = data.toByteArray(); - byte[] enc = processPacket(text, 0, text.length); - - System.arraycopy(enc, 0, out, outOff, enc.length); + int len = processPacket(data.getBuffer(), 0, data.size(), out, outOff); reset(); - return enc.length; + return len; } public void reset() @@ -178,9 +184,55 @@ public class CCMBlockCipher return totalData < macSize ? 0 : totalData - macSize; } + /** + * Process a packet of data for either CCM decryption or encryption. + * + * @param in data for processing. + * @param inOff offset at which data starts in the input array. + * @param inLen length of the data in the input array. + * @return a byte array containing the processed input.. + * @throws IllegalStateException if the cipher is not appropriately set up. + * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + */ public byte[] processPacket(byte[] in, int inOff, int inLen) throws IllegalStateException, InvalidCipherTextException { + byte[] output; + + if (forEncryption) + { + output = new byte[inLen + macSize]; + } + else + { + if (inLen < macSize) + { + throw new InvalidCipherTextException("data too short"); + } + output = new byte[inLen - macSize]; + } + + processPacket(in, inOff, inLen, output, 0); + + return output; + } + + /** + * Process a packet of data for either CCM decryption or encryption. + * + * @param in data for processing. + * @param inOff offset at which data starts in the input array. + * @param inLen length of the data in the input array. + * @param output output array. + * @param outOff offset into output array to start putting processed bytes. + * @return the number of bytes added to output. + * @throws IllegalStateException if the cipher is not appropriately set up. + * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + * @throws DataLengthException if output buffer too short. + */ + public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff) + throws IllegalStateException, InvalidCipherTextException, DataLengthException + { // TODO: handle null keyParam (e.g. via RepeatedKeySpec) // Need to keep the CTR and CBC Mac parts around and reset if (keyParam == null) @@ -206,42 +258,52 @@ public class CCMBlockCipher BlockCipher ctrCipher = new SICBlockCipher(cipher); ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); - int index = inOff; - int outOff = 0; - byte[] output; + int outputLen; + int inIndex = inOff; + int outIndex = outOff; if (forEncryption) { - output = new byte[inLen + macSize]; + outputLen = inLen + macSize; + if (output.length < (outputLen + outOff)) + { + throw new DataLengthException("Output buffer too short."); + } calculateMac(in, inOff, inLen, macBlock); ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0 - while (index < inLen - blockSize) // S1... + while (inIndex < (inOff + inLen - blockSize)) // S1... { - ctrCipher.processBlock(in, index, output, outOff); - outOff += blockSize; - index += blockSize; + ctrCipher.processBlock(in, inIndex, output, outIndex); + outIndex += blockSize; + inIndex += blockSize; } byte[] block = new byte[blockSize]; - System.arraycopy(in, index, block, 0, inLen - index); + System.arraycopy(in, inIndex, block, 0, inLen + inOff - inIndex); ctrCipher.processBlock(block, 0, block, 0); - System.arraycopy(block, 0, output, outOff, inLen - index); + System.arraycopy(block, 0, output, outIndex, inLen + inOff - inIndex); - outOff += inLen - index; - - System.arraycopy(macBlock, 0, output, outOff, output.length - outOff); + System.arraycopy(macBlock, 0, output, outOff + inLen, macSize); } else { - output = new byte[inLen - macSize]; + if (inLen < macSize) + { + throw new InvalidCipherTextException("data too short"); + } + outputLen = inLen - macSize; + if (output.length < (outputLen + outOff)) + { + throw new DataLengthException("Output buffer too short."); + } - System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize); + System.arraycopy(in, inOff + outputLen, macBlock, 0, macSize); ctrCipher.processBlock(macBlock, 0, macBlock, 0); @@ -250,24 +312,24 @@ public class CCMBlockCipher macBlock[i] = 0; } - while (outOff < output.length - blockSize) + while (inIndex < (inOff + outputLen - blockSize)) { - ctrCipher.processBlock(in, index, output, outOff); - outOff += blockSize; - index += blockSize; + ctrCipher.processBlock(in, inIndex, output, outIndex); + outIndex += blockSize; + inIndex += blockSize; } byte[] block = new byte[blockSize]; - System.arraycopy(in, index, block, 0, output.length - outOff); + System.arraycopy(in, inIndex, block, 0, outputLen - (inIndex - inOff)); ctrCipher.processBlock(block, 0, block, 0); - System.arraycopy(block, 0, output, outOff, output.length - outOff); + System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); byte[] calculatedMacBlock = new byte[blockSize]; - calculateMac(output, 0, output.length, calculatedMacBlock); + calculateMac(output, outOff, outputLen, calculatedMacBlock); if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) { @@ -275,7 +337,7 @@ public class CCMBlockCipher } } - return output; + return outputLen; } private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) @@ -344,8 +406,7 @@ public class CCMBlockCipher } if (associatedText.size() > 0) { - byte[] tmp = associatedText.toByteArray(); - cMac.update(tmp, 0, tmp.length); + cMac.update(associatedText.getBuffer(), 0, associatedText.size()); } extra = (extra + textLength) % 16; @@ -375,4 +436,17 @@ public class CCMBlockCipher { return getAssociatedTextLength() > 0; } + + private class ExposedByteArrayOutputStream + extends ByteArrayOutputStream + { + public ExposedByteArrayOutputStream() + { + } + + public byte[] getBuffer() + { + return this.buf; + } + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java index d0fb9bb..a885169 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java @@ -4,6 +4,7 @@ import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.util.Arrays; /** * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. @@ -246,6 +247,16 @@ public class CFBBlockCipher } /** + * Return the current state of the initialisation vector. + * + * @return current IV + */ + public byte[] getCurrentIV() + { + return Arrays.clone(cfbV); + } + + /** * reset the chaining vector back to the IV and reset the underlying * cipher. */ diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java index b8e5b61..5388b40 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java @@ -22,8 +22,9 @@ public class CTSBlockCipher public CTSBlockCipher( BlockCipher cipher) { - if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher)) + if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher) || (cipher instanceof SICBlockCipher)) { + // TODO: This is broken - need to introduce marker interface to differentiate block cipher primitive from mode? throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers"); } @@ -72,7 +73,7 @@ public class CTSBlockCipher } /** - * process a single byte, producing an output block if neccessary. + * process a single byte, producing an output block if necessary. * * @param in the input byte. * @param out the space for any output that might be produced. @@ -200,60 +201,81 @@ public class CTSBlockCipher if (forEncryption) { - cipher.processBlock(buf, 0, block, 0); - if (bufOff < blockSize) { throw new DataLengthException("need at least one block of input for CTS"); } - for (int i = bufOff; i != buf.length; i++) - { - buf[i] = block[i - blockSize]; - } - - for (int i = blockSize; i != bufOff; i++) - { - buf[i] ^= block[i - blockSize]; - } + cipher.processBlock(buf, 0, block, 0); - if (cipher instanceof CBCBlockCipher) + if (bufOff > blockSize) { - BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); - - c.processBlock(buf, blockSize, out, outOff); + for (int i = bufOff; i != buf.length; i++) + { + buf[i] = block[i - blockSize]; + } + + for (int i = blockSize; i != bufOff; i++) + { + buf[i] ^= block[i - blockSize]; + } + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, blockSize, out, outOff); + } + else + { + cipher.processBlock(buf, blockSize, out, outOff); + } + + System.arraycopy(block, 0, out, outOff + blockSize, len); } else { - cipher.processBlock(buf, blockSize, out, outOff); + System.arraycopy(block, 0, out, outOff, blockSize); } - - System.arraycopy(block, 0, out, outOff + blockSize, len); } else { + if (bufOff < blockSize) + { + throw new DataLengthException("need at least one block of input for CTS"); + } + byte[] lastBlock = new byte[blockSize]; - if (cipher instanceof CBCBlockCipher) + if (bufOff > blockSize) { - BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); - - c.processBlock(buf, 0, block, 0); + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, 0, block, 0); + } + else + { + cipher.processBlock(buf, 0, block, 0); + } + + for (int i = blockSize; i != bufOff; i++) + { + lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + } + + System.arraycopy(buf, blockSize, block, 0, len); + + cipher.processBlock(block, 0, out, outOff); + System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); } else { cipher.processBlock(buf, 0, block, 0); - } - for (int i = blockSize; i != bufOff; i++) - { - lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + System.arraycopy(block, 0, out, outOff, blockSize); } - - System.arraycopy(buf, blockSize, block, 0, len); - - cipher.processBlock(block, 0, out, outOff); - System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); } int offset = bufOff; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java index 4999caa..8f74000 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java @@ -123,16 +123,10 @@ public class EAXBlockCipher mac.update(nonce, 0, nonce.length); mac.doFinal(nonceMac, 0); - tag[blockSize - 1] = hTAG; - mac.update(tag, 0, blockSize); - - if (initialAssociatedText != null) - { - processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); - } - // Same BlockCipher underlies this and the mac, so reuse last key on cipher cipher.init(true, new ParametersWithIV(null, nonceMac)); + + reset(); } private void initCipher() @@ -206,7 +200,7 @@ public class EAXBlockCipher { if (cipherInitialized) { - throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun."); + throw new IllegalStateException("AAD data cannot be added after encryption/decryption processing has begun."); } mac.update(in, inOff, len); } @@ -246,6 +240,10 @@ public class EAXBlockCipher if (forEncryption) { + if (out.length < (outOff + extra)) + { + throw new DataLengthException("Output buffer too short"); + } cipher.processBlock(bufBlock, 0, tmp, 0); cipher.processBlock(bufBlock, blockSize, tmp, blockSize); @@ -263,6 +261,10 @@ public class EAXBlockCipher } else { + if (extra < macSize) + { + throw new InvalidCipherTextException("data too short"); + } if (extra > macSize) { mac.update(bufBlock, 0, extra - macSize); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCFBBlockCipher.java new file mode 100644 index 0000000..887c169 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCFBBlockCipher.java @@ -0,0 +1,109 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.ParametersWithSBox; + +/** + * An implementation of the GOST CFB mode with CryptoPro key meshing as described in RFC 4357. + */ +public class GCFBBlockCipher + implements BlockCipher +{ + private static final byte[] C = + { + 0x69, 0x00, 0x72, 0x22, 0x64, (byte)0xC9, 0x04, 0x23, + (byte)0x8D, 0x3A, (byte)0xDB, (byte)0x96, 0x46, (byte)0xE9, 0x2A, (byte)0xC4, + 0x18, (byte)0xFE, (byte)0xAC, (byte)0x94, 0x00, (byte)0xED, 0x07, 0x12, + (byte)0xC0, (byte)0x86, (byte)0xDC, (byte)0xC2, (byte)0xEF, 0x4C, (byte)0xA9, 0x2B + }; + + private final CFBBlockCipher cfbEngine; + + private KeyParameter key; + private long counter = 0; + private boolean forEncryption; + + public GCFBBlockCipher(BlockCipher engine) + { + this.cfbEngine = new CFBBlockCipher(engine, engine.getBlockSize() * 8); + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + counter = 0; + cfbEngine.init(forEncryption, params); + + this.forEncryption = forEncryption; + + if (params instanceof ParametersWithIV) + { + params = ((ParametersWithIV)params).getParameters(); + } + + if (params instanceof ParametersWithRandom) + { + params = ((ParametersWithRandom)params).getParameters(); + } + + if (params instanceof ParametersWithSBox) + { + params = ((ParametersWithSBox)params).getParameters(); + } + + key = (KeyParameter)params; + } + + public String getAlgorithmName() + { + return "G" + cfbEngine.getAlgorithmName(); + } + + public int getBlockSize() + { + return cfbEngine.getBlockSize(); + } + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + if (counter > 0 && counter % 1024 == 0) + { + BlockCipher base = cfbEngine.getUnderlyingCipher(); + + base.init(false, key); + + byte[] nextKey = new byte[32]; + + base.processBlock(C, 0, nextKey, 0); + base.processBlock(C, 8, nextKey, 8); + base.processBlock(C, 16, nextKey, 16); + base.processBlock(C, 24, nextKey, 24); + + key = new KeyParameter(nextKey); + + byte[] iv = new byte[8]; + + base.init(true, key); + + base.processBlock(cfbEngine.getCurrentIV(), 0, iv, 0); + + cfbEngine.init(forEncryption, new ParametersWithIV(key, iv)); + } + + counter += cfbEngine.getBlockSize(); + + return cfbEngine.processBlock(in, inOff, out, outOff); + } + + public void reset() + { + counter = 0; + cfbEngine.reset(); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java index 1178974..0e66cf3 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java @@ -206,6 +206,9 @@ public class GOFBBlockCipher */ public void reset() { + firstStep = true; + N3 = 0; + N4 = 0; System.arraycopy(IV, 0, ofbV, 0, IV.length); cipher.reset(); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java index d4d2910..6dc7148 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java @@ -13,7 +13,7 @@ import org.bouncycastle.util.Arrays; /** * An implementation of the "work in progress" Internet-Draft <a - * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-00">The OCB Authenticated-Encryption + * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-03">The OCB Authenticated-Encryption * Algorithm</a>, licensed per: * <p/> * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for @@ -111,7 +111,6 @@ public class OCBBlockCipher public void init(boolean forEncryption, CipherParameters parameters) throws IllegalArgumentException { - this.forEncryption = forEncryption; this.macBlock = null; @@ -156,23 +155,18 @@ public class OCBBlockCipher N = new byte[0]; } - if (N.length > 16 || (N.length == 16 && (N[0] & 0x80) != 0)) + if (N.length > 15) { - /* - * NOTE: We don't just ignore bit 128 because it would hide from the caller the fact - * that two nonces differing only in bit 128 are not different. - */ - throw new IllegalArgumentException("IV must be no more than 127 bits"); + throw new IllegalArgumentException("IV must be no more than 15 bytes"); } /* * KEY-DEPENDENT INITIALISATION */ - // if keyParam is null we're reusing the last key. - if (keyParameter != null) + if (keyParameter == null) { - // TODO + // TODO If 'keyParameter' is null we're re-using the last key. } // hashCipher always used in forward mode @@ -193,17 +187,10 @@ public class OCBBlockCipher byte[] nonce = new byte[16]; System.arraycopy(N, 0, nonce, nonce.length - N.length, N.length); - if (N.length == 16) - { - nonce[0] &= 0x80; - } - else - { - nonce[15 - N.length] = 1; - } + nonce[0] = (byte)(macSize << 4); + nonce[15 - N.length] |= 1; int bottom = nonce[15] & 0x3F; - // System.out.println("bottom: " + bottom); byte[] Ktop = new byte[16]; nonce[15] &= 0xC0; @@ -314,7 +301,6 @@ public class OCBBlockCipher public int processBytes(byte[] input, int inOff, int len, byte[] output, int outOff) throws DataLengthException { - int resultLen = 0; for (int i = 0; i < len; ++i) @@ -334,7 +320,6 @@ public class OCBBlockCipher throws IllegalStateException, InvalidCipherTextException { - /* * For decryption, get the tag from the end of the message */ @@ -483,7 +468,6 @@ public class OCBBlockCipher protected void reset(boolean clearMac) { - hashCipher.reset(); mainCipher.reset(); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OldCTSBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OldCTSBlockCipher.java new file mode 100644 index 0000000..b34432a --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OldCTSBlockCipher.java @@ -0,0 +1,269 @@ +package org.bouncycastle.crypto.modes; + +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to + * be used to produce cipher text which is the same length as the plain text. + * <p> + * This version applies the CTS algorithm from one block up, rather than following the errata update issued in 2004, where CTS mode is applied + * from greater than 1 block up and the first block is processed using CBC mode. + * </p> + */ +public class OldCTSBlockCipher + extends BufferedBlockCipher +{ + private int blockSize; + + /** + * Create a buffered block cipher that uses Cipher Text Stealing + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public OldCTSBlockCipher( + BlockCipher cipher) + { + if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher)) + { + throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers"); + } + + this.cipher = cipher; + + blockSize = cipher.getBlockSize(); + + buf = new byte[blockSize * 2]; + bufOff = 0; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + return len + bufOff; + } + + /** + * process a single byte, producing an output block if necessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception org.bouncycastle.crypto.DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception org.bouncycastle.crypto.DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + System.arraycopy(in, inOff, buf, bufOff, blockSize); + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception org.bouncycastle.crypto.DataLengthException if there is insufficient space in out for + * the output. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception org.bouncycastle.crypto.InvalidCipherTextException if cipher text decrypts wrongly (in + * case the exception will never get thrown). + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + if (bufOff + outOff > out.length) + { + throw new DataLengthException("output buffer to small in doFinal"); + } + + int blockSize = cipher.getBlockSize(); + int len = bufOff - blockSize; + byte[] block = new byte[blockSize]; + + if (forEncryption) + { + cipher.processBlock(buf, 0, block, 0); + + if (bufOff < blockSize) + { + throw new DataLengthException("need at least one block of input for CTS"); + } + + for (int i = bufOff; i != buf.length; i++) + { + buf[i] = block[i - blockSize]; + } + + for (int i = blockSize; i != bufOff; i++) + { + buf[i] ^= block[i - blockSize]; + } + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, blockSize, out, outOff); + } + else + { + cipher.processBlock(buf, blockSize, out, outOff); + } + + System.arraycopy(block, 0, out, outOff + blockSize, len); + } + else + { + byte[] lastBlock = new byte[blockSize]; + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, 0, block, 0); + } + else + { + cipher.processBlock(buf, 0, block, 0); + } + + for (int i = blockSize; i != bufOff; i++) + { + lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + } + + System.arraycopy(buf, blockSize, block, 0, len); + + cipher.processBlock(block, 0, out, outOff); + System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); + } + + int offset = bufOff; + + reset(); + + return offset; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java index 18e612b..4dee63a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java @@ -220,13 +220,13 @@ public class PGPCFBBlockCipher throw new DataLengthException("input buffer too short"); } - if ((outOff + blockSize) > out.length) - { - throw new DataLengthException("output buffer too short"); - } - if (count == 0) { + if ((outOff + 2 * blockSize + 2) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + cipher.processBlock(FR, 0, FRE, 0); for (int n = 0; n < blockSize; n++) @@ -258,6 +258,11 @@ public class PGPCFBBlockCipher } else if (count >= blockSize + 2) { + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + cipher.processBlock(FR, 0, FRE, 0); for (int n = 0; n < blockSize; n++) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java index f2be2fc..fc25810 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java @@ -4,21 +4,21 @@ import org.bouncycastle.util.Arrays; public class BasicGCMExponentiator implements GCMExponentiator { - private byte[] x; + private int[] x; public void init(byte[] x) { - this.x = Arrays.clone(x); + this.x = GCMUtil.asInts(x); } public void exponentiateX(long pow, byte[] output) { // Initial value is little-endian 1 - byte[] y = GCMUtil.oneAsBytes(); + int[] y = GCMUtil.oneAsInts(); if (pow > 0) { - byte[] powX = Arrays.clone(x); + int[] powX = Arrays.clone(x); do { if ((pow & 1L) != 0) @@ -31,6 +31,6 @@ public class BasicGCMExponentiator implements GCMExponentiator while (pow > 0); } - System.arraycopy(y, 0, output, 0, 16); + GCMUtil.asBytes(y, output); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java index 4875301..3031a44 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java @@ -5,6 +5,32 @@ import org.bouncycastle.util.Arrays; abstract class GCMUtil { + private static final int E1 = 0xe1000000; + private static final byte E1B = (byte)0xe1; + private static final long E1L = (E1 & 0xFFFFFFFFL) << 24; + + private static int[] generateLookup() + { + int[] lookup = new int[256]; + + for (int c = 0; c < 256; ++c) + { + int v = 0; + for (int i = 7; i >= 0; --i) + { + if ((c & (1 << i)) != 0) + { + v ^= (E1 >>> (7 - i)); + } + } + lookup[c] = v; + } + + return lookup; + } + + private static final int[] LOOKUP = generateLookup(); + static byte[] oneAsBytes() { byte[] tmp = new byte[16]; @@ -15,78 +41,155 @@ abstract class GCMUtil static int[] oneAsInts() { int[] tmp = new int[4]; - tmp[0] = 0x80000000; + tmp[0] = 1 << 31; + return tmp; + } + + static long[] oneAsLongs() + { + long[] tmp = new long[2]; + tmp[0] = 1L << 63; return tmp; } - static byte[] asBytes(int[] ns) + static byte[] asBytes(int[] x) + { + byte[] z = new byte[16]; + Pack.intToBigEndian(x, z, 0); + return z; + } + + static void asBytes(int[] x, byte[] z) + { + Pack.intToBigEndian(x, z, 0); + } + + static byte[] asBytes(long[] x) + { + byte[] z = new byte[16]; + Pack.longToBigEndian(x, z, 0); + return z; + } + + static void asBytes(long[] x, byte[] z) + { + Pack.longToBigEndian(x, z, 0); + } + + static int[] asInts(byte[] x) + { + int[] z = new int[4]; + Pack.bigEndianToInt(x, 0, z); + return z; + } + + static void asInts(byte[] x, int[] z) { - byte[] output = new byte[16]; - Pack.intToBigEndian(ns, output, 0); - return output; + Pack.bigEndianToInt(x, 0, z); } - static int[] asInts(byte[] bs) + static long[] asLongs(byte[] x) { - int[] output = new int[4]; - Pack.bigEndianToInt(bs, 0, output); - return output; + long[] z = new long[2]; + Pack.bigEndianToLong(x, 0, z); + return z; } - static void asInts(byte[] bs, int[] output) + static void asLongs(byte[] x, long[] z) { - Pack.bigEndianToInt(bs, 0, output); + Pack.bigEndianToLong(x, 0, z); } - static void multiply(byte[] block, byte[] val) + static void multiply(byte[] x, byte[] y) { - byte[] tmp = Arrays.clone(block); - byte[] c = new byte[16]; + byte[] r0 = Arrays.clone(x); + byte[] r1 = new byte[16]; for (int i = 0; i < 16; ++i) { - byte bits = val[i]; + byte bits = y[i]; for (int j = 7; j >= 0; --j) { if ((bits & (1 << j)) != 0) { - xor(c, tmp); + xor(r1, r0); } - boolean lsb = (tmp[15] & 1) != 0; - shiftRight(tmp); - if (lsb) + if (shiftRight(r0) != 0) { - // R = new byte[]{ 0xe1, ... }; -// GCMUtil.xor(v, R); - tmp[0] ^= (byte)0xe1; + r0[0] ^= E1B; } } } - System.arraycopy(c, 0, block, 0, 16); + System.arraycopy(r1, 0, x, 0, 16); + } + + static void multiply(int[] x, int[] y) + { + int[] r0 = Arrays.clone(x); + int[] r1 = new int[4]; + + for (int i = 0; i < 4; ++i) + { + int bits = y[i]; + for (int j = 31; j >= 0; --j) + { + if ((bits & (1 << j)) != 0) + { + xor(r1, r0); + } + + if (shiftRight(r0) != 0) + { + r0[0] ^= E1; + } + } + } + + System.arraycopy(r1, 0, x, 0, 4); + } + + static void multiply(long[] x, long[] y) + { + long[] r0 = new long[]{ x[0], x[1] }; + long[] r1 = new long[2]; + + for (int i = 0; i < 2; ++i) + { + long bits = y[i]; + for (int j = 63; j >= 0; --j) + { + if ((bits & (1L << j)) != 0) + { + xor(r1, r0); + } + + if (shiftRight(r0) != 0) + { + r0[0] ^= E1L; + } + } + } + + x[0] = r1[0]; + x[1] = r1[1]; } // P is the value with only bit i=1 set static void multiplyP(int[] x) { - boolean lsb = (x[3] & 1) != 0; - shiftRight(x); - if (lsb) + if (shiftRight(x) != 0) { - // R = new int[]{ 0xe1000000, 0, 0, 0 }; -// xor(v, R); - x[0] ^= 0xe1000000; + x[0] ^= E1; } } - static void multiplyP(int[] x, int[] output) + static void multiplyP(int[] x, int[] y) { - boolean lsb = (x[3] & 1) != 0; - shiftRight(x, output); - if (lsb) + if (shiftRight(x, y) != 0) { - output[0] ^= 0xe1000000; + y[0] ^= E1; } } @@ -98,163 +201,257 @@ abstract class GCMUtil // multiplyP(x); // } - int lsw = x[3]; - shiftRightN(x, 8); - for (int i = 7; i >= 0; --i) - { - if ((lsw & (1 << i)) != 0) - { - x[0] ^= (0xe1000000 >>> (7 - i)); - } - } + int c = shiftRightN(x, 8); + x[0] ^= LOOKUP[c >>> 24]; } - static void multiplyP8(int[] x, int[] output) + static void multiplyP8(int[] x, int[] y) { - int lsw = x[3]; - shiftRightN(x, 8, output); - for (int i = 7; i >= 0; --i) - { - if ((lsw & (1 << i)) != 0) - { - output[0] ^= (0xe1000000 >>> (7 - i)); - } - } + int c = shiftRightN(x, 8, y); + y[0] ^= LOOKUP[c >>> 24]; } - static void shiftRight(byte[] block) + static byte shiftRight(byte[] x) { - int i = 0; - int bit = 0; - for (;;) +// int c = 0; +// for (int i = 0; i < 16; ++i) +// { +// int b = x[i] & 0xff; +// x[i] = (byte)((b >>> 1) | c); +// c = (b & 1) << 7; +// } +// return (byte)c; + + int i = 0, c = 0; + do { - int b = block[i] & 0xff; - block[i] = (byte) ((b >>> 1) | bit); - if (++i == 16) - { - break; - } - bit = (b & 1) << 7; + int b = x[i] & 0xff; + x[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + x[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + x[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + x[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; } + while (i < 16); + return (byte)c; } - static void shiftRight(byte[] block, byte[] output) + static byte shiftRight(byte[] x, byte[] z) { - int i = 0; - int bit = 0; - for (;;) +// int c = 0; +// for (int i = 0; i < 16; ++i) +// { +// int b = x[i] & 0xff; +// z[i] = (byte) ((b >>> 1) | c); +// c = (b & 1) << 7; +// } +// return (byte) c; + + int i = 0, c = 0; + do { - int b = block[i] & 0xff; - output[i] = (byte) ((b >>> 1) | bit); - if (++i == 16) - { - break; - } - bit = (b & 1) << 7; + int b = x[i] & 0xff; + z[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + z[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + z[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; + b = x[i] & 0xff; + z[i++] = (byte)((b >>> 1) | c); + c = (b & 1) << 7; } + while (i < 16); + return (byte)c; } - static void shiftRight(int[] block) + static int shiftRight(int[] x) { - int i = 0; - int bit = 0; - for (;;) - { - int b = block[i]; - block[i] = (b >>> 1) | bit; - if (++i == 4) - { - break; - } - bit = b << 31; - } +// int c = 0; +// for (int i = 0; i < 4; ++i) +// { +// int b = x[i]; +// x[i] = (b >>> 1) | c; +// c = b << 31; +// } +// return c; + + int b = x[0]; + x[0] = b >>> 1; + int c = b << 31; + b = x[1]; + x[1] = (b >>> 1) | c; + c = b << 31; + b = x[2]; + x[2] = (b >>> 1) | c; + c = b << 31; + b = x[3]; + x[3] = (b >>> 1) | c; + return b << 31; } - static void shiftRight(int[] block, int[] output) + static int shiftRight(int[] x, int[] z) { - int i = 0; - int bit = 0; - for (;;) - { - int b = block[i]; - output[i] = (b >>> 1) | bit; - if (++i == 4) - { - break; - } - bit = b << 31; - } +// int c = 0; +// for (int i = 0; i < 4; ++i) +// { +// int b = x[i]; +// z[i] = (b >>> 1) | c; +// c = b << 31; +// } +// return c; + + int b = x[0]; + z[0] = b >>> 1; + int c = b << 31; + b = x[1]; + z[1] = (b >>> 1) | c; + c = b << 31; + b = x[2]; + z[2] = (b >>> 1) | c; + c = b << 31; + b = x[3]; + z[3] = (b >>> 1) | c; + return b << 31; } - static void shiftRightN(int[] block, int n) + static long shiftRight(long[] x) { - int i = 0; - int bits = 0; - for (;;) - { - int b = block[i]; - block[i] = (b >>> n) | bits; - if (++i == 4) - { - break; - } - bits = b << (32 - n); - } + long b = x[0]; + x[0] = b >>> 1; + long c = b << 63; + b = x[1]; + x[1] = (b >>> 1) | c; + return b << 63; + } + + static long shiftRight(long[] x, long[] z) + { + long b = x[0]; + z[0] = b >>> 1; + long c = b << 63; + b = x[1]; + z[1] = (b >>> 1) | c; + return b << 63; + } + + static int shiftRightN(int[] x, int n) + { +// int c = 0, nInv = 32 - n; +// for (int i = 0; i < 4; ++i) +// { +// int b = x[i]; +// x[i] = (b >>> n) | c; +// c = b << nInv; +// } +// return c; + + int b = x[0], nInv = 32 - n; + x[0] = b >>> n; + int c = b << nInv; + b = x[1]; + x[1] = (b >>> n) | c; + c = b << nInv; + b = x[2]; + x[2] = (b >>> n) | c; + c = b << nInv; + b = x[3]; + x[3] = (b >>> n) | c; + return b << nInv; } - static void shiftRightN(int[] block, int n, int[] output) + static int shiftRightN(int[] x, int n, int[] z) + { +// int c = 0, nInv = 32 - n; +// for (int i = 0; i < 4; ++i) +// { +// int b = x[i]; +// z[i] = (b >>> n) | c; +// c = b << nInv; +// } +// return c; + + int b = x[0], nInv = 32 - n; + z[0] = b >>> n; + int c = b << nInv; + b = x[1]; + z[1] = (b >>> n) | c; + c = b << nInv; + b = x[2]; + z[2] = (b >>> n) | c; + c = b << nInv; + b = x[3]; + z[3] = (b >>> n) | c; + return b << nInv; + } + + static void xor(byte[] x, byte[] y) { int i = 0; - int bits = 0; - for (;;) + do { - int b = block[i]; - output[i] = (b >>> n) | bits; - if (++i == 4) - { - break; - } - bits = b << (32 - n); + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; } + while (i < 16); } - static void xor(byte[] block, byte[] val) + static void xor(byte[] x, byte[] y, int yOff, int yLen) { - for (int i = 15; i >= 0; --i) + while (yLen-- > 0) { - block[i] ^= val[i]; + x[yLen] ^= y[yOff + yLen]; } } - static void xor(byte[] block, byte[] val, int off, int len) + static void xor(byte[] x, byte[] y, byte[] z) { - while (len-- > 0) + int i = 0; + do { - block[len] ^= val[off + len]; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; } + while (i < 16); } - static void xor(byte[] block, byte[] val, byte[] output) + static void xor(int[] x, int[] y) { - for (int i = 15; i >= 0; --i) - { - output[i] = (byte)(block[i] ^ val[i]); - } + x[0] ^= y[0]; + x[1] ^= y[1]; + x[2] ^= y[2]; + x[3] ^= y[3]; } - static void xor(int[] block, int[] val) + static void xor(int[] x, int[] y, int[] z) { - for (int i = 3; i >= 0; --i) - { - block[i] ^= val[i]; - } + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; } - static void xor(int[] block, int[] val, int[] output) + static void xor(long[] x, long[] y) { - for (int i = 3; i >= 0; --i) - { - output[i] = block[i] ^ val[i]; - } + x[0] ^= y[0]; + x[1] ^= y[1]; + } + + static void xor(long[] x, long[] y, long[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java index a051208..6eff4e3 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java @@ -12,31 +12,32 @@ public class Tables1kGCMExponentiator implements GCMExponentiator public void init(byte[] x) { - if (lookupPowX2 != null && Arrays.areEqual(x, (byte[])lookupPowX2.elementAt(0))) + int[] y = GCMUtil.asInts(x); + if (lookupPowX2 != null && Arrays.areEqual(y, (int[])lookupPowX2.elementAt(0))) { return; } lookupPowX2 = new Vector(8); - lookupPowX2.addElement(Arrays.clone(x)); + lookupPowX2.addElement(y); } public void exponentiateX(long pow, byte[] output) { - byte[] y = GCMUtil.oneAsBytes(); + int[] y = GCMUtil.oneAsInts(); int bit = 0; while (pow > 0) { if ((pow & 1L) != 0) { ensureAvailable(bit); - GCMUtil.multiply(y, (byte[])lookupPowX2.elementAt(bit)); + GCMUtil.multiply(y, (int[])lookupPowX2.elementAt(bit)); } ++bit; pow >>>= 1; } - System.arraycopy(y, 0, output, 0, 16); + GCMUtil.asBytes(y, output); } private void ensureAvailable(int bit) @@ -44,7 +45,7 @@ public class Tables1kGCMExponentiator implements GCMExponentiator int count = lookupPowX2.size(); if (count <= bit) { - byte[] tmp = (byte[])lookupPowX2.elementAt(count - 1); + int[] tmp = (int[])lookupPowX2.elementAt(count - 1); do { tmp = Arrays.clone(tmp); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/modes/package.html deleted file mode 100644 index 5402df4..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Modes for symmetric ciphers. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/package.html deleted file mode 100644 index ee5487f..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Base classes for the lightweight API. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/paddings/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/paddings/package.html deleted file mode 100644 index 2b82e60..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/paddings/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Paddings for symmetric ciphers. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java index 05a1327..9cc6e72 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java @@ -41,7 +41,7 @@ public class ECDomainParameters byte[] seed) { this.curve = curve; - this.G = G; + this.G = G.normalize(); this.n = n; this.h = h; this.seed = seed; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java index 5fbea19..b6b3fb6 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECPublicKeyParameters.java @@ -12,7 +12,7 @@ public class ECPublicKeyParameters ECDomainParameters params) { super(false, params); - this.Q = Q; + this.Q = Q.normalize(); } public ECPoint getQ() diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFCounterParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFCounterParameters.java new file mode 100644 index 0000000..0eb6cb7 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFCounterParameters.java @@ -0,0 +1,52 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.util.Arrays; + +public final class KDFCounterParameters + implements DerivationParameters +{ + + private final byte[] ki; + private final byte[] fixedInputData; + private final int r; + + public KDFCounterParameters(byte[] ki, byte[] fixedInputData, int r) + { + if (ki == null) + { + throw new IllegalArgumentException("A KDF requires Ki (a seed) as input"); + } + this.ki = Arrays.clone(ki); + + if (fixedInputData == null) + { + this.fixedInputData = new byte[0]; + } + else + { + this.fixedInputData = Arrays.clone(fixedInputData); + } + + if (r != 8 && r != 16 && r != 24 && r != 32) + { + throw new IllegalArgumentException("Length of counter should be 8, 16, 24 or 32"); + } + this.r = r; + } + + public byte[] getKI() + { + return ki; + } + + public byte[] getFixedInputData() + { + return Arrays.clone(fixedInputData); + } + + public int getR() + { + return r; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFDoublePipelineIterationParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFDoublePipelineIterationParameters.java new file mode 100644 index 0000000..383678a --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFDoublePipelineIterationParameters.java @@ -0,0 +1,80 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.util.Arrays; + +/** + * Note that counter is only supported at the location presented in the + * NIST SP 800-108 specification, not in the additional locations present + * in the CAVP test vectors. + */ +public final class KDFDoublePipelineIterationParameters + implements DerivationParameters +{ + + // could be any valid value, using 32, don't know why + private static final int UNUSED_R = 32; + + private final byte[] ki; + private final boolean useCounter; + private final int r; + private final byte[] fixedInputData; + + private KDFDoublePipelineIterationParameters(byte[] ki, byte[] fixedInputData, int r, boolean useCounter) + { + if (ki == null) + { + throw new IllegalArgumentException("A KDF requires Ki (a seed) as input"); + } + this.ki = Arrays.clone(ki); + + if (fixedInputData == null) + { + this.fixedInputData = new byte[0]; + } + else + { + this.fixedInputData = Arrays.clone(fixedInputData); + } + + if (r != 8 && r != 16 && r != 24 && r != 32) + { + throw new IllegalArgumentException("Length of counter should be 8, 16, 24 or 32"); + } + this.r = r; + + this.useCounter = useCounter; + } + + public static KDFDoublePipelineIterationParameters createWithCounter( + byte[] ki, byte[] fixedInputData, int r) + { + return new KDFDoublePipelineIterationParameters(ki, fixedInputData, r, true); + } + + public static KDFDoublePipelineIterationParameters createWithoutCounter( + byte[] ki, byte[] fixedInputData) + { + return new KDFDoublePipelineIterationParameters(ki, fixedInputData, UNUSED_R, false); + } + + public byte[] getKI() + { + return ki; + } + + public boolean useCounter() + { + return useCounter; + } + + public int getR() + { + return r; + } + + public byte[] getFixedInputData() + { + return Arrays.clone(fixedInputData); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFFeedbackParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFFeedbackParameters.java new file mode 100644 index 0000000..8a6e7f5 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/KDFFeedbackParameters.java @@ -0,0 +1,96 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.util.Arrays; + +/** + * Note that counter is only supported at the location presented in the + * NIST SP 800-108 specification, not in the additional locations present + * in the CAVP test vectors. + */ +public final class KDFFeedbackParameters + implements DerivationParameters +{ + + // could be any valid value, using 32, don't know why + private static final int UNUSED_R = -1; + + private final byte[] ki; + private final byte[] iv; + private final boolean useCounter; + private final int r; + private final byte[] fixedInputData; + + private KDFFeedbackParameters(byte[] ki, byte[] iv, byte[] fixedInputData, int r, boolean useCounter) + { + if (ki == null) + { + throw new IllegalArgumentException("A KDF requires Ki (a seed) as input"); + } + this.ki = Arrays.clone(ki); + + if (fixedInputData == null) + { + this.fixedInputData = new byte[0]; + } + else + { + this.fixedInputData = Arrays.clone(fixedInputData); + } + + this.r = r; + + if (iv == null) + { + this.iv = new byte[0]; + } + else + { + this.iv = Arrays.clone(iv); + } + + this.useCounter = useCounter; + } + + public static KDFFeedbackParameters createWithCounter( + byte[] ki, final byte[] iv, byte[] fixedInputData, int r) + { + if (r != 8 && r != 16 && r != 24 && r != 32) + { + throw new IllegalArgumentException("Length of counter should be 8, 16, 24 or 32"); + } + + return new KDFFeedbackParameters(ki, iv, fixedInputData, r, true); + } + + public static KDFFeedbackParameters createWithoutCounter( + byte[] ki, final byte[] iv, byte[] fixedInputData) + { + return new KDFFeedbackParameters(ki, iv, fixedInputData, UNUSED_R, false); + } + + public byte[] getKI() + { + return ki; + } + + public byte[] getIV() + { + return iv; + } + + public boolean useCounter() + { + return useCounter; + } + + public int getR() + { + return r; + } + + public byte[] getFixedInputData() + { + return Arrays.clone(fixedInputData); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/SkeinParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/SkeinParameters.java new file mode 100644 index 0000000..76241ee --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/SkeinParameters.java @@ -0,0 +1,293 @@ +package org.bouncycastle.crypto.params; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.digests.SkeinDigest; +import org.bouncycastle.crypto.digests.SkeinEngine; +import org.bouncycastle.crypto.macs.SkeinMac; +import org.bouncycastle.util.Integers; + +/** + * Parameters for the Skein hash function - a series of byte[] strings identified by integer tags. + * <p/> + * Parameterised Skein can be used for: + * <ul> + * <li>MAC generation, by providing a {@link SkeinParameters.Builder#setKey(byte[]) key}.</li> + * <li>Randomised hashing, by providing a {@link SkeinParameters.Builder#setNonce(byte[]) nonce}.</li> + * <li>A hash function for digital signatures, associating a + * {@link SkeinParameters.Builder#setPublicKey(byte[]) public key} with the message digest.</li> + * <li>A key derivation function, by providing a + * {@link SkeinParameters.Builder#setKeyIdentifier(byte[]) key identifier}.</li> + * <li>Personalised hashing, by providing a + * {@link SkeinParameters.Builder#setPersonalisation(Date, String, String) recommended format} or + * {@link SkeinParameters.Builder#setPersonalisation(byte[]) arbitrary} personalisation string.</li> + * </ul> + * + * @see SkeinEngine + * @see SkeinDigest + * @see SkeinMac + */ +public class SkeinParameters + implements CipherParameters +{ + /** + * The parameter type for a secret key, supporting MAC or KDF functions: {@value + * #PARAM_TYPE_KEY}. + */ + public static final int PARAM_TYPE_KEY = 0; + + /** + * The parameter type for the Skein configuration block: {@value #PARAM_TYPE_CONFIG}. + */ + public static final int PARAM_TYPE_CONFIG = 4; + + /** + * The parameter type for a personalisation string: {@value #PARAM_TYPE_PERSONALISATION}. + */ + public static final int PARAM_TYPE_PERSONALISATION = 8; + + /** + * The parameter type for a public key: {@value #PARAM_TYPE_PUBLIC_KEY}. + */ + public static final int PARAM_TYPE_PUBLIC_KEY = 12; + + /** + * The parameter type for a key identifier string: {@value #PARAM_TYPE_KEY_IDENTIFIER}. + */ + public static final int PARAM_TYPE_KEY_IDENTIFIER = 16; + + /** + * The parameter type for a nonce: {@value #PARAM_TYPE_NONCE}. + */ + public static final int PARAM_TYPE_NONCE = 20; + + /** + * The parameter type for the message: {@value #PARAM_TYPE_MESSAGE}. + */ + public static final int PARAM_TYPE_MESSAGE = 48; + + /** + * The parameter type for the output transformation: {@value #PARAM_TYPE_OUTPUT}. + */ + public static final int PARAM_TYPE_OUTPUT = 63; + + private Hashtable parameters; + + public SkeinParameters() + { + this(new Hashtable()); + } + + private SkeinParameters(final Hashtable parameters) + { + this.parameters = parameters; + } + + /** + * Obtains a map of type (Integer) to value (byte[]) for the parameters tracked in this object. + */ + public Hashtable getParameters() + { + return parameters; + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_KEY key parameter}, or <code>null</code> if not + * set. + */ + public byte[] getKey() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_KEY)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_PERSONALISATION personalisation parameter}, or + * <code>null</code> if not set. + */ + public byte[] getPersonalisation() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_PERSONALISATION)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_PUBLIC_KEY public key parameter}, or + * <code>null</code> if not set. + */ + public byte[] getPublicKey() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_PUBLIC_KEY)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_KEY_IDENTIFIER key identifier parameter}, or + * <code>null</code> if not set. + */ + public byte[] getKeyIdentifier() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_KEY_IDENTIFIER)); + } + + /** + * Obtains the value of the {@link #PARAM_TYPE_NONCE nonce parameter}, or <code>null</code> if + * not set. + */ + public byte[] getNonce() + { + return (byte[])parameters.get(Integers.valueOf(PARAM_TYPE_NONCE)); + } + + /** + * A builder for {@link SkeinParameters}. + */ + public static class Builder + { + private Hashtable parameters = new Hashtable(); + + public Builder() + { + } + + public Builder(Hashtable paramsMap) + { + Enumeration keys = paramsMap.keys(); + while (keys.hasMoreElements()) + { + Integer key = (Integer)keys.nextElement(); + parameters.put(key, paramsMap.get(key)); + } + } + + public Builder(SkeinParameters params) + { + Enumeration keys = params.parameters.keys(); + while (keys.hasMoreElements()) + { + Integer key = (Integer)keys.nextElement(); + parameters.put(key, params.parameters.get(key)); + } + } + + /** + * Sets a parameters to apply to the Skein hash function.<br> + * Parameter types must be in the range 0,5..62, and cannot use the value {@value + * SkeinParameters#PARAM_TYPE_MESSAGE} (reserved for message body). + * <p/> + * Parameters with type < {@value SkeinParameters#PARAM_TYPE_MESSAGE} are processed before + * the message content, parameters with type > {@value SkeinParameters#PARAM_TYPE_MESSAGE} + * are processed after the message and prior to output. + * + * @param type the type of the parameter, in the range 5..62. + * @param value the byte sequence of the parameter. + * @return + */ + public Builder set(int type, byte[] value) + { + if (value == null) + { + throw new IllegalArgumentException("Parameter value must not be null."); + } + if ((type != PARAM_TYPE_KEY) + && (type <= PARAM_TYPE_CONFIG || type >= PARAM_TYPE_OUTPUT || type == PARAM_TYPE_MESSAGE)) + { + throw new IllegalArgumentException("Parameter types must be in the range 0,5..47,49..62."); + } + if (type == PARAM_TYPE_CONFIG) + { + throw new IllegalArgumentException("Parameter type " + PARAM_TYPE_CONFIG + + " is reserved for internal use."); + } + this.parameters.put(Integers.valueOf(type), value); + return this; + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY} parameter. + */ + public Builder setKey(byte[] key) + { + return set(PARAM_TYPE_KEY, key); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_PERSONALISATION} parameter. + */ + public Builder setPersonalisation(byte[] personalisation) + { + return set(PARAM_TYPE_PERSONALISATION, personalisation); + } + + /** + * Implements the recommended personalisation format for Skein defined in Section 4.11 of + * the Skein 1.3 specification. + * <p/> + * The format is <code>YYYYMMDD email@address distinguisher</code>, encoded to a byte + * sequence using UTF-8 encoding. + * + * @param date the date the personalised application of the Skein was defined. + * @param emailAddress the email address of the creation of the personalised application. + * @param distinguisher an arbitrary personalisation string distinguishing the application. + * @return + */ + public Builder setPersonalisation(Date date, String emailAddress, String distinguisher) + { + try + { + final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); + final DateFormat format = new SimpleDateFormat("YYYYMMDD"); + out.write(format.format(date)); + out.write(" "); + out.write(emailAddress); + out.write(" "); + out.write(distinguisher); + out.close(); + return set(PARAM_TYPE_PERSONALISATION, bout.toByteArray()); + } + catch (IOException e) + { + throw new IllegalStateException("Byte I/O failed: " + e); + } + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY_IDENTIFIER} parameter. + */ + public Builder setPublicKey(byte[] publicKey) + { + return set(PARAM_TYPE_PUBLIC_KEY, publicKey); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_KEY_IDENTIFIER} parameter. + */ + public Builder setKeyIdentifier(byte[] keyIdentifier) + { + return set(PARAM_TYPE_KEY_IDENTIFIER, keyIdentifier); + } + + /** + * Sets the {@link SkeinParameters#PARAM_TYPE_NONCE} parameter. + */ + public Builder setNonce(byte[] nonce) + { + return set(PARAM_TYPE_NONCE, nonce); + } + + /** + * Constructs a new {@link SkeinParameters} instance with the parameters provided to this + * builder. + */ + public SkeinParameters build() + { + return new SkeinParameters(parameters); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/TweakableBlockCipherParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/TweakableBlockCipherParameters.java new file mode 100644 index 0000000..fa16fac --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/TweakableBlockCipherParameters.java @@ -0,0 +1,40 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.util.Arrays; + +/** + * Parameters for tweakable block ciphers. + */ +public class TweakableBlockCipherParameters + implements CipherParameters +{ + private final byte[] tweak; + private final KeyParameter key; + + public TweakableBlockCipherParameters(final KeyParameter key, final byte[] tweak) + { + this.key = key; + this.tweak = Arrays.clone(tweak); + } + + /** + * Gets the key. + * + * @return the key to use, or <code>null</code> to use the current key. + */ + public KeyParameter getKey() + { + return key; + } + + /** + * Gets the tweak value. + * + * @return the tweak to use, or <code>null</code> to use the current tweak. + */ + public byte[] getTweak() + { + return tweak; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/params/package.html deleted file mode 100644 index 4e00a75..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/params/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Classes for parameter objects for ciphers and generators. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java index 209b5e2..3245eab 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/FixedSecureRandom.java @@ -4,6 +4,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.SecureRandom; +/** + * A secure random that returns pre-seeded data to calls of nextBytes() or generateSeed(). + */ public class FixedSecureRandom extends SecureRandom { @@ -70,7 +73,16 @@ public class FixedSecureRandom _index += bytes.length; } - + + public byte[] generateSeed(int numBytes) + { + byte[] bytes = new byte[numBytes]; + + this.nextBytes(bytes); + + return bytes; + } + // // classpath's implementation of SecureRandom doesn't currently go back to nextBytes // when next is called. We can't override next as it's a final method. diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java index 66f05c5..59ea101 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java @@ -6,6 +6,7 @@ import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.DualECPoints; import org.bouncycastle.crypto.prng.drbg.DualECSP800DRBG; import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG; import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG; @@ -144,7 +145,7 @@ public class SP800SecureRandomBuilder } /** - * Build a SecureRandom based on a SP 800-90A Dual EC DRBG. + * Build a SecureRandom based on a SP 800-90A Dual EC DRBG using the NIST point set. * * @param digest digest algorithm to use in the DRBG underneath the SecureRandom. * @param nonce nonce value to use in DRBG construction. @@ -156,6 +157,21 @@ public class SP800SecureRandomBuilder return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new DualECDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant); } + /** + * Build a SecureRandom based on a SP 800-90A Dual EC DRBG according to a defined point set. + * + * @param pointSet an array of DualECPoints to use for DRB generation. + * @param digest digest algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a Dual EC DRBG. + */ + public SP800SecureRandom buildDualEC(DualECPoints[] pointSet, Digest digest, byte[] nonce, boolean predictionResistant) + { + return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new ConfigurableDualECDRBGProvider(pointSet, digest, nonce, personalizationString, securityStrength), predictionResistant); + } + + private static class HashDRBGProvider implements DRBGProvider { @@ -200,6 +216,31 @@ public class SP800SecureRandomBuilder } } + private static class ConfigurableDualECDRBGProvider + implements DRBGProvider + { + private final DualECPoints[] pointSet; + private final Digest digest; + private final byte[] nonce; + private final byte[] personalizationString; + private final int securityStrength; + + public ConfigurableDualECDRBGProvider(DualECPoints[] pointSet, Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.pointSet = new DualECPoints[pointSet.length]; + System.arraycopy(pointSet, 0, this.pointSet, 0, pointSet.length); + this.digest = digest; + this.nonce = nonce; + this.personalizationString = personalizationString; + this.securityStrength = securityStrength; + } + + public SP80090DRBG get(EntropySource entropySource) + { + return new DualECSP800DRBG(pointSet, digest, securityStrength, entropySource, personalizationString, nonce); + } + } + private static class HMacDRBGProvider implements DRBGProvider { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECPoints.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECPoints.java new file mode 100644 index 0000000..c3715bc --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECPoints.java @@ -0,0 +1,82 @@ +package org.bouncycastle.crypto.prng.drbg; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * General class for providing point pairs for use with DualEC DRBG. See NIST SP 800-90A for further details. + */ +public class DualECPoints +{ + private final ECPoint p; + private final ECPoint q; + private final int securityStrength; + private final int cofactor; + + /** + * Base Constructor. + * <p> + * The cofactor is used to calculate the output block length (maxOutlen) according to + * <pre> + * max_outlen = largest multiple of 8 less than ((field size in bits) - (13 + log2(cofactor)) + * </pre> + * </p> + * @param securityStrength maximum security strength to be associated with these parameters + * @param p the P point. + * @param q the Q point. + * @param cofactor cofactor associated with the domain parameters for the point generation. + */ + public DualECPoints(int securityStrength, ECPoint p, ECPoint q, int cofactor) + { + if (!p.getCurve().equals(q.getCurve())) + { + throw new IllegalArgumentException("points need to be on the same curve"); + } + + this.securityStrength = securityStrength; + this.p = p; + this.q = q; + this.cofactor = cofactor; + } + + public int getSeedLen() + { + return p.getCurve().getFieldSize(); + } + + public int getMaxOutlen() + { + return ((p.getCurve().getFieldSize() - (13 + log2(cofactor))) / 8) * 8; + } + + public ECPoint getP() + { + return p; + } + + public ECPoint getQ() + { + return q; + } + + public int getSecurityStrength() + { + return securityStrength; + } + + public int getCofactor() + { + return cofactor; + } + + private static int log2(int value) + { + int log = 0; + + while ((value >>= 1) != 0) + { + log++; + } + + return log; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java index 3cee39c..8d326ff 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java @@ -6,7 +6,6 @@ import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.prng.EntropySource; import org.bouncycastle.math.ec.ECCurve; -import org.bouncycastle.math.ec.ECFieldElement; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; @@ -35,6 +34,26 @@ public class DualECSP800DRBG private static final BigInteger p521_Qx = new BigInteger("1b9fa3e518d683c6b65763694ac8efbaec6fab44f2276171a42726507dd08add4c3b3f4c1ebc5b1222ddba077f722943b24c3edfa0f85fe24d0c8c01591f0be6f63", 16); private static final BigInteger p521_Qy = new BigInteger("1f3bdba585295d9a1110d1df1f9430ef8442c5018976ff3437ef91b81dc0b8132c8d5c39c32d0e004a3092b7d327c0e7a4d26d2c7b69b58f9066652911e457779de", 16); + private static final DualECPoints[] nistPoints; + + static + { + nistPoints = new DualECPoints[3]; + + ECCurve.Fp curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-256").getCurve(); + + nistPoints[0] = new DualECPoints(128, curve.createPoint(p256_Px, p256_Py), curve.createPoint(p256_Qx, p256_Qy), 1); + + curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-384").getCurve(); + + nistPoints[1] = new DualECPoints(192, curve.createPoint(p384_Px, p384_Py), curve.createPoint(p384_Qx, p384_Qy), 1); + + curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-521").getCurve(); + + nistPoints[2] = new DualECPoints(256, curve.createPoint(p521_Px, p521_Py), curve.createPoint(p521_Qx, p521_Qy), 1); + } + + private static final long RESEED_MAX = 1L << (32 - 1); private static final int MAX_ADDITIONAL_INPUT = 1 << (13 - 1); private static final int MAX_ENTROPY_LENGTH = 1 << (13 - 1); @@ -65,6 +84,23 @@ public class DualECSP800DRBG */ public DualECSP800DRBG(Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) { + this(nistPoints, digest, securityStrength, entropySource, personalizationString, nonce); + } + + /** + * Construct a SP800-90A Dual EC DRBG. + * <p> + * Minimum entropy requirement is the security strength requested. + * </p> + * @param pointSet an array of points to choose from, in order of increasing security strength + * @param digest source digest to use with the DRB stream. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public DualECSP800DRBG(DualECPoints[] pointSet, Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) + { _digest = digest; _entropySource = entropySource; _securityStrength = securityStrength; @@ -82,43 +118,23 @@ public class DualECSP800DRBG byte[] entropy = entropySource.getEntropy(); byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString); - if (securityStrength <= 128) - { - if (Utils.getMaxSecurityStrength(digest) < 128) - { - throw new IllegalArgumentException("Requested security strength is not supported by digest"); - } - _seedlen = 256; - _outlen = 240 / 8; - _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-256").getCurve(); - _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Px), new ECFieldElement.Fp(_curve.getQ(), p256_Py)); - _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Qx), new ECFieldElement.Fp(_curve.getQ(), p256_Qy)); - } - else if (securityStrength <= 192) + for (int i = 0; i != pointSet.length; i++) { - if (Utils.getMaxSecurityStrength(digest) < 192) + if (securityStrength <= pointSet[i].getSecurityStrength()) { - throw new IllegalArgumentException("Requested security strength is not supported by digest"); + if (Utils.getMaxSecurityStrength(digest) < pointSet[i].getSecurityStrength()) + { + throw new IllegalArgumentException("Requested security strength is not supported by digest"); + } + _seedlen = pointSet[i].getSeedLen(); + _outlen = pointSet[i].getMaxOutlen() / 8; + _P = pointSet[i].getP(); + _Q = pointSet[i].getQ(); + break; } - _seedlen = 384; - _outlen = 368 / 8; - _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-384").getCurve(); - _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Px), new ECFieldElement.Fp(_curve.getQ(), p384_Py)); - _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Qx), new ECFieldElement.Fp(_curve.getQ(), p384_Qy)); } - else if (securityStrength <= 256) - { - if (Utils.getMaxSecurityStrength(digest) < 256) - { - throw new IllegalArgumentException("Requested security strength is not supported by digest"); - } - _seedlen = 521; - _outlen = 504 / 8; - _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-521").getCurve(); - _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Px), new ECFieldElement.Fp(_curve.getQ(), p521_Py)); - _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Qx), new ECFieldElement.Fp(_curve.getQ(), p521_Qy)); - } - else + + if (_P == null) { throw new IllegalArgumentException("security strength cannot be greater than 256 bits"); } @@ -159,50 +175,69 @@ public class DualECSP800DRBG additionalInput = null; } + BigInteger s; + if (additionalInput != null) { // Note: we ignore the use of pad8 on the additional input as we mandate byte arrays for it. additionalInput = Utils.hash_df(_digest, additionalInput, _seedlen); + s = new BigInteger(1, xor(_s, additionalInput)); } + else + { + s = new BigInteger(1, _s); + } + + // make sure we start with a clean output array. + Arrays.fill(output, (byte)0); + + int outOffset = 0; for (int i = 0; i < m; i++) { - BigInteger t = new BigInteger(1, xor(_s, additionalInput)); - - _s = _P.multiply(t).getX().toBigInteger().toByteArray(); + s = getScalarMultipleXCoord(_P, s); //System.err.println("S: " + new String(Hex.encode(_s))); - byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray(); + byte[] r = _Q.multiply(s).normalize().getAffineXCoord().toBigInteger().toByteArray(); if (r.length > _outlen) { - System.arraycopy(r, r.length - _outlen, output, i * _outlen, _outlen); + System.arraycopy(r, r.length - _outlen, output, outOffset, _outlen); } else { - System.arraycopy(r, 0, output, i * _outlen + (_outlen - r.length), r.length); + System.arraycopy(r, 0, output, outOffset + (_outlen - r.length), r.length); } //System.err.println("R: " + new String(Hex.encode(r))); - additionalInput = null; + outOffset += _outlen; _reseedCounter++; } - if (m * _outlen < output.length) + if (outOffset < output.length) { - BigInteger t = new BigInteger(1, xor(_s, additionalInput)); + s = getScalarMultipleXCoord(_P, s); - _s = _P.multiply(t).getX().toBigInteger().toByteArray(); + byte[] r = _Q.multiply(s).normalize().getAffineXCoord().toBigInteger().toByteArray(); - byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray(); + int required = output.length - outOffset; - System.arraycopy(r, 0, output, m * _outlen, output.length - (m * _outlen)); + if (r.length > _outlen) + { + System.arraycopy(r, r.length - _outlen, output, outOffset, required); + } + else + { + System.arraycopy(r, 0, output, outOffset + (_outlen - r.length), required); + } + + _reseedCounter++; } // Need to preserve length of S as unsigned int. - _s = BigIntegers.asUnsignedByteArray(_sLength, _P.multiply(new BigInteger(1, _s)).getX().toBigInteger()); + _s = BigIntegers.asUnsignedByteArray(_sLength, _P.multiply(s).normalize().getAffineXCoord().toBigInteger()); return numberOfBits; } @@ -264,4 +299,9 @@ public class DualECSP800DRBG return s; } + + private BigInteger getScalarMultipleXCoord(ECPoint p, BigInteger s) + { + return p.multiply(s).normalize().getAffineXCoord().toBigInteger(); + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/package.html deleted file mode 100644 index 630809b..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/drbg/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -NIST Deterministic Random Bit Generators (SP 800-90A). -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/prng/package.html deleted file mode 100644 index 9ad3854..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Lightweight psuedo-random number generators and SecureRandom variants and builders. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSAKCalculator.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSAKCalculator.java new file mode 100644 index 0000000..fced06e --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSAKCalculator.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Interface define calculators of K values for DSA/ECDSA. + */ +public interface DSAKCalculator +{ + /** + * Return true if this calculator is deterministic, false otherwise. + * + * @return true if deterministic, otherwise false. + */ + boolean isDeterministic(); + + /** + * Non-deterministic initialiser. + * + * @param n the order of the DSA group. + * @param random a source of randomness. + */ + void init(BigInteger n, SecureRandom random); + + /** + * Deterministic initialiser. + * + * @param n the order of the DSA group. + * @param d the DSA private value. + * @param message the message being signed. + */ + void init(BigInteger n, BigInteger d, byte[] message); + + /** + * Return the next valid value of K. + * + * @return a K value. + */ + BigInteger nextK(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java index a96cef0..292c408 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java @@ -1,5 +1,8 @@ package org.bouncycastle.crypto.signers; +import java.math.BigInteger; +import java.security.SecureRandom; + import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DSA; import org.bouncycastle.crypto.params.DSAKeyParameters; @@ -8,9 +11,6 @@ import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; import org.bouncycastle.crypto.params.DSAPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; -import java.math.BigInteger; -import java.security.SecureRandom; - /** * The Digital Signature Algorithm - as described in "Handbook of Applied * Cryptography", pages 452 - 453. @@ -18,9 +18,28 @@ import java.security.SecureRandom; public class DSASigner implements DSA { - DSAKeyParameters key; + private final DSAKCalculator kCalculator; - SecureRandom random; + private DSAKeyParameters key; + private SecureRandom random; + + /** + * Default configuration, random K values. + */ + public DSASigner() + { + this.kCalculator = new RandomDSAKCalculator(); + } + + /** + * Configuration with an alternate, possibly deterministic calculator of K. + * + * @param kCalculator a K value calculator. + */ + public DSASigner(DSAKCalculator kCalculator) + { + this.kCalculator = kCalculator; + } public void init( boolean forSigning, @@ -59,14 +78,17 @@ public class DSASigner { DSAParameters params = key.getParameters(); BigInteger m = calculateE(params.getQ(), message); - BigInteger k; - int qBitLength = params.getQ().bitLength(); - do + if (kCalculator.isDeterministic()) { - k = new BigInteger(qBitLength, random); + kCalculator.init(params.getQ(), ((DSAPrivateKeyParameters)key).getX(), message); } - while (k.compareTo(params.getQ()) >= 0); + else + { + kCalculator.init(params.getQ(), random); + } + + BigInteger k = kCalculator.nextK(); BigInteger r = params.getG().modPow(k, params.getP()).mod(params.getQ()); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java index a8fc194..0e76950 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java @@ -5,6 +5,7 @@ import java.security.SecureRandom; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; @@ -56,12 +57,17 @@ public class DSTU4145Signer public BigInteger[] generateSignature(byte[] message) { - ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); - if (h.toBigInteger().signum() == 0) + ECDomainParameters parameters = key.getParameters(); + + ECCurve curve = parameters.getCurve(); + + ECFieldElement h = hash2FieldElement(curve, message); + if (h.isZero()) { - h = key.getParameters().getCurve().fromBigInteger(ONE); + h = curve.fromBigInteger(ONE); } + BigInteger n = parameters.getN(); BigInteger e, r, s; ECFieldElement Fe, y; @@ -71,17 +77,17 @@ public class DSTU4145Signer { do { - e = generateRandomInteger(key.getParameters().getN(), random); - Fe = key.getParameters().getG().multiply(e).getX(); + e = generateRandomInteger(n, random); + Fe = parameters.getG().multiply(e).normalize().getAffineXCoord(); } - while (Fe.toBigInteger().signum() == 0); + while (Fe.isZero()); y = h.multiply(Fe); - r = fieldElement2Integer(key.getParameters().getN(), y); + r = fieldElement2Integer(n, y); } while (r.signum() == 0); - s = r.multiply(((ECPrivateKeyParameters)key).getD()).add(e).mod(key.getParameters().getN()); + s = r.multiply(((ECPrivateKeyParameters)key).getD()).add(e).mod(n); } while (s.signum() == 0); @@ -90,22 +96,28 @@ public class DSTU4145Signer public boolean verifySignature(byte[] message, BigInteger r, BigInteger s) { - if (r.signum() == 0 || s.signum() == 0) + if (r.signum() <= 0 || s.signum() <= 0) { return false; } - if (r.compareTo(key.getParameters().getN()) >= 0 || s.compareTo(key.getParameters().getN()) >= 0) + + ECDomainParameters parameters = key.getParameters(); + + BigInteger n = parameters.getN(); + if (r.compareTo(n) >= 0 || s.compareTo(n) >= 0) { return false; } - ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); - if (h.toBigInteger().signum() == 0) + ECCurve curve = parameters.getCurve(); + + ECFieldElement h = hash2FieldElement(curve, message); + if (h.isZero()) { - h = key.getParameters().getCurve().fromBigInteger(ONE); + h = curve.fromBigInteger(ONE); } - ECPoint R = ECAlgorithms.sumOfTwoMultiplies(key.getParameters().getG(), s, ((ECPublicKeyParameters)key).getQ(), r); + ECPoint R = ECAlgorithms.sumOfTwoMultiplies(parameters.getG(), s, ((ECPublicKeyParameters)key).getQ(), r).normalize(); // components must be bogus. if (R.isInfinity()) @@ -113,8 +125,8 @@ public class DSTU4145Signer return false; } - ECFieldElement y = h.multiply(R.getX()); - return fieldElement2Integer(key.getParameters().getN(), y).compareTo(r) == 0; + ECFieldElement y = h.multiply(R.getAffineXCoord()); + return fieldElement2Integer(n, y).compareTo(r) == 0; } /** @@ -142,7 +154,7 @@ public class DSTU4145Signer byte[] data = Arrays.clone(hash); reverseBytes(data); BigInteger num = new BigInteger(1, data); - while (num.bitLength() >= curve.getFieldSize()) + while (num.bitLength() > curve.getFieldSize()) { num = num.clearBit(num.bitLength() - 1); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java index a80c574..2a1f98e 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java @@ -19,9 +19,28 @@ import org.bouncycastle.math.ec.ECPoint; public class ECDSASigner implements ECConstants, DSA { - ECKeyParameters key; + private final DSAKCalculator kCalculator; - SecureRandom random; + private ECKeyParameters key; + private SecureRandom random; + + /** + * Default configuration, random K values. + */ + public ECDSASigner() + { + this.kCalculator = new RandomDSAKCalculator(); + } + + /** + * Configuration with an alternate, possibly deterministic calculator of K. + * + * @param kCalculator a K value calculator. + */ + public ECDSASigner(DSAKCalculator kCalculator) + { + this.kCalculator = kCalculator; + } public void init( boolean forSigning, @@ -64,24 +83,28 @@ public class ECDSASigner BigInteger r = null; BigInteger s = null; + if (kCalculator.isDeterministic()) + { + kCalculator.init(n, ((ECPrivateKeyParameters)key).getD(), message); + } + else + { + kCalculator.init(n, random); + } + // 5.3.2 do // generate s { BigInteger k = null; - int nBitLength = n.bitLength(); do // generate r { - do - { - k = new BigInteger(nBitLength, random); - } - while (k.equals(ZERO) || k.compareTo(n) >= 0); + k = kCalculator.nextK(); - ECPoint p = key.getParameters().getG().multiply(k); + ECPoint p = key.getParameters().getG().multiply(k).normalize(); // 5.3.3 - BigInteger x = p.getX().toBigInteger(); + BigInteger x = p.getAffineXCoord().toBigInteger(); r = x.mod(n); } @@ -135,7 +158,7 @@ public class ECDSASigner ECPoint G = key.getParameters().getG(); ECPoint Q = ((ECPublicKeyParameters)key).getQ(); - ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2); + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2).normalize(); // components must be bogus. if (point.isInfinity()) @@ -143,7 +166,7 @@ public class ECDSASigner return false; } - BigInteger v = point.getX().toBigInteger().mod(n); + BigInteger v = point.getAffineXCoord().toBigInteger().mod(n); return v.equals(r); } @@ -153,17 +176,11 @@ public class ECDSASigner int log2n = n.bitLength(); int messageBitLength = message.length * 8; - if (log2n >= messageBitLength) + BigInteger e = new BigInteger(1, message); + if (log2n < messageBitLength) { - return new BigInteger(1, message); - } - else - { - BigInteger trunc = new BigInteger(1, message); - - trunc = trunc.shiftRight(messageBitLength - log2n); - - return trunc; + e = e.shiftRight(messageBitLength - log2n); } + return e; } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java index 7256d35..f6d7f4f 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java @@ -82,9 +82,9 @@ public class ECGOST3410Signer } while (k.equals(ECConstants.ZERO)); - ECPoint p = key.getParameters().getG().multiply(k); + ECPoint p = key.getParameters().getG().multiply(k).normalize(); - BigInteger x = p.getX().toBigInteger(); + BigInteger x = p.getAffineXCoord().toBigInteger(); r = x.mod(n); } @@ -143,7 +143,7 @@ public class ECGOST3410Signer ECPoint G = key.getParameters().getG(); // P ECPoint Q = ((ECPublicKeyParameters)key).getQ(); - ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2); + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2).normalize(); // components must be bogus. if (point.isInfinity()) @@ -151,7 +151,7 @@ public class ECGOST3410Signer return false; } - BigInteger R = point.getX().toBigInteger().mod(n); + BigInteger R = point.getAffineXCoord().toBigInteger().mod(n); return R.equals(r); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java index 07e8ca7..72bbbcb 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java @@ -101,8 +101,8 @@ public class ECNRSigner // BigInteger Vx = tempPair.getPublic().getW().getAffineX(); ECPublicKeyParameters V = (ECPublicKeyParameters)tempPair.getPublic(); // get temp's public key - BigInteger Vx = V.getQ().getX().toBigInteger(); // get the point's x coordinate - + BigInteger Vx = V.getQ().normalize().getAffineXCoord().toBigInteger(); // get the point's x coordinate + r = Vx.add(e).mod(n); } while (r.equals(ECConstants.ZERO)); @@ -172,7 +172,7 @@ public class ECNRSigner ECPoint G = pubKey.getParameters().getG(); ECPoint W = pubKey.getQ(); // calculate P using Bouncy math - ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r); + ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r).normalize(); // components must be bogus. if (P.isInfinity()) @@ -180,7 +180,7 @@ public class ECNRSigner return false; } - BigInteger x = P.getX().toBigInteger(); + BigInteger x = P.getAffineXCoord().toBigInteger(); BigInteger t = r.subtract(x).mod(n); return t.equals(e); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/HMacDSAKCalculator.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/HMacDSAKCalculator.java new file mode 100644 index 0000000..b96e3f3 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/HMacDSAKCalculator.java @@ -0,0 +1,161 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; + +/** + * A deterministic K calculator based on the algorithm in section 3.2 of RFC 6979. + */ +public class HMacDSAKCalculator + implements DSAKCalculator +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private final HMac hMac; + private final byte[] K; + private final byte[] V; + + private BigInteger n; + + /** + * Base constructor. + * + * @param digest digest to build the HMAC on. + */ + public HMacDSAKCalculator(Digest digest) + { + this.hMac = new HMac(digest); + this.V = new byte[hMac.getMacSize()]; + this.K = new byte[hMac.getMacSize()]; + } + + public boolean isDeterministic() + { + return true; + } + + public void init(BigInteger n, SecureRandom random) + { + throw new IllegalStateException("Operation not supported"); + } + + public void init(BigInteger n, BigInteger d, byte[] message) + { + this.n = n; + + Arrays.fill(V, (byte)0x01); + Arrays.fill(K, (byte)0); + + byte[] x = new byte[(n.bitLength() + 7) / 8]; + byte[] dVal = BigIntegers.asUnsignedByteArray(d); + + System.arraycopy(dVal, 0, x, x.length - dVal.length, dVal.length); + + byte[] m = new byte[(n.bitLength() + 7) / 8]; + + BigInteger mInt = bitsToInt(message); + + if (mInt.compareTo(n) > 0) + { + mInt = mInt.subtract(n); + } + + byte[] mVal = BigIntegers.asUnsignedByteArray(mInt); + + System.arraycopy(mVal, 0, m, m.length - mVal.length, mVal.length); + + hMac.init(new KeyParameter(K)); + + hMac.update(V, 0, V.length); + hMac.update((byte)0x00); + hMac.update(x, 0, x.length); + hMac.update(m, 0, m.length); + + hMac.doFinal(K, 0); + + hMac.init(new KeyParameter(K)); + + hMac.update(V, 0, V.length); + + hMac.doFinal(V, 0); + + hMac.update(V, 0, V.length); + hMac.update((byte)0x01); + hMac.update(x, 0, x.length); + hMac.update(m, 0, m.length); + + hMac.doFinal(K, 0); + + hMac.init(new KeyParameter(K)); + + hMac.update(V, 0, V.length); + + hMac.doFinal(V, 0); + } + + public BigInteger nextK() + { + byte[] t = new byte[((n.bitLength() + 7) / 8)]; + + for (;;) + { + int tOff = 0; + + while (tOff < t.length) + { + hMac.update(V, 0, V.length); + + hMac.doFinal(V, 0); + + if (t.length - tOff < V.length) + { + System.arraycopy(V, 0, t, tOff, t.length - tOff); + tOff += t.length - tOff; + } + else + { + System.arraycopy(V, 0, t, tOff, V.length); + tOff += V.length; + } + } + + BigInteger k = bitsToInt(t); + + if (k.equals(ZERO) || k.compareTo(n) >= 0) + { + hMac.update(V, 0, V.length); + hMac.update((byte)0x00); + + hMac.doFinal(K, 0); + + hMac.init(new KeyParameter(K)); + + hMac.update(V, 0, V.length); + + hMac.doFinal(V, 0); + } + else + { + return k; + } + } + } + + private BigInteger bitsToInt(byte[] t) + { + BigInteger v = new BigInteger(1, t); + + if (t.length * 8 > n.bitLength()) + { + v = v.shiftRight(t.length * 8 - n.bitLength()); + } + + return v; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java index f33ed31..aaae064 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java @@ -57,9 +57,15 @@ public class RSADigestSigner public RSADigestSigner( Digest digest) { - this.digest = digest; + this(digest, (ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName())); + } - algId = new AlgorithmIdentifier((ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE); + public RSADigestSigner( + Digest digest, + ASN1ObjectIdentifier digestOid) + { + this.digest = digest; + this.algId = new AlgorithmIdentifier(digestOid, DERNull.INSTANCE); } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/RandomDSAKCalculator.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/RandomDSAKCalculator.java new file mode 100644 index 0000000..bbd8cda --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/RandomDSAKCalculator.java @@ -0,0 +1,43 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +class RandomDSAKCalculator + implements DSAKCalculator +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private BigInteger q; + private SecureRandom random; + + public boolean isDeterministic() + { + return false; + } + + public void init(BigInteger n, SecureRandom random) + { + this.q = n; + this.random = random; + } + + public void init(BigInteger n, BigInteger d, byte[] message) + { + throw new IllegalStateException("Operation not supported"); + } + + public BigInteger nextK() + { + int qBitLength = q.bitLength(); + + BigInteger k; + do + { + k = new BigInteger(qBitLength, random); + } + while (k.equals(ZERO) || k.compareTo(q) >= 0); + + return k; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/signers/package.html deleted file mode 100644 index 151d3d5..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Basic signers. -</body> -</html> diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsAgreementCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsAgreementCredentials.java new file mode 100644 index 0000000..ef7f4fb --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsAgreementCredentials.java @@ -0,0 +1,7 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsAgreementCredentials + extends AbstractTlsCredentials + implements TlsAgreementCredentials +{ +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java index 9c2a526..71a2cab 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java @@ -5,11 +5,9 @@ import java.io.IOException; public class AbstractTlsCipherFactory implements TlsCipherFactory { - public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) throws IOException { - throw new TlsFatalAlert(AlertDescription.internal_error); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java index 9e113f9..7d4fd03 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsClient.java @@ -8,12 +8,13 @@ public abstract class AbstractTlsClient extends AbstractTlsPeer implements TlsClient { - protected TlsCipherFactory cipherFactory; protected TlsClientContext context; protected Vector supportedSignatureAlgorithms; + protected int[] namedCurves; + protected short[] clientECPointFormats, serverECPointFormats; protected int selectedCipherSuite; protected short selectedCompressionMethod; @@ -33,6 +34,11 @@ public abstract class AbstractTlsClient this.context = context; } + public TlsSession getSessionToResume() + { + return null; + } + /** * RFC 5246 E.1. "TLS clients that wish to negotiate with older servers MAY send any value * {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest @@ -46,7 +52,7 @@ public abstract class AbstractTlsClient // return ProtocolVersion.SSLv3; // "the lowest version number supported by the client" - // return getMinimumServerVersion(); + // return getMinimumVersion(); // "the value of ClientHello.client_version" return getClientVersion(); @@ -54,13 +60,12 @@ public abstract class AbstractTlsClient public ProtocolVersion getClientVersion() { - return ProtocolVersion.TLSv11; + return ProtocolVersion.TLSv12; } public Hashtable getClientExtensions() throws IOException { - Hashtable clientExtensions = null; ProtocolVersion clientVersion = context.getClientVersion(); @@ -71,14 +76,13 @@ public abstract class AbstractTlsClient */ if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) { - // TODO Provide a way for the user to specify the acceptable hash/signature algorithms. - short[] hashAlgorithms = new short[]{HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, - HashAlgorithm.sha224, HashAlgorithm.sha1}; + short[] hashAlgorithms = new short[]{ HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, + HashAlgorithm.sha224, HashAlgorithm.sha1 }; // TODO Sort out ECDSA signatures and add them as the preferred option here - short[] signatureAlgorithms = new short[]{SignatureAlgorithm.rsa}; + short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.rsa }; this.supportedSignatureAlgorithms = new Vector(); for (int i = 0; i < hashAlgorithms.length; ++i) @@ -96,14 +100,33 @@ public abstract class AbstractTlsClient this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.dsa)); - if (clientExtensions == null) - { - clientExtensions = new Hashtable(); - } + clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(clientExtensions); TlsUtils.addSignatureAlgorithmsExtension(clientExtensions, supportedSignatureAlgorithms); } + if (TlsECCUtils.containsECCCipherSuites(getCipherSuites())) + { + /* + * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message + * appends these extensions (along with any others), enumerating the curves it supports + * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic + * Curves Extension and the Supported Point Formats Extension. + */ + /* + * TODO Could just add all the curves since we support them all, but users may not want + * to use unnecessarily large fields. Need configuration options. + */ + this.namedCurves = new int[]{ NamedCurve.secp256r1, NamedCurve.secp384r1 }; + this.clientECPointFormats = new short[]{ ECPointFormat.uncompressed, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, }; + + clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(clientExtensions); + + TlsECCUtils.addSupportedEllipticCurvesExtension(clientExtensions, namedCurves); + TlsECCUtils.addSupportedPointFormatsExtension(clientExtensions, clientECPointFormats); + } + return clientExtensions; } @@ -141,19 +164,6 @@ public abstract class AbstractTlsClient this.selectedCompressionMethod = selectedCompressionMethod; } - public void notifySecureRenegotiation(boolean secureRenegotiation) - throws IOException - { - if (!secureRenegotiation) - { - /* - * RFC 5746 3.4. In this case, some clients may want to terminate the handshake instead - * of continuing; see Section 4.1 for discussion. - */ - // throw new TlsFatalAlert(AlertDescription.handshake_failure); - } - } - public void processServerExtensions(Hashtable serverExtensions) throws IOException { @@ -170,6 +180,18 @@ public abstract class AbstractTlsClient { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } + + int[] namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(serverExtensions); + if (namedCurves != null) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + this.serverECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(serverExtensions); + if (this.serverECPointFormats != null && !TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } } } @@ -210,9 +232,4 @@ public abstract class AbstractTlsClient throws IOException { } - - public void notifyHandshakeComplete() - throws IOException - { - } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java index 1ff67e3..5e02892 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsContext.java @@ -5,12 +5,12 @@ import java.security.SecureRandom; abstract class AbstractTlsContext implements TlsContext { - private SecureRandom secureRandom; private SecurityParameters securityParameters; private ProtocolVersion clientVersion = null; private ProtocolVersion serverVersion = null; + private TlsSession session = null; private Object userObject = null; AbstractTlsContext(SecureRandom secureRandom, SecurityParameters securityParameters) @@ -34,7 +34,7 @@ abstract class AbstractTlsContext return clientVersion; } - public void setClientVersion(ProtocolVersion clientVersion) + void setClientVersion(ProtocolVersion clientVersion) { this.clientVersion = clientVersion; } @@ -44,11 +44,21 @@ abstract class AbstractTlsContext return serverVersion; } - public void setServerVersion(ProtocolVersion serverVersion) + void setServerVersion(ProtocolVersion serverVersion) { this.serverVersion = serverVersion; } + public TlsSession getResumableSession() + { + return session; + } + + void setResumableSession(TlsSession session) + { + this.session = session; + } + public Object getUserObject() { return userObject; @@ -61,6 +71,10 @@ abstract class AbstractTlsContext public byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length) { + if (context_value != null && !TlsUtils.isValidUint16(context_value.length)) + { + throw new IllegalArgumentException("'context_value' must have length less than 2^16 (or be null)"); + } SecurityParameters sp = getSecurityParameters(); byte[] cr = sp.getClientRandom(), sr = sp.getServerRandom(); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCredentials.java new file mode 100644 index 0000000..b98743f --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsCredentials.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsCredentials + implements TlsCredentials +{ +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsEncryptionCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsEncryptionCredentials.java new file mode 100644 index 0000000..e6ff39b --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsEncryptionCredentials.java @@ -0,0 +1,7 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsEncryptionCredentials + extends AbstractTlsCredentials + implements TlsEncryptionCredentials +{ +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java index 85057c1..43e80e6 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java @@ -7,7 +7,6 @@ import java.util.Vector; public abstract class AbstractTlsKeyExchange implements TlsKeyExchange { - protected int keyExchange; protected Vector supportedSignatureAlgorithms; @@ -27,7 +26,6 @@ public abstract class AbstractTlsKeyExchange if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) { - /* * RFC 5264 7.4.1.4.1. If the client does not send the signature_algorithms extension, * the server MUST do the following: @@ -45,7 +43,6 @@ public abstract class AbstractTlsKeyExchange { switch (keyExchange) { - case KeyExchangeAlgorithm.DH_DSS: case KeyExchangeAlgorithm.DHE_DSS: case KeyExchangeAlgorithm.SRP_DSS: @@ -73,6 +70,12 @@ public abstract class AbstractTlsKeyExchange break; } + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.SRP: + break; + default: throw new IllegalStateException("unsupported key exchange algorithm"); } @@ -88,7 +91,6 @@ public abstract class AbstractTlsKeyExchange public void processServerCertificate(Certificate serverCertificate) throws IOException { - if (supportedSignatureAlgorithms == null) { /* diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java index bdfd0d5..80d6af7 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsPeer.java @@ -1,8 +1,21 @@ package org.bouncycastle.crypto.tls; +import java.io.IOException; + public abstract class AbstractTlsPeer implements TlsPeer { + public void notifySecureRenegotiation(boolean secureRenegotiation) throws IOException + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4/3.6. In this case, some clients/servers may want to terminate the handshake instead + * of continuing; see Section 4.1/4.3 for discussion. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause) { @@ -11,4 +24,8 @@ public abstract class AbstractTlsPeer public void notifyAlertReceived(short alertLevel, short alertDescription) { } + + public void notifyHandshakeComplete() throws IOException + { + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java index 8235fd1..bd428a9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsServer.java @@ -4,11 +4,12 @@ import java.io.IOException; import java.util.Hashtable; import java.util.Vector; +import org.bouncycastle.util.Arrays; + public abstract class AbstractTlsServer extends AbstractTlsPeer implements TlsServer { - protected TlsCipherFactory cipherFactory; protected TlsServerContext context; @@ -18,6 +19,8 @@ public abstract class AbstractTlsServer protected short[] offeredCompressionMethods; protected Hashtable clientExtensions; + protected short maxFragmentLengthOffered; + protected boolean truncatedHMacOffered; protected Vector supportedSignatureAlgorithms; protected boolean eccCipherSuitesOffered; protected int[] namedCurves; @@ -38,6 +41,16 @@ public abstract class AbstractTlsServer this.cipherFactory = cipherFactory; } + protected boolean allowTruncatedHMac() + { + return false; + } + + protected Hashtable checkServerExtensions() + { + return this.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.serverExtensions); + } + protected abstract int[] getCipherSuites(); protected short[] getCompressionMethods() @@ -57,7 +70,6 @@ public abstract class AbstractTlsServer protected boolean supportsClientECCCapabilities(int[] namedCurves, short[] ecPointFormats) { - // NOTE: BC supports all the current set of point formats so we don't check them here if (namedCurves == null) @@ -106,27 +118,15 @@ public abstract class AbstractTlsServer this.offeredCompressionMethods = offeredCompressionMethods; } - public void notifySecureRenegotiation(boolean secureRenegotiation) - throws IOException - { - if (!secureRenegotiation) - { - /* - * RFC 5746 3.6. In this case, some servers may want to terminate the handshake instead - * of continuing; see Section 4.3 for discussion. - */ - throw new TlsFatalAlert(AlertDescription.handshake_failure); - } - } - public void processClientExtensions(Hashtable clientExtensions) throws IOException { - this.clientExtensions = clientExtensions; if (clientExtensions != null) { + this.maxFragmentLengthOffered = TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions); + this.truncatedHMacOffered = TlsExtensionsUtils.hasTruncatedHMacExtension(clientExtensions); this.supportedSignatureAlgorithms = TlsUtils.getSignatureAlgorithmsExtension(clientExtensions); if (this.supportedSignatureAlgorithms != null) @@ -176,7 +176,6 @@ public abstract class AbstractTlsServer public int getSelectedCipherSuite() throws IOException { - /* * TODO RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate * cipher suites against the "signature_algorithms" extension before selecting them. This is @@ -197,7 +196,9 @@ public abstract class AbstractTlsServer for (int i = 0; i < cipherSuites.length; ++i) { int cipherSuite = cipherSuites[i]; - if (TlsProtocol.arrayContains(this.offeredCipherSuites, cipherSuite) + + // TODO Certain cipher suites may only be available starting at a particular version + if (Arrays.contains(this.offeredCipherSuites, cipherSuite) && (eccCipherSuitesEnabled || !TlsECCUtils.isECCCipherSuite(cipherSuite))) { return this.selectedCipherSuite = cipherSuite; @@ -212,7 +213,7 @@ public abstract class AbstractTlsServer short[] compressionMethods = getCompressionMethods(); for (int i = 0; i < compressionMethods.length; ++i) { - if (TlsProtocol.arrayContains(offeredCompressionMethods, compressionMethods[i])) + if (Arrays.contains(offeredCompressionMethods, compressionMethods[i])) { return this.selectedCompressionMethod = compressionMethods[i]; } @@ -224,6 +225,15 @@ public abstract class AbstractTlsServer public Hashtable getServerExtensions() throws IOException { + if (this.maxFragmentLengthOffered >= 0) + { + TlsExtensionsUtils.addMaxFragmentLengthExtension(checkServerExtensions(), this.maxFragmentLengthOffered); + } + + if (this.truncatedHMacOffered && allowTruncatedHMac()) + { + TlsExtensionsUtils.addTruncatedHMacExtension(checkServerExtensions()); + } if (this.clientECPointFormats != null && TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) { @@ -232,15 +242,13 @@ public abstract class AbstractTlsServer * message including a Supported Point Formats Extension appends this extension (along * with others) to its ServerHello message, enumerating the point formats it can parse. */ - this.serverECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2, - ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed}; + this.serverECPointFormats = new short[]{ ECPointFormat.ansiX962_compressed_char2, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed }; - this.serverExtensions = new Hashtable(); - TlsECCUtils.addSupportedPointFormatsExtension(serverExtensions, serverECPointFormats); - return serverExtensions; + TlsECCUtils.addSupportedPointFormatsExtension(checkServerExtensions(), serverECPointFormats); } - return null; + return serverExtensions; } public Vector getServerSupplementalData() @@ -249,7 +257,14 @@ public abstract class AbstractTlsServer return null; } + public CertificateStatus getCertificateStatus() + throws IOException + { + return null; + } + public CertificateRequest getCertificateRequest() + throws IOException { return null; } @@ -296,9 +311,4 @@ public abstract class AbstractTlsServer */ return new NewSessionTicket(0L, TlsUtils.EMPTY_BYTES); } - - public void notifyHandshakeComplete() - throws IOException - { - } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java index a0c24c7..3a1d631 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSigner.java @@ -1,13 +1,38 @@ package org.bouncycastle.crypto.tls; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + public abstract class AbstractTlsSigner implements TlsSigner { - protected TlsContext context; public void init(TlsContext context) { this.context = context; } + + public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + throws CryptoException + { + return generateRawSignature(null, privateKey, md5AndSha1); + } + + public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + throws CryptoException + { + return verifyRawSignature(null, sigBytes, publicKey, md5AndSha1); + } + + public Signer createSigner(AsymmetricKeyParameter privateKey) + { + return createSigner(null, privateKey); + } + + public Signer createVerifyer(AsymmetricKeyParameter publicKey) + { + return createVerifyer(null, publicKey); + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSignerCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSignerCredentials.java new file mode 100644 index 0000000..3452f52 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AbstractTlsSignerCredentials.java @@ -0,0 +1,11 @@ +package org.bouncycastle.crypto.tls; + +public abstract class AbstractTlsSignerCredentials + extends AbstractTlsCredentials + implements TlsSignerCredentials +{ + public SignatureAndHashAlgorithm getSignatureAndHashAlgorithm() + { + throw new IllegalStateException("TlsSignerCredentials implementation does not support (D)TLS 1.2+"); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java index 5e3269b..91366be 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/AlertDescription.java @@ -5,11 +5,12 @@ package org.bouncycastle.crypto.tls; */ public class AlertDescription { - /** * This message notifies the recipient that the sender will not send any more messages on this - * connection. The session becomes unresumable if any connection is terminated without proper - * close_notify messages with level equal to warning. + * connection. Note that as of TLS 1.1, failure to properly close a connection no longer + * requires that a session not be resumed. This is a change from TLS 1.0 ("The session becomes + * unresumable if any connection is terminated without proper close_notify messages with level + * equal to warning.") to conform with widespread implementation practice. */ public static final short close_notify = 0; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java index 8b9d4ab..7642c4a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ByteQueue.java @@ -25,12 +25,12 @@ public class ByteQueue /** * The initial size for our buffer. */ - private static final int INITBUFSIZE = 1024; + private static final int DEFAULT_CAPACITY = 1024; /** * The buffer where we store our data. */ - private byte[] databuf = new byte[ByteQueue.INITBUFSIZE]; + private byte[] databuf;; /** * How many bytes at the beginning of the buffer are skipped. @@ -42,6 +42,16 @@ public class ByteQueue */ private int available = 0; + public ByteQueue() + { + this(DEFAULT_CAPACITY); + } + + public ByteQueue(int capacity) + { + databuf = new byte[capacity]; + } + /** * Read data from the buffer. * @@ -62,26 +72,34 @@ public class ByteQueue + " is too small for a read of " + len + " bytes"); } System.arraycopy(databuf, skipped + skip, buf, offset, len); - return; } /** * Add some data to our buffer. * - * @param data A byte-array to read data from. - * @param offset How many bytes to skip at the beginning of the array. - * @param len How many bytes to read from the array. + * @param buf A byte-array to read data from. + * @param off How many bytes to skip at the beginning of the array. + * @param len How many bytes to read from the array. */ - public void addData(byte[] data, int offset, int len) + public void addData(byte[] buf, int off, int len) { if ((skipped + available + len) > databuf.length) { - byte[] tmp = new byte[ByteQueue.nextTwoPow(data.length)]; - System.arraycopy(databuf, skipped, tmp, 0, available); + int desiredSize = ByteQueue.nextTwoPow(available + len); + if (desiredSize > databuf.length) + { + byte[] tmp = new byte[desiredSize]; + System.arraycopy(databuf, skipped, tmp, 0, available); + databuf = tmp; + } + else + { + System.arraycopy(databuf, skipped, databuf, 0, available); + } skipped = 0; - databuf = tmp; } - System.arraycopy(data, offset, databuf, skipped + available, len); + + System.arraycopy(buf, off, databuf, skipped + available, len); available += len; } @@ -102,15 +120,27 @@ public class ByteQueue */ available -= i; skipped += i; + } - /* - * If more than half of our data is skipped, we will move the data in the buffer. - */ - if (skipped > (databuf.length / 2)) - { - System.arraycopy(databuf, skipped, databuf, 0, available); - skipped = 0; - } + /** + * Remove data from the buffer. + * + * @param buf The buffer where the removed data will be copied to. + * @param off How many bytes to skip at the beginning of buf. + * @param len How many bytes to read at all. + * @param skip How many bytes from our data to skip. + */ + public void removeData(byte[] buf, int off, int len, int skip) + { + read(buf, off, len, skip); + removeData(skip + len); + } + + public byte[] removeData(int len, int skip) + { + byte[] buf = new byte[len]; + removeData(buf, 0, len, skip); + return buf; } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertChainType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertChainType.java new file mode 100644 index 0000000..8902ed7 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertChainType.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.tls; + +/* + * RFC 3546 3.3. + */ +public class CertChainType +{ + public static final short individual_certs = 0; + public static final short pkipath = 1; + + public static boolean isValid(short certChainType) + { + return certChainType >= individual_certs && certChainType <= pkipath; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/Certificate.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/Certificate.java index fab79f4..02cf693 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/Certificate.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/Certificate.java @@ -7,7 +7,6 @@ import java.io.OutputStream; import java.util.Vector; import org.bouncycastle.asn1.ASN1Encoding; -import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Primitive; /** @@ -25,7 +24,6 @@ import org.bouncycastle.asn1.ASN1Primitive; */ public class Certificate { - public static final Certificate EMPTY_CHAIN = new Certificate( new org.bouncycastle.asn1.x509.Certificate[0]); @@ -46,7 +44,7 @@ public class Certificate */ public org.bouncycastle.asn1.x509.Certificate[] getCerts() { - return clone(certificateList); + return getCertificateList(); } /** @@ -55,7 +53,7 @@ public class Certificate */ public org.bouncycastle.asn1.x509.Certificate[] getCertificateList() { - return clone(certificateList); + return cloneCertificateList(); } public org.bouncycastle.asn1.x509.Certificate getCertificateAt(int index) @@ -86,21 +84,23 @@ public class Certificate public void encode(OutputStream output) throws IOException { - Vector encCerts = new Vector(this.certificateList.length); + Vector derEncodings = new Vector(this.certificateList.length); + int totalLength = 0; for (int i = 0; i < this.certificateList.length; ++i) { - byte[] encCert = certificateList[i].getEncoded(ASN1Encoding.DER); - encCerts.addElement(encCert); - totalLength += encCert.length + 3; + byte[] derEncoding = certificateList[i].getEncoded(ASN1Encoding.DER); + derEncodings.addElement(derEncoding); + totalLength += derEncoding.length + 3; } + TlsUtils.checkUint24(totalLength); TlsUtils.writeUint24(totalLength, output); - for (int i = 0; i < encCerts.size(); ++i) + for (int i = 0; i < derEncodings.size(); ++i) { - byte[] encCert = (byte[])encCerts.elementAt(i); - TlsUtils.writeOpaque24(encCert, output); + byte[] derEncoding = (byte[])derEncodings.elementAt(i); + TlsUtils.writeOpaque24(derEncoding, output); } } @@ -114,40 +114,36 @@ public class Certificate public static Certificate parse(InputStream input) throws IOException { - org.bouncycastle.asn1.x509.Certificate[] certs; - int left = TlsUtils.readUint24(input); - if (left == 0) + int totalLength = TlsUtils.readUint24(input); + if (totalLength == 0) { return EMPTY_CHAIN; } - Vector tmp = new Vector(); - while (left > 0) - { - int size = TlsUtils.readUint24(input); - left -= 3 + size; - byte[] buf = TlsUtils.readFully(size, input); + byte[] certListData = TlsUtils.readFully(totalLength, input); - ByteArrayInputStream bis = new ByteArrayInputStream(buf); - ASN1Primitive asn1 = new ASN1InputStream(bis).readObject(); - TlsProtocol.assertEmpty(bis); + ByteArrayInputStream buf = new ByteArrayInputStream(certListData); - tmp.addElement(org.bouncycastle.asn1.x509.Certificate.getInstance(asn1)); + Vector certificate_list = new Vector(); + while (buf.available() > 0) + { + byte[] derEncoding = TlsUtils.readOpaque24(buf); + ASN1Primitive asn1Cert = TlsUtils.readDERObject(derEncoding); + certificate_list.addElement(org.bouncycastle.asn1.x509.Certificate.getInstance(asn1Cert)); } - certs = new org.bouncycastle.asn1.x509.Certificate[tmp.size()]; - for (int i = 0; i < tmp.size(); i++) + + org.bouncycastle.asn1.x509.Certificate[] certificateList = new org.bouncycastle.asn1.x509.Certificate[certificate_list.size()]; + for (int i = 0; i < certificate_list.size(); i++) { - certs[i] = (org.bouncycastle.asn1.x509.Certificate)tmp.elementAt(i); + certificateList[i] = (org.bouncycastle.asn1.x509.Certificate)certificate_list.elementAt(i); } - return new Certificate(certs); + return new Certificate(certificateList); } - private org.bouncycastle.asn1.x509.Certificate[] clone(org.bouncycastle.asn1.x509.Certificate[] list) + protected org.bouncycastle.asn1.x509.Certificate[] cloneCertificateList() { - org.bouncycastle.asn1.x509.Certificate[] rv = new org.bouncycastle.asn1.x509.Certificate[list.length]; - - System.arraycopy(list, 0, rv, 0, rv.length); - - return rv; + org.bouncycastle.asn1.x509.Certificate[] result = new org.bouncycastle.asn1.x509.Certificate[certificateList.length]; + System.arraycopy(certificateList, 0, result, 0, result.length); + return result; } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java index 00bf950..e9606e3 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateRequest.java @@ -25,8 +25,9 @@ import org.bouncycastle.asn1.x500.X500Name; */ public class CertificateRequest { - private short[] certificateTypes; - private Vector certificateAuthorities; + protected short[] certificateTypes; + protected Vector supportedSignatureAlgorithms; + protected Vector certificateAuthorities; /* * TODO RFC 5264 7.4.4 A list of the hash/signature algorithm pairs that the server is able to @@ -37,9 +38,10 @@ public class CertificateRequest * @param certificateTypes see {@link ClientCertificateType} for valid constants. * @param certificateAuthorities a {@link Vector} of {@link X500Name}. */ - public CertificateRequest(short[] certificateTypes, Vector certificateAuthorities) + public CertificateRequest(short[] certificateTypes, Vector supportedSignatureAlgorithms, Vector certificateAuthorities) { this.certificateTypes = certificateTypes; + this.supportedSignatureAlgorithms = supportedSignatureAlgorithms; this.certificateAuthorities = certificateAuthorities; } @@ -53,6 +55,14 @@ public class CertificateRequest } /** + * @return a {@link Vector} of {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + */ + public Vector getSupportedSignatureAlgorithms() + { + return supportedSignatureAlgorithms; + } + + /** * @return a {@link Vector} of {@link X500Name} */ public Vector getCertificateAuthorities() @@ -69,15 +79,19 @@ public class CertificateRequest public void encode(OutputStream output) throws IOException { - if (certificateTypes == null || certificateTypes.length == 0) { - TlsUtils.writeUint8((short)0, output); + TlsUtils.writeUint8(0, output); } else { - TlsUtils.writeUint8((short)certificateTypes.length, output); - TlsUtils.writeUint8Array(certificateTypes, output); + TlsUtils.writeUint8ArrayWithUint8Length(certificateTypes, output); + } + + if (supportedSignatureAlgorithms != null) + { + // TODO Check whether SignatureAlgorithm.anonymous is allowed here + TlsUtils.encodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, false, output); } if (certificateAuthorities == null || certificateAuthorities.isEmpty()) @@ -86,22 +100,23 @@ public class CertificateRequest } else { + Vector derEncodings = new Vector(certificateAuthorities.size()); - Vector encDNs = new Vector(certificateAuthorities.size()); int totalLength = 0; for (int i = 0; i < certificateAuthorities.size(); ++i) { - X500Name authorityDN = (X500Name)certificateAuthorities.elementAt(i); - byte[] encDN = authorityDN.getEncoded(ASN1Encoding.DER); - encDNs.addElement(encDN); - totalLength += encDN.length; + X500Name certificateAuthority = (X500Name)certificateAuthorities.elementAt(i); + byte[] derEncoding = certificateAuthority.getEncoded(ASN1Encoding.DER); + derEncodings.addElement(derEncoding); + totalLength += derEncoding.length; } + TlsUtils.checkUint16(totalLength); TlsUtils.writeUint16(totalLength, output); - for (int i = 0; i < encDNs.size(); ++i) + for (int i = 0; i < derEncodings.size(); ++i) { - byte[] encDN = (byte[])encDNs.elementAt(i); + byte[] encDN = (byte[])derEncodings.elementAt(i); output.write(encDN); } } @@ -109,12 +124,15 @@ public class CertificateRequest /** * Parse a {@link CertificateRequest} from an {@link InputStream}. - * - * @param input the {@link InputStream} to parse from. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link InputStream} to parse from. * @return a {@link CertificateRequest} object. * @throws IOException */ - public static CertificateRequest parse(InputStream input) + public static CertificateRequest parse(TlsContext context, InputStream input) throws IOException { int numTypes = TlsUtils.readUint8(input); @@ -124,17 +142,23 @@ public class CertificateRequest certificateTypes[i] = TlsUtils.readUint8(input); } - byte[] authorities = TlsUtils.readOpaque16(input); - - Vector authorityDNs = new Vector(); + Vector supportedSignatureAlgorithms = null; + if (TlsUtils.isTLSv12(context)) + { + // TODO Check whether SignatureAlgorithm.anonymous is allowed here + supportedSignatureAlgorithms = TlsUtils.parseSupportedSignatureAlgorithms(false, input); + } - ByteArrayInputStream bis = new ByteArrayInputStream(authorities); + Vector certificateAuthorities = new Vector(); + byte[] certAuthData = TlsUtils.readOpaque16(input); + ByteArrayInputStream bis = new ByteArrayInputStream(certAuthData); while (bis.available() > 0) { - byte[] dnBytes = TlsUtils.readOpaque16(bis); - authorityDNs.addElement(X500Name.getInstance(ASN1Primitive.fromByteArray(dnBytes))); + byte[] derEncoding = TlsUtils.readOpaque16(bis); + ASN1Primitive asn1 = TlsUtils.readDERObject(derEncoding); + certificateAuthorities.addElement(X500Name.getInstance(asn1)); } - return new CertificateRequest(certificateTypes, authorityDNs); + return new CertificateRequest(certificateTypes, supportedSignatureAlgorithms, certificateAuthorities); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatus.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatus.java new file mode 100644 index 0000000..34a0284 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatus.java @@ -0,0 +1,105 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ocsp.OCSPResponse; + +public class CertificateStatus +{ + protected short statusType; + protected Object response; + + public CertificateStatus(short statusType, Object response) + { + if (!isCorrectType(statusType, response)) + { + throw new IllegalArgumentException("'response' is not an instance of the correct type"); + } + + this.statusType = statusType; + this.response = response; + } + + public short getStatusType() + { + return statusType; + } + + public Object getResponse() + { + return response; + } + + public OCSPResponse getOCSPResponse() + { + if (!isCorrectType(CertificateStatusType.ocsp, response)) + { + throw new IllegalStateException("'response' is not an OCSPResponse"); + } + return (OCSPResponse)response; + } + + /** + * Encode this {@link CertificateStatus} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + TlsUtils.writeUint8(statusType, output); + + switch (statusType) + { + case CertificateStatusType.ocsp: + byte[] derEncoding = ((OCSPResponse) response).getEncoded(ASN1Encoding.DER); + TlsUtils.writeOpaque24(derEncoding, output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link CertificateStatus} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link CertificateStatus} object. + * @throws IOException + */ + public static CertificateStatus parse(InputStream input) throws IOException + { + short status_type = TlsUtils.readUint8(input); + Object response; + + switch (status_type) + { + case CertificateStatusType.ocsp: + { + byte[] derEncoding = TlsUtils.readOpaque24(input); + response = OCSPResponse.getInstance(TlsUtils.readDERObject(derEncoding)); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new CertificateStatus(status_type, response); + } + + protected static boolean isCorrectType(short statusType, Object response) + { + switch (statusType) + { + case CertificateStatusType.ocsp: + return response instanceof OCSPResponse; + default: + throw new IllegalArgumentException("'statusType' is an unsupported value"); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusRequest.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusRequest.java new file mode 100644 index 0000000..b947c48 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusRequest.java @@ -0,0 +1,98 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class CertificateStatusRequest +{ + protected short statusType; + protected Object request; + + public CertificateStatusRequest(short statusType, Object request) + { + if (!isCorrectType(statusType, request)) + { + throw new IllegalArgumentException("'request' is not an instance of the correct type"); + } + + this.statusType = statusType; + this.request = request; + } + + public short getStatusType() + { + return statusType; + } + + public Object getRequest() + { + return request; + } + + public OCSPStatusRequest getOCSPStatusRequest() + { + if (!isCorrectType(CertificateStatusType.ocsp, request)) + { + throw new IllegalStateException("'request' is not an OCSPStatusRequest"); + } + return (OCSPStatusRequest)request; + } + + /** + * Encode this {@link CertificateStatusRequest} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + TlsUtils.writeUint8(statusType, output); + + switch (statusType) + { + case CertificateStatusType.ocsp: + ((OCSPStatusRequest) request).encode(output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link CertificateStatusRequest} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link CertificateStatusRequest} object. + * @throws IOException + */ + public static CertificateStatusRequest parse(InputStream input) throws IOException + { + short status_type = TlsUtils.readUint8(input); + Object result; + + switch (status_type) + { + case CertificateStatusType.ocsp: + result = OCSPStatusRequest.parse(input); + break; + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new CertificateStatusRequest(status_type, result); + } + + protected static boolean isCorrectType(short statusType, Object request) + { + switch (statusType) + { + case CertificateStatusType.ocsp: + return request instanceof OCSPStatusRequest; + default: + throw new IllegalArgumentException("'statusType' is an unsupported value"); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusType.java new file mode 100644 index 0000000..bfe8298 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateStatusType.java @@ -0,0 +1,9 @@ +package org.bouncycastle.crypto.tls; + +public class CertificateStatusType +{ + /* + * RFC 3546 3.6 + */ + public static final short ocsp = 1; +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateURL.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateURL.java new file mode 100644 index 0000000..aab8908 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CertificateURL.java @@ -0,0 +1,133 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +/* + * RFC 3546 3.3 + */ +public class CertificateURL +{ + protected short type; + protected Vector urlAndHashList; + + /** + * @param type + * see {@link CertChainType} for valid constants. + * @param urlAndHashList + * a {@link Vector} of {@link URLAndHash}. + */ + public CertificateURL(short type, Vector urlAndHashList) + { + if (!CertChainType.isValid(type)) + { + throw new IllegalArgumentException("'type' is not a valid CertChainType value"); + } + if (urlAndHashList == null || urlAndHashList.isEmpty()) + { + throw new IllegalArgumentException("'urlAndHashList' must have length > 0"); + } + + this.type = type; + this.urlAndHashList = urlAndHashList; + } + + /** + * @return {@link CertChainType} + */ + public short getType() + { + return type; + } + + /** + * @return a {@link Vector} of {@link URLAndHash} + */ + public Vector getURLAndHashList() + { + return urlAndHashList; + } + + /** + * Encode this {@link CertificateURL} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) + throws IOException + { + TlsUtils.writeUint8(this.type, output); + + ListBuffer16 buf = new ListBuffer16(); + for (int i = 0; i < this.urlAndHashList.size(); ++i) + { + URLAndHash urlAndHash = (URLAndHash)this.urlAndHashList.elementAt(i); + urlAndHash.encode(buf); + } + buf.encodeTo(output); + } + + /** + * Parse a {@link CertificateURL} from an {@link InputStream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link InputStream} to parse from. + * @return a {@link CertificateURL} object. + * @throws IOException + */ + public static CertificateURL parse(TlsContext context, InputStream input) + throws IOException + { + short type = TlsUtils.readUint8(input); + if (!CertChainType.isValid(type)) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + int totalLength = TlsUtils.readUint16(input); + if (totalLength < 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + byte[] urlAndHashListData = TlsUtils.readFully(totalLength, input); + + ByteArrayInputStream buf = new ByteArrayInputStream(urlAndHashListData); + + Vector url_and_hash_list = new Vector(); + while (buf.available() > 0) + { + URLAndHash url_and_hash = URLAndHash.parse(context, buf); + url_and_hash_list.addElement(url_and_hash); + } + + return new CertificateURL(type, url_and_hash_list); + } + + // TODO Could be more generally useful + class ListBuffer16 extends ByteArrayOutputStream + { + ListBuffer16() throws IOException + { + // Reserve space for length + TlsUtils.writeUint16(0, this); + } + + void encodeTo(OutputStream output) throws IOException + { + // Patch actual length back in + int length = count - 2; + TlsUtils.checkUint16(length); + TlsUtils.writeUint16(length, buf, 0); + output.write(buf, 0, count); + buf = null; + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ChangeCipherSpec.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ChangeCipherSpec.java new file mode 100644 index 0000000..a858ded --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ChangeCipherSpec.java @@ -0,0 +1,6 @@ +package org.bouncycastle.crypto.tls; + +public class ChangeCipherSpec +{ + public static final short change_cipher_spec = 1; +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java index 2979cde..c1e7533 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CipherSuite.java @@ -5,7 +5,6 @@ package org.bouncycastle.crypto.tls; */ public class CipherSuite { - public static final int TLS_NULL_WITH_NULL_NULL = 0x0000; public static final int TLS_RSA_WITH_NULL_MD5 = 0x0001; public static final int TLS_RSA_WITH_NULL_SHA = 0x0002; @@ -201,7 +200,98 @@ public class CipherSuite public static final int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032; /* + * RFC 5487 + */ + public static final int TLS_PSK_WITH_AES_128_GCM_SHA256 = 0x00A8; + public static final int TLS_PSK_WITH_AES_256_GCM_SHA384 = 0x00A9; + public static final int TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = 0x00AA; + public static final int TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = 0x00AB; + public static final int TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = 0x00AC; + public static final int TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = 0x00AD; + public static final int TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE; + public static final int TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF; + public static final int TLS_PSK_WITH_NULL_SHA256 = 0x00B0; + public static final int TLS_PSK_WITH_NULL_SHA384 = 0x00B1; + public static final int TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = 0x00B2; + public static final int TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = 0x00B3; + public static final int TLS_DHE_PSK_WITH_NULL_SHA256 = 0x00B4; + public static final int TLS_DHE_PSK_WITH_NULL_SHA384 = 0x00B5; + public static final int TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = 0x00B6; + public static final int TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = 0x00B7; + public static final int TLS_RSA_PSK_WITH_NULL_SHA256 = 0x00B8; + public static final int TLS_RSA_PSK_WITH_NULL_SHA384 = 0x00B9; + + /* + * RFC 5489 + */ + public static final int TLS_ECDHE_PSK_WITH_RC4_128_SHA = 0xC033; + public static final int TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = 0xC034; + public static final int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035; + public static final int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036; + public static final int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = 0xC037; + public static final int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = 0xC038; + public static final int TLS_ECDHE_PSK_WITH_NULL_SHA = 0xC039; + public static final int TLS_ECDHE_PSK_WITH_NULL_SHA256 = 0xC03A; + public static final int TLS_ECDHE_PSK_WITH_NULL_SHA384 = 0xC03B; + + /* * RFC 5746 */ public static final int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; + + /* + * RFC 6655 + */ + public static final int TLS_RSA_WITH_AES_128_CCM = 0xC09C; + public static final int TLS_RSA_WITH_AES_256_CCM = 0xC09D; + public static final int TLS_DHE_RSA_WITH_AES_128_CCM = 0xC09E; + public static final int TLS_DHE_RSA_WITH_AES_256_CCM = 0xC09F; + public static final int TLS_RSA_WITH_AES_128_CCM_8 = 0xC0A0; + public static final int TLS_RSA_WITH_AES_256_CCM_8 = 0xC0A1; + public static final int TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xC0A2; + public static final int TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xC0A3; + public static final int TLS_PSK_WITH_AES_128_CCM = 0xC0A4; + public static final int TLS_PSK_WITH_AES_256_CCM = 0xC0A5; + public static final int TLS_DHE_PSK_WITH_AES_128_CCM = 0xC0A6; + public static final int TLS_DHE_PSK_WITH_AES_256_CCM = 0xC0A7; + public static final int TLS_PSK_WITH_AES_128_CCM_8 = 0xC0A8; + public static final int TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9; + public static final int TLS_PSK_DHE_WITH_AES_128_CCM_8 = 0xC0AA; + public static final int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB; + + /* + * TBD[draft-josefsson-salsa20-tls-02] + */ + static final int TLS_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xFF00; + static final int TLS_RSA_WITH_SALSA20_SHA1 = 0xFF01; + static final int TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xFF02; + static final int TLS_DHE_RSA_WITH_SALSA20_SHA1 = 0xFF03; + static final int TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xFF04; + static final int TLS_ECDHE_RSA_WITH_SALSA20_SHA1 = 0xFF05; + static final int TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1 = 0xFF06; + static final int TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1 = 0xFF07; + static final int TLS_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xFF08; + static final int TLS_PSK_WITH_SALSA20_SHA1 = 0xFF09; + static final int TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xFF0A; + static final int TLS_DHE_PSK_WITH_SALSA20_SHA1 = 0xFF0B; + static final int TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xFF0C; + static final int TLS_RSA_PSK_WITH_SALSA20_SHA1 = 0xFF0D; + static final int TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xFF0E; + static final int TLS_ECDHE_PSK_WITH_SALSA20_SHA1 = 0xFF0F; + static final int TLS_RSA_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF10; + static final int TLS_RSA_WITH_SALSA20_UMAC96 = 0xFF11; + static final int TLS_DHE_RSA_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF12; + static final int TLS_DHE_RSA_WITH_SALSA20_UMAC96 = 0xFF13; + static final int TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF14; + static final int TLS_ECDHE_RSA_WITH_SALSA20_UMAC96 = 0xFF15; + static final int TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF16; + static final int TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96 = 0xFF17; + static final int TLS_PSK_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF18; + static final int TLS_PSK_WITH_SALSA20_UMAC96 = 0xFF19; + static final int TLS_DHE_PSK_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF1A; + static final int TLS_DHE_PSK_WITH_SALSA20_UMAC96 = 0xFF1B; + static final int TLS_RSA_PSK_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF1C; + static final int TLS_RSA_PSK_WITH_SALSA20_UMAC96 = 0xFF1D; + static final int TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_UMAC96 = 0xFF1E; + static final int TLS_ECDHE_PSK_WITH_SALSA20_UMAC96 = 0xFF1F; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java index 1a48491..43b73bf 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/CombinedHash.java @@ -8,7 +8,6 @@ import org.bouncycastle.crypto.Digest; class CombinedHash implements TlsHandshakeHash { - protected TlsContext context; protected Digest md5; protected Digest sha1; @@ -31,16 +30,35 @@ class CombinedHash this.context = context; } - public TlsHandshakeHash commit() + public TlsHandshakeHash notifyPRFDetermined() { return this; } - public TlsHandshakeHash fork() + public void trackHashAlgorithm(short hashAlgorithm) + { + throw new IllegalStateException("CombinedHash only supports calculating the legacy PRF for handshake hash"); + } + + public void sealHashAlgorithms() + { + } + + public TlsHandshakeHash stopTracking() { return new CombinedHash(this); } + public Digest forkPRFHash() + { + return new CombinedHash(this); + } + + public byte[] getFinalHash(short hashAlgorithm) + { + throw new IllegalStateException("CombinedHash doesn't support multiple hashes"); + } + /** * @see org.bouncycastle.crypto.Digest#getAlgorithmName() */ @@ -80,7 +98,7 @@ class CombinedHash */ public int doFinal(byte[] out, int outOff) { - if (context != null && context.getServerVersion().isSSL()) + if (context != null && TlsUtils.isSSL(context)) { ssl3Complete(md5, SSL3Mac.IPAD, SSL3Mac.OPAD, 48); ssl3Complete(sha1, SSL3Mac.IPAD, SSL3Mac.OPAD, 40); @@ -102,15 +120,15 @@ class CombinedHash protected void ssl3Complete(Digest d, byte[] ipad, byte[] opad, int padLength) { - byte[] secret = context.getSecurityParameters().masterSecret; + byte[] master_secret = context.getSecurityParameters().masterSecret; - d.update(secret, 0, secret.length); + d.update(master_secret, 0, master_secret.length); d.update(ipad, 0, padLength); byte[] tmp = new byte[d.getDigestSize()]; d.doFinal(tmp, 0); - d.update(secret, 0, secret.length); + d.update(master_secret, 0, master_secret.length); d.update(opad, 0, padLength); d.update(tmp, 0, tmp.length); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ContentType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ContentType.java index d814eac..65ed9b6 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ContentType.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ContentType.java @@ -9,4 +9,5 @@ public class ContentType public static final short alert = 21; public static final short handshake = 22; public static final short application_data = 23; + public static final short heartbeat = 24; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java index 8ccacfb..b3b1abe 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSClientProtocol.java @@ -13,7 +13,6 @@ import org.bouncycastle.util.Arrays; public class DTLSClientProtocol extends DTLSProtocol { - public DTLSClientProtocol(SecureRandom secureRandom) { super(secureRandom); @@ -22,7 +21,6 @@ public class DTLSClientProtocol public DTLSTransport connect(TlsClient client, DatagramTransport transport) throws IOException { - if (client == null) { throw new IllegalArgumentException("'client' cannot be null"); @@ -43,6 +41,17 @@ public class DTLSClientProtocol DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.clientContext, client, ContentType.handshake); + TlsSession sessionToResume = state.client.getSessionToResume(); + if (sessionToResume != null) + { + SessionParameters sessionParameters = sessionToResume.exportSessionParameters(); + if (sessionParameters != null) + { + state.tlsSession = sessionToResume; + state.sessionParameters = sessionParameters; + } + } + try { return clientHandshake(state, recordLayer); @@ -67,7 +76,6 @@ public class DTLSClientProtocol protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLayer recordLayer) throws IOException { - SecurityParameters securityParameters = state.clientContext.getSecurityParameters(); DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer); @@ -76,23 +84,23 @@ public class DTLSClientProtocol DTLSReliableHandshake.Message serverMessage = handshake.receiveMessage(); + while (serverMessage.getType() == HandshakeType.hello_verify_request) { - // NOTE: After receiving a record from the server, we discover the record layer version - ProtocolVersion server_version = recordLayer.getDiscoveredPeerVersion(); + ProtocolVersion recordLayerVersion = recordLayer.resetDiscoveredPeerVersion(); ProtocolVersion client_version = state.clientContext.getClientVersion(); - if (!server_version.isEqualOrEarlierVersionOf(client_version)) + /* + * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0 regardless of + * the version of TLS that is expected to be negotiated. DTLS 1.2 and 1.0 clients MUST use + * the version solely to indicate packet formatting (which is the same in both DTLS 1.2 and + * 1.0) and not as part of version negotiation. + */ + if (!recordLayerVersion.isEqualOrEarlierVersionOf(client_version)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - state.clientContext.setServerVersion(server_version); - state.client.notifyServerVersion(server_version); - } - - while (serverMessage.getType() == HandshakeType.hello_verify_request) - { - byte[] cookie = parseHelloVerifyRequest(state.clientContext, serverMessage.getBody()); + byte[] cookie = processHelloVerifyRequest(state, serverMessage.getBody()); byte[] patched = patchClientHelloWithCookie(clientHelloBody, cookie); handshake.resetHandshakeMessagesDigest(); @@ -103,16 +111,24 @@ public class DTLSClientProtocol if (serverMessage.getType() == HandshakeType.server_hello) { + reportServerVersion(state, recordLayer.getDiscoveredPeerVersion()); + processServerHello(state, serverMessage.getBody()); - serverMessage = handshake.receiveMessage(); } else { throw new TlsFatalAlert(AlertDescription.unexpected_message); } - securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite); + if (state.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + state.maxFragmentLength); + recordLayer.setPlaintextLimit(plainTextLimit); + } + + securityParameters.cipherSuite = state.selectedCipherSuite; securityParameters.compressionAlgorithm = state.selectedCompressionMethod; + securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.clientContext, state.selectedCipherSuite); /* * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has @@ -122,6 +138,48 @@ public class DTLSClientProtocol handshake.notifyHelloComplete(); + boolean resumedSession = state.selectedSessionID.length > 0 && state.tlsSession != null + && Arrays.areEqual(state.selectedSessionID, state.tlsSession.getSessionID()); + + if (resumedSession) + { + if (securityParameters.getCipherSuite() != state.sessionParameters.getCipherSuite() + || securityParameters.getCompressionAlgorithm() != state.sessionParameters.getCompressionAlgorithm()) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + securityParameters.masterSecret = Arrays.clone(state.sessionParameters.getMasterSecret()); + recordLayer.initPendingEpoch(state.client.getCipher()); + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null)); + processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedServerVerifyData); + + // NOTE: Calculated exclusive of the Finished message itself + byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null)); + handshake.sendMessage(HandshakeType.finished, clientVerifyData); + + handshake.finish(); + + state.clientContext.setResumableSession(state.tlsSession); + + state.client.notifyHandshakeComplete(); + + return new DTLSTransport(recordLayer); + } + + invalidateSession(state); + + if (state.selectedSessionID.length > 0) + { + state.tlsSession = new TlsSessionImpl(state.selectedSessionID, null); + } + + serverMessage = handshake.receiveMessage(); + if (serverMessage.getType() == HandshakeType.supplemental_data) { processServerSupplementalData(state, serverMessage.getBody()); @@ -135,9 +193,11 @@ public class DTLSClientProtocol state.keyExchange = state.client.getKeyExchange(); state.keyExchange.init(state.clientContext); + Certificate serverCertificate = null; + if (serverMessage.getType() == HandshakeType.certificate) { - processServerCertificate(state, serverMessage.getBody()); + serverCertificate = processServerCertificate(state, serverMessage.getBody()); serverMessage = handshake.receiveMessage(); } else @@ -146,6 +206,22 @@ public class DTLSClientProtocol state.keyExchange.skipServerCredentials(); } + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.isEmpty()) + { + state.allowCertificateStatus = false; + } + + if (serverMessage.getType() == HandshakeType.certificate_status) + { + processCertificateStatus(state, serverMessage.getBody()); + serverMessage = handshake.receiveMessage(); + } + else + { + // Okay, CertificateStatus is optional + } + if (serverMessage.getType() == HandshakeType.server_key_exchange) { processServerKeyExchange(state, serverMessage.getBody()); @@ -160,6 +236,14 @@ public class DTLSClientProtocol if (serverMessage.getType() == HandshakeType.certificate_request) { processCertificateRequest(state, serverMessage.getBody()); + + /* + * TODO Give the client a chance to immediately select the CertificateVerify hash + * algorithm here to avoid tracking the other hash algorithms unnecessarily? + */ + TlsUtils.trackHashAlgorithms(handshake.getHandshakeHash(), + state.certificateRequest.getSupportedSignatureAlgorithms()); + serverMessage = handshake.receiveMessage(); } else @@ -179,6 +263,8 @@ public class DTLSClientProtocol throw new TlsFatalAlert(AlertDescription.unexpected_message); } + handshake.getHandshakeHash().sealHashAlgorithms(); + Vector clientSupplementalData = state.client.getClientSupplementalData(); if (clientSupplementalData != null) { @@ -223,25 +309,45 @@ public class DTLSClientProtocol handshake.sendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody); TlsProtocol.establishMasterSecret(state.clientContext, state.keyExchange); + recordLayer.initPendingEpoch(state.client.getCipher()); + + TlsHandshakeHash prepareFinishHash = handshake.prepareToFinish(); - if (state.clientCredentials instanceof TlsSignerCredentials) + if (state.clientCredentials != null && state.clientCredentials instanceof TlsSignerCredentials) { + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials; + /* - * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended - * from TLS 1.2 + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 */ - TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials; - byte[] md5andsha1 = handshake.getCurrentHash(); - byte[] signature = signerCredentials.generateCertificateSignature(md5andsha1); - byte[] certificateVerifyBody = generateCertificateVerify(state, signature); + SignatureAndHashAlgorithm signatureAndHashAlgorithm; + byte[] hash; + + if (TlsUtils.isTLSv12(state.clientContext)) + { + signatureAndHashAlgorithm = signerCredentials.getSignatureAndHashAlgorithm(); + if (signatureAndHashAlgorithm == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + hash = prepareFinishHash.getFinalHash(signatureAndHashAlgorithm.getHash()); + } + else + { + signatureAndHashAlgorithm = null; + hash = TlsProtocol.getCurrentPRFHash(state.clientContext, prepareFinishHash, null); + } + + byte[] signature = signerCredentials.generateCertificateSignature(hash); + DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature); + byte[] certificateVerifyBody = generateCertificateVerify(state, certificateVerify); handshake.sendMessage(HandshakeType.certificate_verify, certificateVerifyBody); } - recordLayer.initPendingEpoch(state.client.getCipher()); - // NOTE: Calculated exclusive of the Finished message itself - byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "client finished", - handshake.getCurrentHash()); + byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null)); handshake.sendMessage(HandshakeType.finished, clientVerifyData); if (state.expectSessionTicket) @@ -258,39 +364,42 @@ public class DTLSClientProtocol } // NOTE: Calculated exclusive of the actual Finished message from the server - byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "server finished", - handshake.getCurrentHash()); - serverMessage = handshake.receiveMessage(); + byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null)); + processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedServerVerifyData); - if (serverMessage.getType() == HandshakeType.finished) - { - processFinished(serverMessage.getBody(), expectedServerVerifyData); - } - else + handshake.finish(); + + if (state.tlsSession != null) { - throw new TlsFatalAlert(AlertDescription.unexpected_message); - } + state.sessionParameters = new SessionParameters.Builder() + .setCipherSuite(securityParameters.cipherSuite) + .setCompressionAlgorithm(securityParameters.compressionAlgorithm) + .setMasterSecret(securityParameters.masterSecret) + .setPeerCertificate(serverCertificate) + .build(); - handshake.finish(); + state.tlsSession = TlsUtils.importSession(state.tlsSession.getSessionID(), state.sessionParameters); + + state.clientContext.setResumableSession(state.tlsSession); + } state.client.notifyHandshakeComplete(); return new DTLSTransport(recordLayer); } - protected byte[] generateCertificateVerify(ClientHandshakeState state, byte[] signature) + protected byte[] generateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeOpaque16(signature, buf); + certificateVerify.encode(buf); return buf.toByteArray(); } protected byte[] generateClientHello(ClientHandshakeState state, TlsClient client) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); ProtocolVersion client_version = client.getClientVersion(); @@ -304,8 +413,17 @@ public class DTLSClientProtocol buf.write(state.clientContext.getSecurityParameters().getClientRandom()); - // Session id - TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + // Session ID + byte[] session_id = TlsUtils.EMPTY_BYTES; + if (state.tlsSession != null) + { + session_id = state.tlsSession.getSessionID(); + if (session_id == null || session_id.length > 32) + { + session_id = TlsUtils.EMPTY_BYTES; + } + } + TlsUtils.writeOpaque8(session_id, buf); // Cookie TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); @@ -325,32 +443,26 @@ public class DTLSClientProtocol * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the * ClientHello. Including both is NOT RECOMMENDED. */ - boolean noRenegExt = state.clientExtensions == null - || state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo) == null; + byte[] renegExtData = TlsUtils.getExtensionData(state.clientExtensions, TlsProtocol.EXT_RenegotiationInfo); + boolean noRenegExt = (null == renegExtData); - int count = state.offeredCipherSuites.length; - if (noRenegExt) - { - // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV - ++count; - } + boolean noSCSV = !Arrays.contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); - TlsUtils.writeUint16(2 * count, buf); - TlsUtils.writeUint16Array(state.offeredCipherSuites, buf); - - if (noRenegExt) + if (noRenegExt && noSCSV) { - TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf); + // TODO Consider whether to default to a client extension instead + state.offeredCipherSuites = Arrays.append(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); } + + TlsUtils.writeUint16ArrayWithUint16Length(state.offeredCipherSuites, buf); } // TODO Add support for compression // Compression methods // state.offeredCompressionMethods = client.getCompressionMethods(); - state.offeredCompressionMethods = new short[]{CompressionMethod._null}; + state.offeredCompressionMethods = new short[]{ CompressionMethod._null }; - TlsUtils.writeUint8((short)state.offeredCompressionMethods.length, buf); - TlsUtils.writeUint8Array(state.offeredCompressionMethods, buf); + TlsUtils.writeUint8ArrayWithUint8Length(state.offeredCompressionMethods, buf); // Extensions if (state.clientExtensions != null) @@ -364,16 +476,29 @@ public class DTLSClientProtocol protected byte[] generateClientKeyExchange(ClientHandshakeState state) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); state.keyExchange.generateClientKeyExchange(buf); return buf.toByteArray(); } + protected void invalidateSession(ClientHandshakeState state) + { + if (state.sessionParameters != null) + { + state.sessionParameters.clear(); + state.sessionParameters = null; + } + + if (state.tlsSession != null) + { + state.tlsSession.invalidate(); + state.tlsSession = null; + } + } + protected void processCertificateRequest(ClientHandshakeState state, byte[] body) throws IOException { - if (state.authentication == null) { /* @@ -385,19 +510,69 @@ public class DTLSClientProtocol ByteArrayInputStream buf = new ByteArrayInputStream(body); - state.certificateRequest = CertificateRequest.parse(buf); + state.certificateRequest = CertificateRequest.parse(state.clientContext, buf); TlsProtocol.assertEmpty(buf); state.keyExchange.validateCertificateRequest(state.certificateRequest); } - protected void processNewSessionTicket(ClientHandshakeState state, byte[] body) + protected void processCertificateStatus(ClientHandshakeState state, byte[] body) throws IOException { + if (!state.allowCertificateStatus) + { + /* + * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the + * server MUST have included an extension of type "status_request" with empty + * "extension_data" in the extended server hello.. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } ByteArrayInputStream buf = new ByteArrayInputStream(body); + state.certificateStatus = CertificateStatus.parse(buf); + + TlsProtocol.assertEmpty(buf); + + // TODO[RFC 3546] Figure out how to provide this to the client/authentication. + } + + protected byte[] processHelloVerifyRequest(ClientHandshakeState state, byte[] body) + throws IOException + { + ByteArrayInputStream buf = new ByteArrayInputStream(body); + + ProtocolVersion server_version = TlsUtils.readVersion(buf); + byte[] cookie = TlsUtils.readOpaque8(buf); + + TlsProtocol.assertEmpty(buf); + + // TODO Seems this behaviour is not yet in line with OpenSSL for DTLS 1.2 +// reportServerVersion(state, server_version); + if (!server_version.isEqualOrEarlierVersionOf(state.clientContext.getClientVersion())) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + /* + * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater + * future flexibility. The limit remains 32 for previous versions of DTLS. + */ + if (!ProtocolVersion.DTLSv12.isEqualOrEarlierVersionOf(server_version) && cookie.length > 32) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return cookie; + } + + protected void processNewSessionTicket(ClientHandshakeState state, byte[] body) + throws IOException + { + ByteArrayInputStream buf = new ByteArrayInputStream(body); + NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf); TlsProtocol.assertEmpty(buf); @@ -405,10 +580,9 @@ public class DTLSClientProtocol state.client.notifyNewSessionTicket(newSessionTicket); } - protected void processServerCertificate(ClientHandshakeState state, byte[] body) + protected Certificate processServerCertificate(ClientHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); Certificate serverCertificate = Certificate.parse(buf); @@ -418,34 +592,31 @@ public class DTLSClientProtocol state.keyExchange.processServerCertificate(serverCertificate); state.authentication = state.client.getAuthentication(); state.authentication.notifyServerCertificate(serverCertificate); + + return serverCertificate; } protected void processServerHello(ClientHandshakeState state, byte[] body) throws IOException { - SecurityParameters securityParameters = state.clientContext.getSecurityParameters(); ByteArrayInputStream buf = new ByteArrayInputStream(body); - // TODO Read RFCs for guidance on the expected record layer version number ProtocolVersion server_version = TlsUtils.readVersion(buf); - if (!server_version.equals(state.clientContext.getServerVersion())) - { - throw new TlsFatalAlert(AlertDescription.illegal_parameter); - } + reportServerVersion(state, server_version); securityParameters.serverRandom = TlsUtils.readFully(32, buf); - byte[] sessionID = TlsUtils.readOpaque8(buf); - if (sessionID.length > 32) + state.selectedSessionID = TlsUtils.readOpaque8(buf); + if (state.selectedSessionID.length > 32) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - state.client.notifySessionID(sessionID); + state.client.notifySessionID(state.selectedSessionID); state.selectedCipherSuite = TlsUtils.readUint16(buf); - if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite) + if (!Arrays.contains(state.offeredCipherSuites, state.selectedCipherSuite) || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) { @@ -457,7 +628,7 @@ public class DTLSClientProtocol state.client.notifySelectedCipherSuite(state.selectedCipherSuite); state.selectedCompressionMethod = TlsUtils.readUint8(buf); - if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod)) + if (!Arrays.contains(state.offeredCompressionMethods, state.selectedCompressionMethod)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } @@ -502,7 +673,7 @@ public class DTLSClientProtocol * MUST continue to comply with Section 7.4.1.4 for all other extensions. */ if (!extType.equals(TlsProtocol.EXT_RenegotiationInfo) - && (state.clientExtensions == null || state.clientExtensions.get(extType) == null)) + && null == TlsUtils.getExtensionData(state.clientExtensions, extType)) { /* * RFC 3546 2.3 Note that for all extension types (including those defined in @@ -524,8 +695,8 @@ public class DTLSClientProtocol * When a ServerHello is received, the client MUST check if it includes the * "renegotiation_info" extension: */ - byte[] renegExtValue = (byte[])serverExtensions.get(TlsProtocol.EXT_RenegotiationInfo); - if (renegExtValue != null) + byte[] renegExtData = (byte[])serverExtensions.get(TlsProtocol.EXT_RenegotiationInfo); + if (renegExtData != null) { /* * If the extension is present, set the secure_renegotiation flag to TRUE. The @@ -535,7 +706,7 @@ public class DTLSClientProtocol */ state.secure_renegotiation = true; - if (!Arrays.constantTimeAreEqual(renegExtValue, + if (!Arrays.constantTimeAreEqual(renegExtData, TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) { throw new TlsFatalAlert(AlertDescription.handshake_failure); @@ -543,7 +714,16 @@ public class DTLSClientProtocol } } - state.expectSessionTicket = serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket); + state.maxFragmentLength = evaluateMaxFragmentLengthExtension(state.clientExtensions, serverExtensions, + AlertDescription.illegal_parameter); + + securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(serverExtensions); + + state.allowCertificateStatus = TlsUtils.hasExpectedEmptyExtensionData(serverExtensions, + TlsExtensionsUtils.EXT_status_request, AlertDescription.illegal_parameter); + + state.expectSessionTicket = TlsUtils.hasExpectedEmptyExtensionData(serverExtensions, + TlsProtocol.EXT_SessionTicket, AlertDescription.illegal_parameter); } state.client.notifySecureRenegotiation(state.secure_renegotiation); @@ -557,7 +737,6 @@ public class DTLSClientProtocol protected void processServerKeyExchange(ClientHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); state.keyExchange.processServerKeyExchange(buf); @@ -568,37 +747,30 @@ public class DTLSClientProtocol protected void processServerSupplementalData(ClientHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); Vector serverSupplementalData = TlsProtocol.readSupplementalDataMessage(buf); state.client.processServerSupplementalData(serverSupplementalData); } - protected static byte[] parseHelloVerifyRequest(TlsContext context, byte[] body) + protected void reportServerVersion(ClientHandshakeState state, ProtocolVersion server_version) throws IOException { - - ByteArrayInputStream buf = new ByteArrayInputStream(body); - - ProtocolVersion server_version = TlsUtils.readVersion(buf); - if (!server_version.equals(context.getServerVersion())) + TlsClientContextImpl clientContext = state.clientContext; + ProtocolVersion currentServerVersion = clientContext.getServerVersion(); + if (null == currentServerVersion) + { + clientContext.setServerVersion(server_version); + state.client.notifyServerVersion(server_version); + } + else if (!currentServerVersion.equals(server_version)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - - byte[] cookie = TlsUtils.readOpaque8(buf); - - // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 - - TlsProtocol.assertEmpty(buf); - - return cookie; } protected static byte[] patchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie) throws IOException { - int sessionIDPos = 34; int sessionIDLength = TlsUtils.readUint8(clientHelloBody, sessionIDPos); @@ -607,7 +779,8 @@ public class DTLSClientProtocol byte[] patched = new byte[clientHelloBody.length + cookie.length]; System.arraycopy(clientHelloBody, 0, patched, 0, cookieLengthPos); - TlsUtils.writeUint8((short)cookie.length, patched, cookieLengthPos); + TlsUtils.checkUint8(cookie.length); + TlsUtils.writeUint8(cookie.length, patched, cookieLengthPos); System.arraycopy(cookie, 0, patched, cookiePos, cookie.length); System.arraycopy(clientHelloBody, cookiePos, patched, cookiePos + cookie.length, clientHelloBody.length - cookiePos); @@ -619,15 +792,22 @@ public class DTLSClientProtocol { TlsClient client = null; TlsClientContextImpl clientContext = null; + TlsSession tlsSession = null; + SessionParameters sessionParameters = null; + SessionParameters.Builder sessionParametersBuilder = null; int[] offeredCipherSuites = null; short[] offeredCompressionMethods = null; Hashtable clientExtensions = null; + byte[] selectedSessionID = null; int selectedCipherSuite = -1; short selectedCompressionMethod = -1; boolean secure_renegotiation = false; + short maxFragmentLength = -1; + boolean allowCertificateStatus = false; boolean expectSessionTicket = false; TlsKeyExchange keyExchange = null; TlsAuthentication authentication = null; + CertificateStatus certificateStatus = null; CertificateRequest certificateRequest = null; TlsCredentials clientCredentials = null; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java index 2789b22..e27580c 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSProtocol.java @@ -4,18 +4,17 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.SecureRandom; +import java.util.Hashtable; import java.util.Vector; import org.bouncycastle.util.Arrays; public abstract class DTLSProtocol { - protected final SecureRandom secureRandom; protected DTLSProtocol(SecureRandom secureRandom) { - if (secureRandom == null) { throw new IllegalArgumentException("'secureRandom' cannot be null"); @@ -27,7 +26,6 @@ public abstract class DTLSProtocol protected void processFinished(byte[] body, byte[] expected_verify_data) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf); @@ -40,10 +38,20 @@ public abstract class DTLSProtocol } } + protected static short evaluateMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions, + short alertDescription) throws IOException + { + short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions); + if (maxFragmentLength >= 0 && maxFragmentLength != TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions)) + { + throw new TlsFatalAlert(alertDescription); + } + return maxFragmentLength; + } + protected static byte[] generateCertificate(Certificate certificate) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); certificate.encode(buf); return buf.toByteArray(); @@ -52,7 +60,6 @@ public abstract class DTLSProtocol protected static byte[] generateSupplementalData(Vector supplementalData) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); TlsProtocol.writeSupplementalData(buf, supplementalData); return buf.toByteArray(); @@ -61,7 +68,6 @@ public abstract class DTLSProtocol protected static void validateSelectedCipherSuite(int selectedCipherSuite, short alertDescription) throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5: diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java index 3fde01a..cba13d5 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSRecordLayer.java @@ -5,7 +5,6 @@ import java.io.IOException; class DTLSRecordLayer implements DatagramTransport { - private static final int RECORD_HEADER_LENGTH = 13; private static final int MAX_FRAGMENT_LENGTH = 1 << 14; private static final long TCP_MSL = 1000L * 60 * 2; @@ -21,6 +20,7 @@ class DTLSRecordLayer private volatile boolean failed = false; private volatile ProtocolVersion discoveredPeerVersion = null; private volatile boolean inHandshake; + private volatile int plaintextLimit; private DTLSEpoch currentEpoch, pendingEpoch; private DTLSEpoch readEpoch, writeEpoch; @@ -40,6 +40,13 @@ class DTLSRecordLayer this.pendingEpoch = null; this.readEpoch = currentEpoch; this.writeEpoch = currentEpoch; + + setPlaintextLimit(MAX_FRAGMENT_LENGTH); + } + + void setPlaintextLimit(int plaintextLimit) + { + this.plaintextLimit = plaintextLimit; } ProtocolVersion getDiscoveredPeerVersion() @@ -47,6 +54,13 @@ class DTLSRecordLayer return discoveredPeerVersion; } + ProtocolVersion resetDiscoveredPeerVersion() + { + ProtocolVersion result = discoveredPeerVersion; + discoveredPeerVersion = null; + return result; + } + void initPendingEpoch(TlsCipher pendingCipher) { if (pendingEpoch != null) @@ -99,26 +113,24 @@ class DTLSRecordLayer public int getReceiveLimit() throws IOException { - return Math.min(MAX_FRAGMENT_LENGTH, + return Math.min(this.plaintextLimit, readEpoch.getCipher().getPlaintextLimit(transport.getReceiveLimit() - RECORD_HEADER_LENGTH)); } public int getSendLimit() throws IOException { - return Math.min(MAX_FRAGMENT_LENGTH, + return Math.min(this.plaintextLimit, writeEpoch.getCipher().getPlaintextLimit(transport.getSendLimit() - RECORD_HEADER_LENGTH)); } public int receive(byte[] buf, int off, int len, int waitMillis) throws IOException { - byte[] record = null; - for (; ; ) + for (;;) { - int receiveLimit = Math.min(len, getReceiveLimit()) + RECORD_HEADER_LENGTH; if (record == null || record.length < receiveLimit) { @@ -157,6 +169,7 @@ class DTLSRecordLayer case ContentType.application_data: case ContentType.change_cipher_spec: case ContentType.handshake: + case ContentType.heartbeat: break; default: // TODO Exception? @@ -199,6 +212,11 @@ class DTLSRecordLayer recordEpoch.getReplayWindow().reportAuthenticated(seq); + if (plaintext.length > this.plaintextLimit) + { + continue; + } + if (discoveredPeerVersion == null) { discoveredPeerVersion = version; @@ -208,7 +226,6 @@ class DTLSRecordLayer { case ContentType.alert: { - if (plaintext.length == 2) { short alertLevel = plaintext[0]; @@ -249,14 +266,18 @@ class DTLSRecordLayer { // Implicitly receive change_cipher_spec and change to pending cipher state - if (plaintext.length != 1 || plaintext[0] != 1) + for (int i = 0; i < plaintext.length; ++i) { - continue; - } + short message = TlsUtils.readUint8(plaintext, i); + if (message != ChangeCipherSpec.change_cipher_spec) + { + continue; + } - if (pendingEpoch != null) - { - readEpoch = pendingEpoch; + if (pendingEpoch != null) + { + readEpoch = pendingEpoch; + } } continue; @@ -273,6 +294,12 @@ class DTLSRecordLayer // TODO Consider support for HelloRequest continue; } + break; + } + case ContentType.heartbeat: + { + // TODO[RFC 6520] + continue; } } @@ -300,18 +327,15 @@ class DTLSRecordLayer public void send(byte[] buf, int off, int len) throws IOException { - short contentType = ContentType.application_data; if (this.inHandshake || this.writeEpoch == this.retransmitEpoch) { - contentType = ContentType.handshake; short handshakeType = TlsUtils.readUint8(buf, off); if (handshakeType == HandshakeType.finished) { - DTLSEpoch nextEpoch = null; if (this.inHandshake) { @@ -331,7 +355,7 @@ class DTLSRecordLayer // Implicitly send change_cipher_spec and change to pending cipher state // TODO Send change_cipher_spec and finished records in single datagram? - byte[] data = new byte[]{1}; + byte[] data = new byte[]{ 1 }; sendRecord(ContentType.change_cipher_spec, data, 0, data.length); writeEpoch = nextEpoch; @@ -410,7 +434,6 @@ class DTLSRecordLayer private void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause) throws IOException { - peer.notifyAlertRaised(alertLevel, alertDescription, message, cause); byte[] error = new byte[2]; @@ -434,8 +457,7 @@ class DTLSRecordLayer } int received = Math.min(recordQueue.size(), RECORD_HEADER_LENGTH + length); - recordQueue.read(buf, off, received, 0); - recordQueue.removeData(received); + recordQueue.removeData(buf, off, received, 0); return received; } @@ -457,6 +479,10 @@ class DTLSRecordLayer private void sendRecord(short contentType, byte[] buf, int off, int len) throws IOException { + if (len > this.plaintextLimit) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } /* * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, @@ -473,10 +499,7 @@ class DTLSRecordLayer byte[] ciphertext = writeEpoch.getCipher().encodePlaintext( getMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len); - if (ciphertext.length > MAX_FRAGMENT_LENGTH) - { - throw new TlsFatalAlert(AlertDescription.internal_error); - } + // TODO Check the ciphertext length? byte[] record = new byte[ciphertext.length + RECORD_HEADER_LENGTH]; TlsUtils.writeUint8(contentType, record, 0); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java index 3819251..84ccfcb 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java @@ -10,12 +10,11 @@ import org.bouncycastle.util.Integers; class DTLSReliableHandshake { - private final static int MAX_RECEIVE_AHEAD = 10; private final DTLSRecordLayer recordLayer; - private TlsHandshakeHash hash = new DeferredHash(); + private TlsHandshakeHash handshakeHash; private Hashtable currentInboundFlight = new Hashtable(); private Hashtable previousInboundFlight = null; @@ -27,25 +26,31 @@ class DTLSReliableHandshake DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport) { this.recordLayer = transport; - this.hash.init(context); + this.handshakeHash = new DeferredHash(); + this.handshakeHash.init(context); } void notifyHelloComplete() { - this.hash = this.hash.commit(); + this.handshakeHash = handshakeHash.notifyPRFDetermined(); + } + + TlsHandshakeHash getHandshakeHash() + { + return handshakeHash; } - byte[] getCurrentHash() + TlsHandshakeHash prepareToFinish() { - TlsHandshakeHash copyOfHash = hash.fork(); - byte[] result = new byte[copyOfHash.getDigestSize()]; - copyOfHash.doFinal(result, 0); + TlsHandshakeHash result = handshakeHash; + this.handshakeHash = handshakeHash.stopTracking(); return result; } void sendMessage(short msg_type, byte[] body) throws IOException { + TlsUtils.checkUint24(body.length); if (!sending) { @@ -62,10 +67,21 @@ class DTLSReliableHandshake updateHandshakeMessagesDigest(message); } - Message receiveMessage() + byte[] receiveMessageBody(short msg_type) throws IOException { + Message message = receiveMessage(); + if (message.getType() != msg_type) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + return message.getBody(); + } + + Message receiveMessage() + throws IOException + { if (sending) { sending = false; @@ -93,7 +109,6 @@ class DTLSReliableHandshake for (; ; ) { - int receiveLimit = recordLayer.getReceiveLimit(); if (buf == null || buf.length < receiveLimit) { @@ -280,7 +295,7 @@ class DTLSReliableHandshake void resetHandshakeMessagesDigest() { - hash.reset(); + handshakeHash.reset(); } /** @@ -328,8 +343,8 @@ class DTLSReliableHandshake TlsUtils.writeUint16(message.getSeq(), buf, 4); TlsUtils.writeUint24(0, buf, 6); TlsUtils.writeUint24(body.length, buf, 9); - hash.update(buf, 0, buf.length); - hash.update(body, 0, body.length); + handshakeHash.update(buf, 0, buf.length); + handshakeHash.update(body, 0, body.length); } return message; } @@ -337,7 +352,6 @@ class DTLSReliableHandshake private void writeMessage(Message message) throws IOException { - int sendLimit = recordLayer.getSendLimit(); int fragmentLimit = sendLimit - 12; @@ -364,18 +378,15 @@ class DTLSReliableHandshake private void writeHandshakeFragment(Message message, int fragment_offset, int fragment_length) throws IOException { - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(message.getType(), buf); - TlsUtils.writeUint24(message.getBody().length, buf); - TlsUtils.writeUint16(message.getSeq(), buf); - TlsUtils.writeUint24(fragment_offset, buf); - TlsUtils.writeUint24(fragment_length, buf); - buf.write(message.getBody(), fragment_offset, fragment_length); - - byte[] fragment = buf.toByteArray(); - - recordLayer.send(fragment, 0, fragment.length); + RecordLayerBuffer fragment = new RecordLayerBuffer(12 + fragment_length); + TlsUtils.writeUint8(message.getType(), fragment); + TlsUtils.writeUint24(message.getBody().length, fragment); + TlsUtils.writeUint16(message.getSeq(), fragment); + TlsUtils.writeUint24(fragment_offset, fragment); + TlsUtils.writeUint24(fragment_length, fragment); + fragment.write(message.getBody(), fragment_offset, fragment_length); + + fragment.sendToRecordLayer(recordLayer); } private static boolean checkAll(Hashtable inboundFlight) @@ -402,7 +413,6 @@ class DTLSReliableHandshake static class Message { - private final int message_seq; private final short msg_type; private final byte[] body; @@ -429,4 +439,18 @@ class DTLSReliableHandshake return body; } } + + static class RecordLayerBuffer extends ByteArrayOutputStream + { + RecordLayerBuffer(int size) + { + super(size); + } + + void sendToRecordLayer(DTLSRecordLayer recordLayer) throws IOException + { + recordLayer.send(buf, 0, count); + buf = null; + } + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java index 3a100d1..fbb3336 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DTLSServerProtocol.java @@ -15,7 +15,6 @@ import org.bouncycastle.util.Arrays; public class DTLSServerProtocol extends DTLSProtocol { - protected boolean verifyRequests = true; public DTLSServerProtocol(SecureRandom secureRandom) @@ -36,7 +35,6 @@ public class DTLSServerProtocol public DTLSTransport accept(TlsServer server, DatagramTransport transport) throws IOException { - if (server == null) { throw new IllegalArgumentException("'server' cannot be null"); @@ -80,10 +78,9 @@ public class DTLSServerProtocol } } - public DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLayer recordLayer) + protected DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLayer recordLayer) throws IOException { - SecurityParameters securityParameters = state.serverContext.getSecurityParameters(); DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.serverContext, recordLayer); @@ -105,23 +102,31 @@ public class DTLSServerProtocol throw new TlsFatalAlert(AlertDescription.unexpected_message); } - byte[] serverHelloBody = generateServerHello(state); - handshake.sendMessage(HandshakeType.server_hello, serverHelloBody); - - // TODO This block could really be done before actually sending the hello { - securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite); + byte[] serverHelloBody = generateServerHello(state); + + if (state.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + state.maxFragmentLength); + recordLayer.setPlaintextLimit(plainTextLimit); + } + + securityParameters.cipherSuite = state.selectedCipherSuite; securityParameters.compressionAlgorithm = state.selectedCompressionMethod; - + securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.serverContext, + state.selectedCipherSuite); + /* * 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. */ securityParameters.verifyDataLength = 12; - - handshake.notifyHelloComplete(); + + handshake.sendMessage(HandshakeType.server_hello, serverHelloBody); } + handshake.notifyHelloComplete(); + Vector serverSupplementalData = state.server.getServerSupplementalData(); if (serverSupplementalData != null) { @@ -133,6 +138,9 @@ public class DTLSServerProtocol state.keyExchange.init(state.serverContext); state.serverCredentials = state.server.getCredentials(); + + Certificate serverCertificate = null; + if (state.serverCredentials == null) { state.keyExchange.skipServerCredentials(); @@ -141,10 +149,27 @@ public class DTLSServerProtocol { state.keyExchange.processServerCredentials(state.serverCredentials); - byte[] certificateBody = generateCertificate(state.serverCredentials.getCertificate()); + serverCertificate = state.serverCredentials.getCertificate(); + byte[] certificateBody = generateCertificate(serverCertificate); handshake.sendMessage(HandshakeType.certificate, certificateBody); } + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.isEmpty()) + { + state.allowCertificateStatus = false; + } + + if (state.allowCertificateStatus) + { + CertificateStatus certificateStatus = state.server.getCertificateStatus(); + if (certificateStatus != null) + { + byte[] certificateStatusBody = generateCertificateStatus(state, certificateStatus); + handshake.sendMessage(HandshakeType.certificate_status, certificateStatusBody); + } + } + byte[] serverKeyExchange = state.keyExchange.generateServerKeyExchange(); if (serverKeyExchange != null) { @@ -160,11 +185,16 @@ public class DTLSServerProtocol byte[] certificateRequestBody = generateCertificateRequest(state, state.certificateRequest); handshake.sendMessage(HandshakeType.certificate_request, certificateRequestBody); + + TlsUtils.trackHashAlgorithms(handshake.getHandshakeHash(), + state.certificateRequest.getSupportedSignatureAlgorithms()); } } handshake.sendMessage(HandshakeType.server_hello_done, TlsUtils.EMPTY_BYTES); + handshake.getHandshakeHash().sealHashAlgorithms(); + clientMessage = handshake.receiveMessage(); if (clientMessage.getType() == HandshakeType.supplemental_data) @@ -190,9 +220,7 @@ public class DTLSServerProtocol } else { - ProtocolVersion equivalentTLSVersion = state.serverContext.getServerVersion().getEquivalentTLSVersion(); - - if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion)) + if (TlsUtils.isTLSv12(state.serverContext)) { /* * RFC 5246 If no suitable certificate is available, the client MUST send a @@ -216,8 +244,11 @@ public class DTLSServerProtocol throw new TlsFatalAlert(AlertDescription.unexpected_message); } + TlsProtocol.establishMasterSecret(state.serverContext, state.keyExchange); recordLayer.initPendingEpoch(state.server.getCipher()); + TlsHandshakeHash prepareFinishHash = handshake.prepareToFinish(); + /* * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing * capability (i.e., all certificates except those containing fixed Diffie-Hellman @@ -225,33 +256,14 @@ public class DTLSServerProtocol */ if (expectCertificateVerifyMessage(state)) { - byte[] certificateVerifyHash = handshake.getCurrentHash(); - clientMessage = handshake.receiveMessage(); - - if (clientMessage.getType() == HandshakeType.certificate_verify) - { - processCertificateVerify(state, clientMessage.getBody(), certificateVerifyHash); - } - else - { - throw new TlsFatalAlert(AlertDescription.unexpected_message); - } + byte[] certificateVerifyBody = handshake.receiveMessageBody(HandshakeType.certificate_verify); + processCertificateVerify(state, certificateVerifyBody, prepareFinishHash); } // NOTE: Calculated exclusive of the actual Finished message from the client - byte[] clientFinishedHash = handshake.getCurrentHash(); - clientMessage = handshake.receiveMessage(); - - if (clientMessage.getType() == HandshakeType.finished) - { - byte[] expectedClientVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "client finished", - clientFinishedHash); - processFinished(clientMessage.getBody(), expectedClientVerifyData); - } - else - { - throw new TlsFatalAlert(AlertDescription.unexpected_message); - } + byte[] expectedClientVerifyData = TlsUtils.calculateVerifyData(state.serverContext, ExporterLabel.client_finished, + TlsProtocol.getCurrentPRFHash(state.serverContext, handshake.getHandshakeHash(), null)); + processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedClientVerifyData); if (state.expectSessionTicket) { @@ -261,8 +273,8 @@ public class DTLSServerProtocol } // NOTE: Calculated exclusive of the Finished message itself - byte[] serverVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "server finished", - handshake.getCurrentHash()); + byte[] serverVerifyData = TlsUtils.calculateVerifyData(state.serverContext, ExporterLabel.server_finished, + TlsProtocol.getCurrentPRFHash(state.serverContext, handshake.getHandshakeHash(), null)); handshake.sendMessage(HandshakeType.finished, serverVerifyData); handshake.finish(); @@ -275,16 +287,22 @@ public class DTLSServerProtocol protected byte[] generateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); certificateRequest.encode(buf); return buf.toByteArray(); } - protected byte[] generateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket) + protected byte[] generateCertificateStatus(ServerHandshakeState state, CertificateStatus certificateStatus) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + certificateStatus.encode(buf); + return buf.toByteArray(); + } + protected byte[] generateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket) + throws IOException + { ByteArrayOutputStream buf = new ByteArrayOutputStream(); newSessionTicket.encode(buf); return buf.toByteArray(); @@ -293,6 +311,7 @@ public class DTLSServerProtocol protected byte[] generateServerHello(ServerHandshakeState state) throws IOException { + SecurityParameters securityParameters = state.serverContext.getSecurityParameters(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); @@ -310,7 +329,7 @@ public class DTLSServerProtocol TlsUtils.writeVersion(state.serverContext.getServerVersion(), buf); - buf.write(state.serverContext.getSecurityParameters().serverRandom); + buf.write(securityParameters.getServerRandom()); /* * The server may return an empty session_id to indicate that the session will not be cached @@ -319,7 +338,7 @@ public class DTLSServerProtocol TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); state.selectedCipherSuite = state.server.getSelectedCipherSuite(); - if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite) + if (!Arrays.contains(state.offeredCipherSuites, state.selectedCipherSuite) || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) { @@ -329,7 +348,7 @@ public class DTLSServerProtocol validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error); state.selectedCompressionMethod = state.server.getSelectedCompressionMethod(); - if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod)) + if (!Arrays.contains(state.offeredCompressionMethods, state.selectedCompressionMethod)) { throw new TlsFatalAlert(AlertDescription.internal_error); } @@ -344,9 +363,8 @@ public class DTLSServerProtocol */ if (state.secure_renegotiation) { - - boolean noRenegExt = state.serverExtensions == null - || !state.serverExtensions.containsKey(TlsProtocol.EXT_RenegotiationInfo); + byte[] renegExtData = TlsUtils.getExtensionData(state.serverExtensions, TlsProtocol.EXT_RenegotiationInfo); + boolean noRenegExt = (null == renegExtData); if (noRenegExt) { @@ -357,15 +375,12 @@ public class DTLSServerProtocol * because the client is signaling its willingness to receive the extension via the * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. */ - if (state.serverExtensions == null) - { - state.serverExtensions = new Hashtable(); - } /* * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty * "renegotiation_info" extension in the ServerHello message. */ + state.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(state.serverExtensions); state.serverExtensions.put(TlsProtocol.EXT_RenegotiationInfo, TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)); } @@ -373,7 +388,17 @@ public class DTLSServerProtocol if (state.serverExtensions != null) { - state.expectSessionTicket = state.serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket); + state.maxFragmentLength = evaluateMaxFragmentLengthExtension(state.clientExtensions, state.serverExtensions, + AlertDescription.internal_error); + + securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(state.serverExtensions); + + state.allowCertificateStatus = TlsUtils.hasExpectedEmptyExtensionData(state.serverExtensions, + TlsExtensionsUtils.EXT_status_request, AlertDescription.internal_error); + + state.expectSessionTicket = TlsUtils.hasExpectedEmptyExtensionData(state.serverExtensions, + TlsProtocol.EXT_SessionTicket, AlertDescription.internal_error); + TlsProtocol.writeExtensions(buf, state.serverExtensions); } @@ -383,7 +408,6 @@ public class DTLSServerProtocol protected void notifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate) throws IOException { - if (state.certificateRequest == null) { throw new IllegalStateException(); @@ -429,7 +453,6 @@ public class DTLSServerProtocol protected void processClientCertificate(ServerHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); Certificate clientCertificate = Certificate.parse(buf); @@ -439,27 +462,29 @@ public class DTLSServerProtocol notifyClientCertificate(state, clientCertificate); } - protected void processCertificateVerify(ServerHandshakeState state, byte[] body, byte[] certificateVerifyHash) + protected void processCertificateVerify(ServerHandshakeState state, byte[] body, TlsHandshakeHash prepareFinishHash) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); - byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf); + DigitallySigned clientCertificateVerify = DigitallySigned.parse(state.serverContext, buf); TlsProtocol.assertEmpty(buf); // Verify the CertificateVerify message contains a correct signature. try { - TlsSigner tlsSigner = TlsUtils.createTlsSigner(state.clientCertificateType); - tlsSigner.init(state.serverContext); + // TODO For TLS 1.2, this needs to be the hash specified in the DigitallySigned + byte[] certificateVerifyHash = TlsProtocol.getCurrentPRFHash(state.serverContext, prepareFinishHash, null); org.bouncycastle.asn1.x509.Certificate x509Cert = state.clientCertificate.getCertificateAt(0); SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo); - tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, certificateVerifyHash); + TlsSigner tlsSigner = TlsUtils.createTlsSigner(state.clientCertificateType); + tlsSigner.init(state.serverContext); + tlsSigner.verifyRawSignature(clientCertificateVerify.getAlgorithm(), + clientCertificateVerify.getSignature(), publicKey, certificateVerifyHash); } catch (Exception e) { @@ -470,7 +495,6 @@ public class DTLSServerProtocol protected void processClientHello(ServerHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); // TODO Read RFCs for guidance on the expected record layer version number @@ -545,7 +569,7 @@ public class DTLSServerProtocol * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag * to TRUE. */ - if (TlsProtocol.arrayContains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + if (Arrays.contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) { state.secure_renegotiation = true; } @@ -554,23 +578,19 @@ public class DTLSServerProtocol * The server MUST check if the "renegotiation_info" extension is included in the * ClientHello. */ - if (state.clientExtensions != null) + byte[] renegExtData = TlsUtils.getExtensionData(state.clientExtensions, TlsProtocol.EXT_RenegotiationInfo); + if (renegExtData != null) { - byte[] renegExtValue = (byte[])state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo); - if (renegExtValue != null) - { - /* - * If the extension is present, set secure_renegotiation flag to TRUE. The - * server MUST then verify that the length of the "renegotiated_connection" - * field is zero, and if it is not, MUST abort the handshake. - */ - state.secure_renegotiation = true; + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + state.secure_renegotiation = true; - if (!Arrays.constantTimeAreEqual(renegExtValue, - TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) - { - throw new TlsFatalAlert(AlertDescription.handshake_failure); - } + if (!Arrays.constantTimeAreEqual(renegExtData, TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); } } } @@ -586,20 +606,16 @@ public class DTLSServerProtocol protected void processClientKeyExchange(ServerHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); state.keyExchange.processClientKeyExchange(buf); TlsProtocol.assertEmpty(buf); - - TlsProtocol.establishMasterSecret(state.serverContext, state.keyExchange); } protected void processClientSupplementalData(ServerHandshakeState state, byte[] body) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(body); Vector clientSupplementalData = TlsProtocol.readSupplementalDataMessage(buf); state.server.processClientSupplementalData(clientSupplementalData); @@ -620,6 +636,8 @@ public class DTLSServerProtocol int selectedCipherSuite = -1; short selectedCompressionMethod = -1; boolean secure_renegotiation = false; + short maxFragmentLength = -1; + boolean allowCertificateStatus = false; boolean expectSessionTicket = false; Hashtable serverExtensions = null; TlsKeyExchange keyExchange = null; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java index 98efc4f..78afb41 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java @@ -11,9 +11,8 @@ import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.util.BigIntegers; public class DefaultTlsAgreementCredentials - implements TlsAgreementCredentials + extends AbstractTlsAgreementCredentials { - protected Certificate certificate; protected AsymmetricKeyParameter privateKey; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java index 82b37d9..7f70c64 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java @@ -4,6 +4,7 @@ import java.io.IOException; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.digests.SHA1Digest; @@ -15,24 +16,37 @@ import org.bouncycastle.crypto.engines.CamelliaEngine; import org.bouncycastle.crypto.engines.DESedeEngine; import org.bouncycastle.crypto.engines.RC4Engine; import org.bouncycastle.crypto.engines.SEEDEngine; +import org.bouncycastle.crypto.engines.Salsa20Engine; +import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.modes.CCMBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher; public class DefaultTlsCipherFactory extends AbstractTlsCipherFactory { - public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) throws IOException { - switch (encryptionAlgorithm) { case EncryptionAlgorithm._3DES_EDE_CBC: return createDESedeCipher(context, macAlgorithm); 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); @@ -45,10 +59,14 @@ public class DefaultTlsCipherFactory return createCamelliaCipher(context, 16, macAlgorithm); case EncryptionAlgorithm.CAMELLIA_256_CBC: return createCamelliaCipher(context, 32, macAlgorithm); + 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: @@ -63,6 +81,13 @@ public class DefaultTlsCipherFactory createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize); } + protected TlsAEADCipher createCipher_AES_CCM(TlsContext context, int cipherKeySize, int macSize) + throws IOException + { + return new TlsAEADCipher(context, createAEADBlockCipher_AES_CCM(), + createAEADBlockCipher_AES_CCM(), cipherKeySize, macSize); + } + protected TlsAEADCipher createCipher_AES_GCM(TlsContext context, int cipherKeySize, int macSize) throws IOException { @@ -70,8 +95,7 @@ public class DefaultTlsCipherFactory createAEADBlockCipher_AES_GCM(), cipherKeySize, macSize); } - protected TlsBlockCipher createCamelliaCipher(TlsContext context, int cipherKeySize, - int macAlgorithm) + protected TlsBlockCipher createCamelliaCipher(TlsContext context, int cipherKeySize, int macAlgorithm) throws IOException { return new TlsBlockCipher(context, createCamelliaBlockCipher(), @@ -79,6 +103,13 @@ public class DefaultTlsCipherFactory createHMACDigest(macAlgorithm), cipherKeySize); } + protected TlsBlockCipher createDESedeCipher(TlsContext context, int macAlgorithm) + throws IOException + { + return new TlsBlockCipher(context, createDESedeBlockCipher(), createDESedeBlockCipher(), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 24); + } + protected TlsNullCipher createNullCipher(TlsContext context, int macAlgorithm) throws IOException { @@ -86,19 +117,22 @@ public class DefaultTlsCipherFactory createHMACDigest(macAlgorithm)); } - protected TlsStreamCipher createRC4Cipher(TlsContext context, int cipherKeySize, - int macAlgorithm) + protected TlsStreamCipher createRC4Cipher(TlsContext context, int cipherKeySize, int macAlgorithm) throws IOException { return new TlsStreamCipher(context, createRC4StreamCipher(), createRC4StreamCipher(), createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize); } - protected TlsBlockCipher createDESedeCipher(TlsContext context, int macAlgorithm) + protected TlsStreamCipher createSalsa20Cipher(TlsContext context, int rounds, int cipherKeySize, int macAlgorithm) throws IOException { - return new TlsBlockCipher(context, createDESedeBlockCipher(), createDESedeBlockCipher(), - createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 24); + /* + * TODO To be able to support UMAC96, we need to give the TlsStreamCipher a Mac instead of + * assuming HMAC and passing a digest. + */ + return new TlsStreamCipher(context, createSalsa20StreamCipher(rounds), createSalsa20StreamCipher(rounds), + createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize); } protected TlsBlockCipher createSEEDCipher(TlsContext context, int macAlgorithm) @@ -108,14 +142,14 @@ public class DefaultTlsCipherFactory createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 16); } - protected StreamCipher createRC4StreamCipher() + protected BlockCipher createAESBlockCipher() { - return new RC4Engine(); + return new CBCBlockCipher(new AESFastEngine()); } - protected BlockCipher createAESBlockCipher() + protected AEADBlockCipher createAEADBlockCipher_AES_CCM() { - return new CBCBlockCipher(new AESFastEngine()); + return new CCMBlockCipher(new AESFastEngine()); } protected AEADBlockCipher createAEADBlockCipher_AES_GCM() @@ -134,13 +168,22 @@ public class DefaultTlsCipherFactory return new CBCBlockCipher(new DESedeEngine()); } + protected StreamCipher createRC4StreamCipher() + { + return new RC4Engine(); + } + + protected StreamCipher createSalsa20StreamCipher(int rounds) + { + return new Salsa20Engine(rounds); + } + protected BlockCipher createSEEDBlockCipher() { return new CBCBlockCipher(new SEEDEngine()); } - protected Digest createHMACDigest(int macAlgorithm) - throws IOException + protected Digest createHMACDigest(int macAlgorithm) throws IOException { switch (macAlgorithm) { @@ -160,4 +203,16 @@ public class DefaultTlsCipherFactory throw new TlsFatalAlert(AlertDescription.internal_error); } } + + protected Mac createMac(int macAlgorithm) throws IOException + { + switch (macAlgorithm) + { + // TODO Need an implementation of UMAC +// case MACAlgorithm.umac96: +// return + default: + return new HMac(createHMACDigest(macAlgorithm)); + } + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java index 4f9fe27..63db45f 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsClient.java @@ -1,15 +1,10 @@ package org.bouncycastle.crypto.tls; import java.io.IOException; -import java.util.Hashtable; public abstract class DefaultTlsClient extends AbstractTlsClient { - - protected int[] namedCurves; - protected short[] clientECPointFormats, serverECPointFormats; - public DefaultTlsClient() { super(); @@ -22,75 +17,15 @@ public abstract class DefaultTlsClient public int[] getCipherSuites() { - return new int[]{CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,}; - } - - public Hashtable getClientExtensions() - throws IOException - { - - Hashtable clientExtensions = super.getClientExtensions(); - - if (TlsECCUtils.containsECCCipherSuites(getCipherSuites())) - { - /* - * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message - * appends these extensions (along with any others), enumerating the curves it supports - * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic - * Curves Extension and the Supported Point Formats Extension. - */ - /* - * TODO Could just add all the curves since we support them all, but users may not want - * to use unnecessarily large fields. Need configuration options. - */ - this.namedCurves = new int[]{NamedCurve.secp256r1, NamedCurve.sect233r1, NamedCurve.secp224r1, - NamedCurve.sect193r1, NamedCurve.secp192r1, NamedCurve.arbitrary_explicit_char2_curves, - NamedCurve.arbitrary_explicit_prime_curves}; - this.clientECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2, - ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed}; - - if (clientExtensions == null) - { - clientExtensions = new Hashtable(); - } - - TlsECCUtils.addSupportedEllipticCurvesExtension(clientExtensions, namedCurves); - TlsECCUtils.addSupportedPointFormatsExtension(clientExtensions, clientECPointFormats); - } - - return clientExtensions; - } - - public void processServerExtensions(Hashtable serverExtensions) - throws IOException - { - - super.processServerExtensions(serverExtensions); - - if (serverExtensions != null) - { - int[] namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(serverExtensions); - if (namedCurves != null) - { - throw new TlsFatalAlert(AlertDescription.illegal_parameter); - } - - this.serverECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(serverExtensions); - if (this.serverECPointFormats != null && !TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite)) - { - throw new TlsFatalAlert(AlertDescription.illegal_parameter); - } - } + return new int[] { CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA }; } public TlsKeyExchange getKeyExchange() throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: @@ -132,12 +67,20 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_UMAC96: case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA); @@ -170,8 +113,12 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96: return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA); case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: @@ -181,24 +128,36 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_UMAC96: return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA); case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_RSA_WITH_NULL_MD5: case CipherSuite.TLS_RSA_WITH_NULL_SHA: case CipherSuite.TLS_RSA_WITH_NULL_SHA256: case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_SALSA20_UMAC96: case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: return createRSAKeyExchange(); @@ -215,7 +174,6 @@ public abstract class DefaultTlsClient public TlsCipher getCipher() throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: @@ -251,6 +209,14 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256); + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null); + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: @@ -286,6 +252,14 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384); + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null); + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: @@ -311,6 +285,18 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.umac96); + case CipherSuite.TLS_RSA_WITH_NULL_MD5: return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5); @@ -334,6 +320,18 @@ public abstract class DefaultTlsClient case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_WITH_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.umac96); + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java index a338c38..dcdb493 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java @@ -10,14 +10,14 @@ import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.params.RSAKeyParameters; public class DefaultTlsEncryptionCredentials - implements TlsEncryptionCredentials + extends AbstractTlsEncryptionCredentials { protected TlsContext context; protected Certificate certificate; protected AsymmetricKeyParameter privateKey; public DefaultTlsEncryptionCredentials(TlsContext context, Certificate certificate, - AsymmetricKeyParameter privateKey) + AsymmetricKeyParameter privateKey) { if (certificate == null) { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java index 246b87e..31b6a74 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsServer.java @@ -48,15 +48,18 @@ public abstract class DefaultTlsServer public TlsCredentials getCredentials() throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: @@ -71,9 +74,13 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: @@ -85,6 +92,8 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: return getRSASignerCredentials(); default: @@ -98,7 +107,6 @@ public abstract class DefaultTlsServer public TlsKeyExchange getKeyExchange() throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: @@ -140,12 +148,20 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_UMAC96: case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA); @@ -178,8 +194,12 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96: return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA); case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: @@ -189,24 +209,36 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_UMAC96: return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA); case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_RSA_WITH_NULL_MD5: case CipherSuite.TLS_RSA_WITH_NULL_SHA: case CipherSuite.TLS_RSA_WITH_NULL_SHA256: case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_SALSA20_UMAC96: case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: return createRSAKeyExchange(); @@ -221,7 +253,6 @@ public abstract class DefaultTlsServer public TlsCipher getCipher() throws IOException { - switch (selectedCipherSuite) { case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: @@ -257,6 +288,14 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256); + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null); + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: @@ -292,6 +331,14 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384); + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null); + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: @@ -317,6 +364,18 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.umac96); + case CipherSuite.TLS_RSA_WITH_NULL_MD5: return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5); @@ -340,6 +399,18 @@ public abstract class DefaultTlsServer case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_WITH_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.umac96); + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java index b775250..15bf4a4 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java @@ -9,17 +9,23 @@ import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.RSAKeyParameters; public class DefaultTlsSignerCredentials - implements TlsSignerCredentials + extends AbstractTlsSignerCredentials { protected TlsContext context; protected Certificate certificate; protected AsymmetricKeyParameter privateKey; + protected SignatureAndHashAlgorithm signatureAndHashAlgorithm; protected TlsSigner signer; public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey) { + this(context, certificate, privateKey, null); + } + public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey, + SignatureAndHashAlgorithm signatureAndHashAlgorithm) + { if (certificate == null) { throw new IllegalArgumentException("'certificate' cannot be null"); @@ -36,6 +42,10 @@ public class DefaultTlsSignerCredentials { throw new IllegalArgumentException("'privateKey' must be private"); } + if (TlsUtils.isTLSv12(context) && signatureAndHashAlgorithm == null) + { + throw new IllegalArgumentException("'signatureAndHashAlgorithm' cannot be null for (D)TLS 1.2+"); + } if (privateKey instanceof RSAKeyParameters) { @@ -59,6 +69,7 @@ public class DefaultTlsSignerCredentials this.context = context; this.certificate = certificate; this.privateKey = privateKey; + this.signatureAndHashAlgorithm = signatureAndHashAlgorithm; } public Certificate getCertificate() @@ -66,16 +77,28 @@ public class DefaultTlsSignerCredentials return certificate; } - public byte[] generateCertificateSignature(byte[] md5andsha1) + public byte[] generateCertificateSignature(byte[] hash) throws IOException { try { - return signer.generateRawSignature(privateKey, md5andsha1); + if (TlsUtils.isTLSv12(context)) + { + return signer.generateRawSignature(signatureAndHashAlgorithm, privateKey, hash); + } + else + { + return signer.generateRawSignature(privateKey, hash); + } } catch (CryptoException e) { throw new TlsFatalAlert(AlertDescription.internal_error); } } + + public SignatureAndHashAlgorithm getSignatureAndHashAlgorithm() + { + return signatureAndHashAlgorithm; + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java index e8c76e6..274e69a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DeferredHash.java @@ -1,8 +1,10 @@ package org.bouncycastle.crypto.tls; -import java.io.ByteArrayOutputStream; +import java.util.Enumeration; +import java.util.Hashtable; import org.bouncycastle.crypto.Digest; +import org.bouncycastle.util.Shorts; /** * Buffers input until the hash algorithm is determined. @@ -10,23 +12,27 @@ import org.bouncycastle.crypto.Digest; class DeferredHash implements TlsHandshakeHash { + protected static final int BUFFERING_HASH_LIMIT = 4; protected TlsContext context; - private ByteArrayOutputStream buf = new ByteArrayOutputStream(); - private int prfAlgorithm = -1; - private Digest hash = null; + private DigestInputBuffer buf; + private Hashtable hashes; + private Short prfHashAlgorithm; DeferredHash() { - this.buf = new ByteArrayOutputStream(); - this.hash = null; + this.buf = new DigestInputBuffer(); + this.hashes = new Hashtable(); + this.prfHashAlgorithm = null; } - private DeferredHash(Digest hash) + private DeferredHash(Short prfHashAlgorithm, Digest prfHash) { this.buf = null; - this.hash = hash; + this.hashes = new Hashtable(); + this.prfHashAlgorithm = prfHashAlgorithm; + hashes.put(prfHashAlgorithm, prfHash); } public void init(TlsContext context) @@ -34,95 +40,168 @@ class DeferredHash this.context = context; } - public TlsHandshakeHash commit() + public TlsHandshakeHash notifyPRFDetermined() { - int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm(); + if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy) + { + CombinedHash legacyHash = new CombinedHash(); + legacyHash.init(context); + buf.updateDigest(legacyHash); + return legacyHash.notifyPRFDetermined(); + } - Digest prfHash = TlsUtils.createPRFHash(prfAlgorithm); + this.prfHashAlgorithm = Shorts.valueOf(TlsUtils.getHashAlgorithmForPRFAlgorithm(prfAlgorithm)); - byte[] data = buf.toByteArray(); - prfHash.update(data, 0, data.length); + checkTrackingHash(prfHashAlgorithm); + + return this; + } - if (prfHash instanceof TlsHandshakeHash) + public void trackHashAlgorithm(short hashAlgorithm) + { + if (buf == null) { - TlsHandshakeHash tlsPRFHash = (TlsHandshakeHash)prfHash; - tlsPRFHash.init(context); - return tlsPRFHash.commit(); + throw new IllegalStateException("Too late to track more hash algorithms"); } - this.prfAlgorithm = prfAlgorithm; - this.hash = prfHash; - this.buf = null; + checkTrackingHash(Shorts.valueOf(hashAlgorithm)); + } - return this; + public void sealHashAlgorithms() + { + checkStopBuffering(); } - public TlsHandshakeHash fork() + public TlsHandshakeHash stopTracking() { - checkHash(); - return new DeferredHash(TlsUtils.clonePRFHash(prfAlgorithm, hash)); + Digest prfHash = TlsUtils.cloneHash(prfHashAlgorithm.shortValue(), (Digest)hashes.get(prfHashAlgorithm)); + if (buf != null) + { + buf.updateDigest(prfHash); + } + DeferredHash result = new DeferredHash(prfHashAlgorithm, prfHash); + result.init(context); + return result; + } + + public Digest forkPRFHash() + { + checkStopBuffering(); + + if (buf != null) + { + Digest prfHash = TlsUtils.createHash(prfHashAlgorithm.shortValue()); + buf.updateDigest(prfHash); + return prfHash; + } + + return TlsUtils.cloneHash(prfHashAlgorithm.shortValue(), (Digest)hashes.get(prfHashAlgorithm)); + } + + public byte[] getFinalHash(short hashAlgorithm) + { + Digest d = (Digest)hashes.get(Shorts.valueOf(hashAlgorithm)); + if (d == null) + { + throw new IllegalStateException("HashAlgorithm " + hashAlgorithm + " is not being tracked"); + } + + d = TlsUtils.cloneHash(hashAlgorithm, d); + if (buf != null) + { + buf.updateDigest(d); + } + + byte[] bs = new byte[d.getDigestSize()]; + d.doFinal(bs, 0); + return bs; } public String getAlgorithmName() { - checkHash(); - return hash.getAlgorithmName(); + throw new IllegalStateException("Use fork() to get a definite Digest"); } public int getDigestSize() { - checkHash(); - return hash.getDigestSize(); + throw new IllegalStateException("Use fork() to get a definite Digest"); } public void update(byte input) { - if (hash == null) + if (buf != null) { buf.write(input); + return; } - else + + Enumeration e = hashes.elements(); + while (e.hasMoreElements()) { + Digest hash = (Digest)e.nextElement(); hash.update(input); } } public void update(byte[] input, int inOff, int len) { - if (hash == null) + if (buf != null) { buf.write(input, inOff, len); + return; } - else + + Enumeration e = hashes.elements(); + while (e.hasMoreElements()) { + Digest hash = (Digest)e.nextElement(); hash.update(input, inOff, len); } } public int doFinal(byte[] output, int outOff) { - checkHash(); - return hash.doFinal(output, outOff); + throw new IllegalStateException("Use fork() to get a definite Digest"); } public void reset() { - if (hash == null) + if (buf != null) { buf.reset(); + return; } - else + + Enumeration e = hashes.elements(); + while (e.hasMoreElements()) { + Digest hash = (Digest)e.nextElement(); hash.reset(); } } - protected void checkHash() + protected void checkStopBuffering() + { + if (buf != null && hashes.size() <= BUFFERING_HASH_LIMIT) + { + Enumeration e = hashes.elements(); + while (e.hasMoreElements()) + { + Digest hash = (Digest)e.nextElement(); + buf.updateDigest(hash); + } + + this.buf = null; + } + } + + protected void checkTrackingHash(Short hashAlgorithm) { - if (hash == null) + if (!hashes.containsKey(hashAlgorithm)) { - throw new IllegalStateException("No hash algorithm has been set"); + Digest hash = TlsUtils.createHash(hashAlgorithm.shortValue()); + hashes.put(hashAlgorithm, hash); } } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigestInputBuffer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigestInputBuffer.java new file mode 100644 index 0000000..7cfa890 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigestInputBuffer.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; + +import org.bouncycastle.crypto.Digest; + +class DigestInputBuffer extends ByteArrayOutputStream +{ + void updateDigest(Digest d) + { + d.update(this.buf, 0, count); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigitallySigned.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigitallySigned.java new file mode 100644 index 0000000..c366ca9 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/DigitallySigned.java @@ -0,0 +1,72 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class DigitallySigned +{ + protected SignatureAndHashAlgorithm algorithm; + protected byte[] signature; + + public DigitallySigned(SignatureAndHashAlgorithm algorithm, byte[] signature) + { + if (signature == null) + { + throw new IllegalArgumentException("'signature' cannot be null"); + } + + this.algorithm = algorithm; + this.signature = signature; + } + + /** + * @return a {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + */ + public SignatureAndHashAlgorithm getAlgorithm() + { + return algorithm; + } + + public byte[] getSignature() + { + return signature; + } + + /** + * Encode this {@link DigitallySigned} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + if (algorithm != null) + { + algorithm.encode(output); + } + TlsUtils.writeOpaque16(signature, output); + } + + /** + * Parse a {@link DigitallySigned} from an {@link InputStream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link InputStream} to parse from. + * @return a {@link DigitallySigned} object. + * @throws IOException + */ + public static DigitallySigned parse(TlsContext context, InputStream input) throws IOException + { + SignatureAndHashAlgorithm algorithm = null; + if (TlsUtils.isTLSv12(context)) + { + algorithm = SignatureAndHashAlgorithm.parse(input); + } + byte[] signature = TlsUtils.readOpaque16(input); + return new DigitallySigned(algorithm, signature); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java index f991e4a..6f3fe01 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java @@ -25,6 +25,12 @@ public class EncryptionAlgorithm public static final int AES_256_CBC = 9; /* + * RFC 5289 + */ + public static final int AES_128_GCM = 10; + public static final int AES_256_GCM = 11; + + /* * RFC 4132 */ public static final int CAMELLIA_128_CBC = 12; @@ -36,8 +42,16 @@ public class EncryptionAlgorithm public static final int SEED_CBC = 14; /* - * RFC 5289 + * RFC 6655 */ - public static final int AES_128_GCM = 10; - public static final int AES_256_GCM = 11; + public static final int AES_128_CCM = 15; + public static final int AES_128_CCM_8 = 16; + public static final int AES_256_CCM = 17; + public static final int AES_256_CCM_8 = 18; + + /* + * TBD[draft-josefsson-salsa20-tls-02] + */ + static final int ESTREAM_SALSA20 = 100; + static final int SALSA20 = 101; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java index 0be6465..8312e93 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ExtensionType.java @@ -3,7 +3,7 @@ package org.bouncycastle.crypto.tls; public class ExtensionType { /* - * RFC 6066 1.1. + * RFC 2546 2.3. */ public static final int server_name = 0; public static final int max_fragment_length = 1; @@ -44,6 +44,11 @@ public class ExtensionType public static final int use_srtp = 14; /* + * RFC 6520 6. + */ + public static final int heartbeat = 15; + + /* * RFC 5746 3.2. */ public static final int renegotiation_info = 0xff01; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java index 53b4520..c81660a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HandshakeType.java @@ -17,6 +17,12 @@ public class HandshakeType public static final short finished = 20; /* + * RFC 3546 2.4 + */ + public static final short certificate_url = 21; + public static final short certificate_status = 22; + + /* * (DTLS) RFC 4347 4.3.2 */ public static final short hello_verify_request = 3; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java index ac0a4c6..dc7482b 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HashAlgorithm.java @@ -5,7 +5,6 @@ package org.bouncycastle.crypto.tls; */ public class HashAlgorithm { - public static final short none = 0; public static final short md5 = 1; public static final short sha1 = 2; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatExtension.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatExtension.java new file mode 100644 index 0000000..f9f3670 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatExtension.java @@ -0,0 +1,56 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class HeartbeatExtension +{ + protected short mode; + + public HeartbeatExtension(short mode) + { + if (!HeartbeatMode.isValid(mode)) + { + throw new IllegalArgumentException("'mode' is not a valid HeartbeatMode value"); + } + + this.mode = mode; + } + + public short getMode() + { + return mode; + } + + /** + * Encode this {@link HeartbeatExtension} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + TlsUtils.writeUint8(mode, output); + } + + /** + * Parse a {@link HeartbeatExtension} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link HeartbeatExtension} object. + * @throws IOException + */ + public static HeartbeatExtension parse(InputStream input) throws IOException + { + short mode = TlsUtils.readUint8(input); + if (!HeartbeatMode.isValid(mode)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return new HeartbeatExtension(mode); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessage.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessage.java new file mode 100644 index 0000000..320a128 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessage.java @@ -0,0 +1,108 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.io.Streams; + +public class HeartbeatMessage +{ + protected short type; + protected byte[] payload; + protected int paddingLength; + + public HeartbeatMessage(short type, byte[] payload, int paddingLength) + { + if (!HeartbeatMessageType.isValid(type)) + { + throw new IllegalArgumentException("'type' is not a valid HeartbeatMessageType value"); + } + if (payload == null || payload.length >= (1 << 16)) + { + throw new IllegalArgumentException("'payload' must have length < 2^16"); + } + if (paddingLength < 16) + { + throw new IllegalArgumentException("'paddingLength' must be at least 16"); + } + + this.type = type; + this.payload = payload; + this.paddingLength = paddingLength; + } + + /** + * Encode this {@link HeartbeatMessage} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(TlsContext context, OutputStream output) throws IOException + { + TlsUtils.writeUint8(type, output); + + TlsUtils.checkUint16(payload.length); + TlsUtils.writeUint16(payload.length, output); + output.write(payload); + + byte[] padding = new byte[paddingLength]; + context.getSecureRandom().nextBytes(padding); + output.write(padding); + } + + /** + * Parse a {@link HeartbeatMessage} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link HeartbeatMessage} object. + * @throws IOException + */ + public static HeartbeatMessage parse(InputStream input) throws IOException + { + short type = TlsUtils.readUint8(input); + if (!HeartbeatMessageType.isValid(type)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + int payload_length = TlsUtils.readUint16(input); + + PayloadBuffer buf = new PayloadBuffer(); + Streams.pipeAll(input, buf); + + byte[] payload = buf.toTruncatedByteArray(payload_length); + if (payload == null) + { + /* + * RFC 6520 4. If the payload_length of a received HeartbeatMessage is too large, the + * received HeartbeatMessage MUST be discarded silently. + */ + return null; + } + + int padding_length = buf.size() - payload.length; + + return new HeartbeatMessage(type, payload, padding_length); + } + + static class PayloadBuffer extends ByteArrayOutputStream + { + byte[] toTruncatedByteArray(int payloadLength) + { + /* + * RFC 6520 4. The padding_length MUST be at least 16. + */ + int minimumCount = payloadLength + 16; + if (count < minimumCount) + { + return null; + } + return Arrays.copyOf(buf, payloadLength); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessageType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessageType.java new file mode 100644 index 0000000..f1a3b43 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMessageType.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.tls; + +/* + * RFC 6520 3. + */ +public class HeartbeatMessageType +{ + public static final short heartbeat_request = 1; + public static final short heartbeat_response = 2; + + public static boolean isValid(short heartbeatMessageType) + { + return heartbeatMessageType >= heartbeat_request && heartbeatMessageType <= heartbeat_response; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMode.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMode.java new file mode 100644 index 0000000..4024faa --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/HeartbeatMode.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto.tls; + +/* + * RFC 6520 + */ +public class HeartbeatMode +{ + public static final short peer_allowed_to_send = 1; + public static final short peer_not_allowed_to_send = 2; + + public static boolean isValid(short heartbeatMode) + { + return heartbeatMode >= peer_allowed_to_send && heartbeatMode <= peer_not_allowed_to_send; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java index c049bb7..72a944f 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java @@ -44,4 +44,9 @@ public class KeyExchangeAlgorithm public static final int SRP = 21; public static final int SRP_DSS = 22; public static final int SRP_RSA = 23; + + /* + * RFC 5489 + */ + public static final int ECDHE_PSK = 24; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java index 40ef15c..92adc8c 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/MACAlgorithm.java @@ -8,7 +8,6 @@ package org.bouncycastle.crypto.tls; */ public class MACAlgorithm { - public static final int _null = 0; public static final int md5 = 1; public static final int sha = 2; @@ -21,4 +20,9 @@ public class MACAlgorithm public static final int hmac_sha256 = 3; public static final int hmac_sha384 = 4; public static final int hmac_sha512 = 5; + + /* + * TBD[draft-josefsson-salsa20-tls-02] + */ + static final int umac96 = 100; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/MaxFragmentLength.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/MaxFragmentLength.java new file mode 100644 index 0000000..413695c --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/MaxFragmentLength.java @@ -0,0 +1,17 @@ +package org.bouncycastle.crypto.tls; + +public class MaxFragmentLength +{ + /* + * RFC 3546 3.2. + */ + public static short pow2_9 = 1; + public static short pow2_10 = 2; + public static short pow2_11 = 3; + public static short pow2_12 = 4; + + public static boolean isValid(short maxFragmentLength) + { + return maxFragmentLength >= pow2_9 && maxFragmentLength <= pow2_12; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/NameType.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NameType.java new file mode 100644 index 0000000..9b0cd1b --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NameType.java @@ -0,0 +1,9 @@ +package org.bouncycastle.crypto.tls; + +public class NameType +{ + /* + * RFC 3546 3.1. + */ + public static final short host_name = 0; +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java index 690115c..a965d13 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NamedCurve.java @@ -36,6 +36,13 @@ public class NamedCurve public static final int secp256r1 = 23; public static final int secp384r1 = 24; public static final int secp521r1 = 25; + + /* + * RFC 7027 + */ + public static final int brainpoolP256r1 = 26; + public static final int brainpoolP384r1 = 27; + public static final int brainpoolP512r1 = 28; /* * reserved (0xFE00..0xFEFF) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java index f3d1022..8f87a65 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/NewSessionTicket.java @@ -6,7 +6,6 @@ import java.io.OutputStream; public class NewSessionTicket { - protected long ticketLifetimeHint; protected byte[] ticket; @@ -26,6 +25,12 @@ public class NewSessionTicket return ticket; } + /** + * Encode this {@link NewSessionTicket} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ public void encode(OutputStream output) throws IOException { @@ -33,6 +38,13 @@ public class NewSessionTicket TlsUtils.writeOpaque16(ticket, output); } + /** + * Parse a {@link NewSessionTicket} from an {@link InputStream}. + * + * @param input the {@link InputStream} to parse from. + * @return a {@link NewSessionTicket} object. + * @throws IOException + */ public static NewSessionTicket parse(InputStream input) throws IOException { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/OCSPStatusRequest.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/OCSPStatusRequest.java new file mode 100644 index 0000000..db8168f --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/OCSPStatusRequest.java @@ -0,0 +1,131 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ocsp.ResponderID; +import org.bouncycastle.asn1.x509.Extensions; + +/** + * RFC 3546 3.6 + */ +public class OCSPStatusRequest +{ + protected Vector responderIDList; + protected Extensions requestExtensions; + + /** + * @param responderIDList + * a {@link Vector} of {@link ResponderID}, specifying the list of trusted OCSP + * responders. An empty list has the special meaning that the responders are + * implicitly known to the server - e.g., by prior arrangement. + * @param requestExtensions + * OCSP request extensions. A null value means that there are no extensions. + */ + public OCSPStatusRequest(Vector responderIDList, Extensions requestExtensions) + { + this.responderIDList = responderIDList; + this.requestExtensions = requestExtensions; + } + + /** + * @return a {@link Vector} of {@link ResponderID} + */ + public Vector getResponderIDList() + { + return responderIDList; + } + + /** + * @return OCSP request extensions + */ + public Extensions getRequestExtensions() + { + return requestExtensions; + } + + /** + * Encode this {@link OCSPStatusRequest} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + if (responderIDList == null || responderIDList.isEmpty()) + { + TlsUtils.writeUint16(0, output); + } + else + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (int i = 0; i < responderIDList.size(); ++i) + { + ResponderID responderID = (ResponderID) responderIDList.elementAt(i); + byte[] derEncoding = responderID.getEncoded(ASN1Encoding.DER); + TlsUtils.writeOpaque16(derEncoding, buf); + } + TlsUtils.checkUint16(buf.size()); + TlsUtils.writeUint16(buf.size(), output); + buf.writeTo(output); + } + + if (requestExtensions == null) + { + TlsUtils.writeUint16(0, output); + } + else + { + byte[] derEncoding = requestExtensions.getEncoded(ASN1Encoding.DER); + TlsUtils.checkUint16(derEncoding.length); + TlsUtils.writeUint16(derEncoding.length, output); + output.write(derEncoding); + } + } + + /** + * Parse a {@link OCSPStatusRequest} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link OCSPStatusRequest} object. + * @throws IOException + */ + public static OCSPStatusRequest parse(InputStream input) throws IOException + { + Vector responderIDList = new Vector(); + { + int length = TlsUtils.readUint16(input); + if (length > 0) + { + byte[] data = TlsUtils.readFully(length, input); + ByteArrayInputStream buf = new ByteArrayInputStream(data); + do + { + byte[] derEncoding = TlsUtils.readOpaque16(buf); + ResponderID responderID = ResponderID.getInstance(TlsUtils.readDERObject(derEncoding)); + responderIDList.addElement(responderID); + } + while (buf.available() > 0); + } + } + + Extensions requestExtensions = null; + { + int length = TlsUtils.readUint16(input); + if (length > 0) + { + byte[] derEncoding = TlsUtils.readFully(length, input); + requestExtensions = Extensions.getInstance(TlsUtils.readDERObject(derEncoding)); + } + } + + return new OCSPStatusRequest(responderIDList, requestExtensions); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java index 29750cb..92475b2 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/PSKTlsClient.java @@ -21,41 +21,89 @@ public abstract class PSKTlsClient public int[] getCipherSuites() { - return new int[]{CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA, - CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA, - CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_PSK_WITH_RC4_128_SHA,}; + return new int[] { CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA }; } - public TlsKeyExchange getKeyExchange() - throws IOException + public TlsKeyExchange getKeyExchange() throws IOException { - switch (selectedCipherSuite) { + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + return createPSKKeyExchange(KeyExchangeAlgorithm.DHE_PSK); + + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_UMAC96: + return createPSKKeyExchange(KeyExchangeAlgorithm.ECDHE_PSK); + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_SALSA20_UMAC96: return createPSKKeyExchange(KeyExchangeAlgorithm.PSK); case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_UMAC96: case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_UMAC96: return createPSKKeyExchange(KeyExchangeAlgorithm.RSA_PSK); - case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: - case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: - case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: - case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: - case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: - return createPSKKeyExchange(KeyExchangeAlgorithm.DHE_PSK); - default: /* * Note: internal error here; the TlsProtocol implementation verifies that the @@ -66,37 +114,114 @@ public abstract class PSKTlsClient } } - public TlsCipher getCipher() - throws IOException + public TlsCipher getCipher() throws IOException { - switch (selectedCipherSuite) { + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: 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._3DES_EDE_CBC, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: 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, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: 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, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null); + + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null); + + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.umac96); + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: case CipherSuite.TLS_PSK_WITH_NULL_SHA: case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: - case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: 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, MACAlgorithm.hmac_sha1); + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_PSK_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_UMAC96: + return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.umac96); + default: /* * Note: internal error here; the TlsProtocol implementation verifies that the @@ -109,6 +234,7 @@ public abstract class PSKTlsClient protected TlsKeyExchange createPSKKeyExchange(int keyExchange) { - return new TlsPSKKeyExchange(keyExchange, supportedSignatureAlgorithms, pskIdentity); + return new TlsPSKKeyExchange(keyExchange, supportedSignatureAlgorithms, pskIdentity, null, namedCurves, + clientECPointFormats, serverECPointFormats); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java index c001e58..b32bd9d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ProtocolVersion.java @@ -4,7 +4,6 @@ import java.io.IOException; public final class ProtocolVersion { - public static final ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0"); public static final ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0"); public static final ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1"); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java index 3a31c20..cc6640b 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/RecordStream.java @@ -12,10 +12,7 @@ import org.bouncycastle.crypto.Digest; */ class RecordStream { - - private static int PLAINTEXT_LIMIT = (1 << 14); - private static int COMPRESSED_LIMIT = PLAINTEXT_LIMIT + 1024; - private static int CIPHERTEXT_LIMIT = COMPRESSED_LIMIT + 1024; + private static int DEFAULT_PLAINTEXT_LIMIT = (1 << 14); private TlsProtocol handler; private InputStream input; @@ -26,11 +23,13 @@ class RecordStream private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); private TlsContext context = null; - private TlsHandshakeHash hash = null; + private TlsHandshakeHash handshakeHash = null; private ProtocolVersion readVersion = null, writeVersion = null; private boolean restrictReadVersion = true; + private int plaintextLimit, compressedLimit, ciphertextLimit; + RecordStream(TlsProtocol handler, InputStream input, OutputStream output) { this.handler = handler; @@ -40,13 +39,27 @@ class RecordStream this.writeCompression = this.readCompression; this.readCipher = new TlsNullCipher(context); this.writeCipher = this.readCipher; + + setPlaintextLimit(DEFAULT_PLAINTEXT_LIMIT); } void init(TlsContext context) { this.context = context; - this.hash = new DeferredHash(); - this.hash.init(context); + this.handshakeHash = new DeferredHash(); + this.handshakeHash.init(context); + } + + int getPlaintextLimit() + { + return plaintextLimit; + } + + void setPlaintextLimit(int plaintextLimit) + { + this.plaintextLimit = plaintextLimit; + this.compressedLimit = this.plaintextLimit + 1024; + this.ciphertextLimit = this.compressedLimit + 1024; } ProtocolVersion getReadVersion() @@ -76,11 +89,6 @@ class RecordStream this.restrictReadVersion = enabled; } - void notifyHelloComplete() - { - this.hash = this.hash.commit(); - } - void setPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher) { this.pendingCompression = tlsCompression; @@ -123,13 +131,17 @@ class RecordStream pendingCipher = null; } - public void readRecord() + public boolean readRecord() throws IOException { + byte[] recordHeader = TlsUtils.readAllOrNothing(5, input); + if (recordHeader == null) + { + return false; + } - short type = TlsUtils.readUint8(input); + short type = TlsUtils.readUint8(recordHeader, 0); - // TODO In earlier RFCs, it was "SHOULD ignore"; should this be version-dependent? /* * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an * unexpected_message alert. @@ -138,7 +150,7 @@ class RecordStream if (!restrictReadVersion) { - int version = TlsUtils.readVersionRaw(input); + int version = TlsUtils.readVersionRaw(recordHeader, 1); if ((version & 0xffffff00) != 0x0300) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); @@ -146,7 +158,7 @@ class RecordStream } else { - ProtocolVersion version = TlsUtils.readVersion(input); + ProtocolVersion version = TlsUtils.readVersion(recordHeader, 1); if (readVersion == null) { readVersion = version; @@ -157,21 +169,21 @@ class RecordStream } } - int length = TlsUtils.readUint16(input); + int length = TlsUtils.readUint16(recordHeader, 3); byte[] plaintext = decodeAndVerify(type, input, length); handler.processRecord(type, plaintext, 0, plaintext.length); + return true; } protected byte[] decodeAndVerify(short type, InputStream input, int len) throws IOException { - - checkLength(len, CIPHERTEXT_LIMIT, AlertDescription.record_overflow); + checkLength(len, ciphertextLimit, AlertDescription.record_overflow); byte[] buf = TlsUtils.readFully(len, input); byte[] decoded = readCipher.decodeCiphertext(readSeqNo++, type, buf, 0, buf.length); - checkLength(decoded.length, COMPRESSED_LIMIT, AlertDescription.record_overflow); + checkLength(decoded.length, compressedLimit, AlertDescription.record_overflow); /* * TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for @@ -190,7 +202,16 @@ class RecordStream * would decompress to a length in excess of 2^14 bytes, it should report a fatal * decompression failure error. */ - checkLength(decoded.length, PLAINTEXT_LIMIT, AlertDescription.decompression_failure); + checkLength(decoded.length, plaintextLimit, AlertDescription.decompression_failure); + + /* + * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (decoded.length < 1 && type != ContentType.application_data) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } return decoded; } @@ -198,7 +219,6 @@ class RecordStream protected void writeRecord(short type, byte[] plaintext, int plaintextOffset, int plaintextLength) throws IOException { - /* * RFC 5264 6. Implementations MUST NOT send record types not defined in this document * unless negotiated by some extension. @@ -208,7 +228,7 @@ class RecordStream /* * RFC 5264 6.2.1 The length should not exceed 2^14. */ - checkLength(plaintextLength, PLAINTEXT_LIMIT, AlertDescription.internal_error); + checkLength(plaintextLength, plaintextLimit, AlertDescription.internal_error); /* * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, @@ -249,7 +269,7 @@ class RecordStream /* * RFC 5264 6.2.3. The length may not exceed 2^14 + 2048. */ - checkLength(ciphertext.length, CIPHERTEXT_LIMIT, AlertDescription.internal_error); + checkLength(ciphertext.length, ciphertextLimit, AlertDescription.internal_error); byte[] record = new byte[ciphertext.length + 5]; TlsUtils.writeUint8(type, record, 0); @@ -260,52 +280,44 @@ class RecordStream output.flush(); } - void updateHandshakeData(byte[] message, int offset, int len) + void notifyHelloComplete() { - hash.update(message, offset, len); + this.handshakeHash = handshakeHash.notifyPRFDetermined(); } - /** - * 'sender' only relevant to SSLv3 - */ - byte[] getCurrentHash(byte[] sender) + TlsHandshakeHash getHandshakeHash() { - TlsHandshakeHash d = hash.fork(); + return handshakeHash; + } - if (context.getServerVersion().isSSL()) - { - if (sender != null) - { - d.update(sender, 0, sender.length); - } - } + TlsHandshakeHash prepareToFinish() + { + TlsHandshakeHash result = handshakeHash; + this.handshakeHash = handshakeHash.stopTracking(); + return result; + } - return doFinal(d); + void updateHandshakeData(byte[] message, int offset, int len) + { + handshakeHash.update(message, offset, len); } - protected void close() - throws IOException + protected void safeClose() { - IOException e = null; try { input.close(); } - catch (IOException ex) + catch (IOException e) { - e = ex; } + try { output.close(); } - catch (IOException ex) - { - e = ex; - } - if (e != null) + catch (IOException e) { - throw e; } } @@ -322,23 +334,16 @@ class RecordStream return contents; } - private static byte[] doFinal(Digest d) - { - byte[] bs = new byte[d.getDigestSize()]; - d.doFinal(bs, 0); - return bs; - } - private static void checkType(short type, short alertDescription) throws IOException { - switch (type) { - case ContentType.change_cipher_spec: + case ContentType.application_data: case ContentType.alert: + case ContentType.change_cipher_spec: case ContentType.handshake: - case ContentType.application_data: + case ContentType.heartbeat: break; default: throw new TlsFatalAlert(alertDescription); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java index a5d4840..15295ea 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SRPTlsClient.java @@ -1,16 +1,17 @@ package org.bouncycastle.crypto.tls; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Hashtable; import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.Integers; public abstract class SRPTlsClient extends AbstractTlsClient { - public static final Integer EXT_SRP = Integers.valueOf(ExtensionType.srp); + /** + * @deprecated use TlsSRPUtils.EXT_SRP instead + */ + public static final Integer EXT_SRP = TlsSRPUtils.EXT_SRP; protected byte[] identity; protected byte[] password; @@ -31,40 +32,23 @@ public abstract class SRPTlsClient public int[] getCipherSuites() { - return new int[]{CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, - CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,}; + return new int[] { CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA }; } public Hashtable getClientExtensions() throws IOException { - - Hashtable clientExtensions = super.getClientExtensions(); - if (clientExtensions == null) - { - clientExtensions = new Hashtable(); - } - - ByteArrayOutputStream srpData = new ByteArrayOutputStream(); - TlsUtils.writeOpaque8(this.identity, srpData); - clientExtensions.put(EXT_SRP, srpData.toByteArray()); - + Hashtable clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(super.getClientExtensions()); + TlsSRPUtils.addSRPExtension(clientExtensions, this.identity); return clientExtensions; } public void processServerExtensions(Hashtable serverExtensions) throws IOException { - // No explicit guidance in RFC 5054 here; we allow an optional empty extension from server - if (serverExtensions != null) + if (!TlsUtils.hasExpectedEmptyExtensionData(serverExtensions, TlsSRPUtils.EXT_SRP, AlertDescription.illegal_parameter)) { - byte[] extValue = (byte[])serverExtensions.get(EXT_SRP); - if (extValue != null && extValue.length > 0) - { - throw new TlsFatalAlert(AlertDescription.illegal_parameter); - } + // No explicit guidance in RFC 5054 here; we allow an optional empty extension from server } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java index a7701fe..984246e 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SecurityParameters.java @@ -1,16 +1,41 @@ package org.bouncycastle.crypto.tls; +import org.bouncycastle.util.Arrays; + public class SecurityParameters { - int entity = -1; - int prfAlgorithm = -1; + int cipherSuite = -1; short compressionAlgorithm = -1; + int prfAlgorithm = -1; int verifyDataLength = -1; byte[] masterSecret = null; byte[] clientRandom = null; byte[] serverRandom = null; + // TODO Keep these internal, since it's maybe not the ideal place for them + short maxFragmentLength = -1; + boolean truncatedHMac = false; + + void copySessionParametersFrom(SecurityParameters other) + { + this.entity = other.entity; + this.cipherSuite = other.cipherSuite; + this.compressionAlgorithm = other.compressionAlgorithm; + this.prfAlgorithm = other.prfAlgorithm; + this.verifyDataLength = other.verifyDataLength; + this.masterSecret = Arrays.clone(other.masterSecret); + } + + void clear() + { + if (this.masterSecret != null) + { + Arrays.fill(this.masterSecret, (byte)0); + this.masterSecret = null; + } + } + /** * @return {@link ConnectionEnd} */ @@ -20,11 +45,11 @@ public class SecurityParameters } /** - * @return {@link PRFAlgorithm} + * @return {@link CipherSuite} */ - public int getPrfAlgorithm() + public int getCipherSuite() { - return prfAlgorithm; + return cipherSuite; } /** @@ -35,6 +60,14 @@ public class SecurityParameters return compressionAlgorithm; } + /** + * @return {@link PRFAlgorithm} + */ + public int getPrfAlgorithm() + { + return prfAlgorithm; + } + public int getVerifyDataLength() { return verifyDataLength; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerDHParams.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerDHParams.java new file mode 100644 index 0000000..c3050f1 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerDHParams.java @@ -0,0 +1,63 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; + +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHPublicKeyParameters; + +public class ServerDHParams +{ + protected DHPublicKeyParameters publicKey; + + public ServerDHParams(DHPublicKeyParameters publicKey) + { + if (publicKey == null) + { + throw new IllegalArgumentException("'publicKey' cannot be null"); + } + + this.publicKey = publicKey; + } + + public DHPublicKeyParameters getPublicKey() + { + return publicKey; + } + + /** + * Encode this {@link ServerDHParams} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + DHParameters dhParameters = publicKey.getParameters(); + BigInteger Ys = publicKey.getY(); + + TlsDHUtils.writeDHParameter(dhParameters.getP(), output); + TlsDHUtils.writeDHParameter(dhParameters.getG(), output); + TlsDHUtils.writeDHParameter(Ys, output); + } + + /** + * Parse a {@link ServerDHParams} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link ServerDHParams} object. + * @throws IOException + */ + public static ServerDHParams parse(InputStream input) throws IOException + { + BigInteger p = TlsDHUtils.readDHParameter(input); + BigInteger g = TlsDHUtils.readDHParameter(input); + BigInteger Ys = TlsDHUtils.readDHParameter(input); + + return new ServerDHParams(new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerName.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerName.java new file mode 100644 index 0000000..df9a439 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerName.java @@ -0,0 +1,112 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bouncycastle.util.Strings; + +public class ServerName +{ + protected short nameType; + protected Object name; + + public ServerName(short nameType, Object name) + { + if (!isCorrectType(nameType, name)) + { + throw new IllegalArgumentException("'name' is not an instance of the correct type"); + } + + this.nameType = nameType; + this.name = name; + } + + public short getNameType() + { + return nameType; + } + + public Object getName() + { + return name; + } + + public String getHostName() + { + if (!isCorrectType(NameType.host_name, name)) + { + throw new IllegalStateException("'name' is not a HostName string"); + } + return (String)name; + } + + /** + * Encode this {@link ServerName} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + TlsUtils.writeUint8(nameType, output); + + switch (nameType) + { + case NameType.host_name: + byte[] utf8Encoding = Strings.toUTF8ByteArray((String)name); + if (utf8Encoding.length < 1) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + TlsUtils.writeOpaque16(utf8Encoding, output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link ServerName} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link ServerName} object. + * @throws IOException + */ + public static ServerName parse(InputStream input) throws IOException + { + short name_type = TlsUtils.readUint8(input); + Object name; + + switch (name_type) + { + case NameType.host_name: + { + byte[] utf8Encoding = TlsUtils.readOpaque16(input); + if (utf8Encoding.length < 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + name = Strings.fromUTF8ByteArray(utf8Encoding); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new ServerName(name_type, name); + } + + protected static boolean isCorrectType(short nameType, Object name) + { + switch (nameType) + { + case NameType.host_name: + return name instanceof String; + default: + throw new IllegalArgumentException("'name' is an unsupported value"); + } + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerNameList.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerNameList.java new file mode 100644 index 0000000..1dc81f0 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/ServerNameList.java @@ -0,0 +1,86 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +public class ServerNameList +{ + protected Vector serverNameList; + + /** + * @param serverNameList a {@link Vector} of {@link ServerName}. + */ + public ServerNameList(Vector serverNameList) + { + if (serverNameList == null || serverNameList.isEmpty()) + { + throw new IllegalArgumentException("'serverNameList' must not be null or empty"); + } + + this.serverNameList = serverNameList; + } + + /** + * @return a {@link Vector} of {@link ServerName}. + */ + public Vector getServerNameList() + { + return serverNameList; + } + + /** + * Encode this {@link ServerNameList} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + for (int i = 0; i < serverNameList.size(); ++i) + { + ServerName entry = (ServerName)serverNameList.elementAt(i); + entry.encode(buf); + } + + TlsUtils.checkUint16(buf.size()); + TlsUtils.writeUint16(buf.size(), output); + buf.writeTo(output); + } + + /** + * Parse a {@link ServerNameList} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link ServerNameList} object. + * @throws IOException + */ + public static ServerNameList parse(InputStream input) throws IOException + { + int length = TlsUtils.readUint16(input); + if (length < 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + byte[] data = TlsUtils.readFully(length, input); + + ByteArrayInputStream buf = new ByteArrayInputStream(data); + + Vector server_name_list = new Vector(); + while (buf.available() > 0) + { + ServerName entry = ServerName.parse(buf); + server_name_list.addElement(entry); + } + + return new ServerNameList(server_name_list); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SessionParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SessionParameters.java new file mode 100644 index 0000000..68412f8 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SessionParameters.java @@ -0,0 +1,142 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.util.Arrays; + +public final class SessionParameters +{ + public static final class Builder + { + private int cipherSuite = -1; + private short compressionAlgorithm = -1; + private byte[] masterSecret = null; + private Certificate peerCertificate = null; + private byte[] encodedServerExtensions = null; + + public Builder() + { + } + + public SessionParameters build() + { + validate(this.cipherSuite >= 0, "cipherSuite"); + validate(this.compressionAlgorithm >= 0, "compressionAlgorithm"); + validate(this.masterSecret != null, "masterSecret"); + return new SessionParameters(cipherSuite, compressionAlgorithm, masterSecret, peerCertificate, + encodedServerExtensions); + } + + public Builder setCipherSuite(int cipherSuite) + { + this.cipherSuite = cipherSuite; + return this; + } + + public Builder setCompressionAlgorithm(short compressionAlgorithm) + { + this.compressionAlgorithm = compressionAlgorithm; + return this; + } + + public Builder setMasterSecret(byte[] masterSecret) + { + this.masterSecret = masterSecret; + return this; + } + + public Builder setPeerCertificate(Certificate peerCertificate) + { + this.peerCertificate = peerCertificate; + return this; + } + + public Builder setServerExtensions(Hashtable serverExtensions) + throws IOException + { + if (serverExtensions == null) + { + encodedServerExtensions = null; + } + else + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + TlsProtocol.writeExtensions(buf, serverExtensions); + encodedServerExtensions = buf.toByteArray(); + } + return this; + } + + private void validate(boolean condition, String parameter) + { + if (!condition) + { + throw new IllegalStateException("Required session parameter '" + parameter + "' not configured"); + } + } + } + + private int cipherSuite; + private short compressionAlgorithm; + private byte[] masterSecret; + private Certificate peerCertificate; + private byte[] encodedServerExtensions; + + private SessionParameters(int cipherSuite, short compressionAlgorithm, byte[] masterSecret, + Certificate peerCertificate, byte[] encodedServerExtensions) + { + this.cipherSuite = cipherSuite; + this.compressionAlgorithm = compressionAlgorithm; + this.masterSecret = Arrays.clone(masterSecret); + this.peerCertificate = peerCertificate; + this.encodedServerExtensions = encodedServerExtensions; + } + + public void clear() + { + if (this.masterSecret != null) + { + Arrays.fill(this.masterSecret, (byte)0); + } + } + + public SessionParameters copy() + { + return new SessionParameters(cipherSuite, compressionAlgorithm, masterSecret, peerCertificate, + encodedServerExtensions); + } + + public int getCipherSuite() + { + return cipherSuite; + } + + public short getCompressionAlgorithm() + { + return compressionAlgorithm; + } + + public byte[] getMasterSecret() + { + return masterSecret; + } + + public Certificate getPeerCertificate() + { + return peerCertificate; + } + + public Hashtable readServerExtensions() throws IOException + { + if (encodedServerExtensions == null) + { + return null; + } + + ByteArrayInputStream buf = new ByteArrayInputStream(encodedServerExtensions); + return TlsProtocol.readExtensions(buf); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java index 7ad4644..a5a591e 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java @@ -9,9 +9,8 @@ import java.io.OutputStream; */ public class SignatureAndHashAlgorithm { - - private short hash; - private short signature; + protected short hash; + protected short signature; /** * @param hash {@link HashAlgorithm} @@ -19,7 +18,6 @@ public class SignatureAndHashAlgorithm */ public SignatureAndHashAlgorithm(short hash, short signature) { - if (!TlsUtils.isValidUint8(hash)) { throw new IllegalArgumentException("'hash' should be a uint8"); @@ -65,7 +63,7 @@ public class SignatureAndHashAlgorithm public int hashCode() { - return (getHash() << 8) | getSignature(); + return (getHash() << 16) | getSignature(); } /** diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignerInputBuffer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignerInputBuffer.java new file mode 100644 index 0000000..8293135 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SignerInputBuffer.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayOutputStream; + +import org.bouncycastle.crypto.Signer; + +class SignerInputBuffer extends ByteArrayOutputStream +{ + void updateSigner(Signer s) + { + s.update(this.buf, 0, count); + } +}
\ No newline at end of file diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java index 5a71f9b..4080aaa 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/SupplementalDataEntry.java @@ -2,19 +2,18 @@ package org.bouncycastle.crypto.tls; public class SupplementalDataEntry { + protected int dataType; + protected byte[] data; - private int supp_data_type; - private byte[] data; - - public SupplementalDataEntry(int supp_data_type, byte[] data) + public SupplementalDataEntry(int dataType, byte[] data) { - this.supp_data_type = supp_data_type; + this.dataType = dataType; this.data = data; } public int getDataType() { - return supp_data_type; + return dataType; } public byte[] getData() diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java index dbf9d79..bb9306a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsAEADCipher.java @@ -10,7 +10,6 @@ import org.bouncycastle.util.Arrays; public class TlsAEADCipher implements TlsCipher { - protected TlsContext context; protected int macSize; protected int nonce_explicit_length; @@ -21,11 +20,9 @@ public class TlsAEADCipher protected byte[] encryptImplicitNonce, decryptImplicitNonce; public TlsAEADCipher(TlsContext context, AEADBlockCipher clientWriteCipher, AEADBlockCipher serverWriteCipher, - int cipherKeySize, int macSize) - throws IOException + int cipherKeySize, int macSize) throws IOException { - - if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion())) + if (!TlsUtils.isTLSv12(context)) { throw new TlsFatalAlert(AlertDescription.internal_error); } @@ -33,7 +30,7 @@ public class TlsAEADCipher this.context = context; this.macSize = macSize; - // NOTE: Valid for RFC 5288 ciphers but may need review for other AEAD ciphers + // 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 @@ -94,12 +91,11 @@ public class TlsAEADCipher public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) throws IOException { - byte[] nonce = new byte[this.encryptImplicitNonce.length + nonce_explicit_length]; System.arraycopy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.length); /* - * RFC 5288 The nonce_explicit MAY be the 64-bit sequence number. + * RFC 5288/6655 The nonce_explicit MAY be the 64-bit sequence number. * * (May need review for other AEAD ciphers). */ @@ -113,12 +109,13 @@ public class TlsAEADCipher System.arraycopy(nonce, encryptImplicitNonce.length, output, 0, nonce_explicit_length); int outputPos = nonce_explicit_length; - encryptCipher.init(true, - new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength))); + byte[] additionalData = getAdditionalData(seqNo, type, plaintextLength); + AEADParameters parameters = new AEADParameters(null, 8 * macSize, nonce, additionalData); - outputPos += encryptCipher.processBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos); try { + encryptCipher.init(true, parameters); + outputPos += encryptCipher.processBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos); outputPos += encryptCipher.doFinal(output, outputPos); } catch (Exception e) @@ -138,7 +135,6 @@ public class TlsAEADCipher public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) throws IOException { - if (getPlaintextLimit(len) < 0) { throw new TlsFatalAlert(AlertDescription.decode_error); @@ -155,13 +151,13 @@ public class TlsAEADCipher byte[] output = new byte[plaintextLength]; int outputPos = 0; - decryptCipher.init(false, - new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength))); - - outputPos += decryptCipher.processBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos); + 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 e) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java index 0b218c1..2f9e8a9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsBlockCipher.java @@ -16,6 +16,7 @@ import org.bouncycastle.util.Arrays; public class TlsBlockCipher implements TlsCipher { + private static boolean encryptThenMAC = false; protected TlsContext context; protected byte[] randomData; @@ -38,17 +39,14 @@ public class TlsBlockCipher } public TlsBlockCipher(TlsContext context, BlockCipher clientWriteCipher, BlockCipher serverWriteCipher, - Digest clientWriteDigest, Digest serverWriteDigest, int cipherKeySize) - throws IOException + Digest clientWriteDigest, Digest serverWriteDigest, int cipherKeySize) throws IOException { - this.context = context; this.randomData = new byte[256]; context.getSecureRandom().nextBytes(randomData); - this.useExplicitIV = ProtocolVersion.TLSv11.isEqualOrEarlierVersionOf(context.getServerVersion() - .getEquivalentTLSVersion()); + this.useExplicitIV = TlsUtils.isTLSv11(context); int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize() + serverWriteDigest.getDigestSize(); @@ -123,13 +121,30 @@ public class TlsBlockCipher int blockSize = encryptCipher.getBlockSize(); int macSize = writeMac.getSize(); - int result = ciphertextLimit - (ciphertextLimit % blockSize) - macSize - 1; + int plaintextLimit = ciphertextLimit; + + // An explicit IV consumes 1 block if (useExplicitIV) { - result -= blockSize; + plaintextLimit -= blockSize; } - return result; + // 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 byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) @@ -139,7 +154,13 @@ public class TlsBlockCipher ProtocolVersion version = context.getServerVersion(); - int padding_length = blockSize - 1 - ((len + macSize) % blockSize); + int enc_input_length = len; + if (!encryptThenMAC) + { + enc_input_length += macSize; + } + + int padding_length = blockSize - 1 - (enc_input_length % blockSize); // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though) if (!version.isDTLS() && !version.isSSL()) @@ -156,7 +177,7 @@ public class TlsBlockCipher totalSize += blockSize; } - byte[] outbuf = new byte[totalSize]; + byte[] outBuf = new byte[totalSize]; int outOff = 0; if (useExplicitIV) @@ -166,25 +187,42 @@ public class TlsBlockCipher encryptCipher.init(true, new ParametersWithIV(null, explicitIV)); - System.arraycopy(explicitIV, 0, outbuf, outOff, blockSize); + System.arraycopy(explicitIV, 0, outBuf, outOff, blockSize); outOff += blockSize; } - byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + int blocks_start = outOff; + + System.arraycopy(plaintext, offset, outBuf, outOff, len); + outOff += len; - System.arraycopy(plaintext, offset, outbuf, outOff, len); - System.arraycopy(mac, 0, outbuf, outOff + len, mac.length); + if (!encryptThenMAC) + { + byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + System.arraycopy(mac, 0, outBuf, outOff, mac.length); + outOff += mac.length; + } - int padOffset = outOff + len + mac.length; for (int i = 0; i <= padding_length; i++) { - outbuf[i + padOffset] = (byte)padding_length; + outBuf[outOff++] = (byte)padding_length; } - for (int i = outOff; i < totalSize; i += blockSize) + + for (int i = blocks_start; i < outOff; i += blockSize) { - encryptCipher.processBlock(outbuf, i, outbuf, i); + encryptCipher.processBlock(outBuf, i, outBuf, i); } - return outbuf; + + if (encryptThenMAC) + { + byte[] mac = writeMac.calculateMac(seqNo, type, outBuf, 0, outOff); + System.arraycopy(mac, 0, outBuf, outOff, mac.length); + outOff += mac.length; + } + +// assert outBuf.length == outOff; + + return outBuf; } public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) @@ -193,7 +231,16 @@ public class TlsBlockCipher int blockSize = decryptCipher.getBlockSize(); int macSize = readMac.getSize(); - int minLen = Math.max(blockSize, macSize + 1); + int minLen = blockSize; + if (encryptThenMAC) + { + minLen += macSize; + } + else + { + minLen = Math.max(minLen, macSize + 1); + } + if (useExplicitIV) { minLen += blockSize; @@ -204,41 +251,67 @@ public class TlsBlockCipher 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); } + if (encryptThenMAC) + { + int end = offset + len; + byte[] receivedMac = Arrays.copyOfRange(ciphertext, end - macSize, end); + byte[] calculatedMac = readMac.calculateMac(seqNo, type, ciphertext, offset, len - macSize); + + boolean 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; - len -= blockSize; + blocks_length -= blockSize; } - for (int i = 0; i < len; i += blockSize) + 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, len, blockSize, macSize); + int totalPad = checkPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMAC ? 0 : macSize); - int macInputLen = len - totalPad - macSize; + int dec_output_length = blocks_length - totalPad; - byte[] decryptedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + macInputLen + macSize); - byte[] calculatedMac = readMac.calculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, len - - 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 = readMac.calculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, + blocks_length - macSize, randomData); - boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, decryptedMac); + boolean 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.copyOfRange(ciphertext, offset, offset + macInputLen); + return Arrays.copyOfRange(ciphertext, offset, offset + dec_output_length); } protected int checkPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize) @@ -251,7 +324,7 @@ public class TlsBlockCipher int dummyIndex = 0; byte padDiff = 0; - if ((context.getServerVersion().isSSL() && totalPad > blockSize) || (macSize + totalPad > len)) + if ((TlsUtils.isSSL(context) && totalPad > blockSize) || (macSize + totalPad > len)) { totalPad = 0; } @@ -310,4 +383,4 @@ public class TlsBlockCipher } return n; } -}
\ No newline at end of file +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java index 62444fa..7db86cd 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClient.java @@ -7,9 +7,18 @@ import java.util.Vector; public interface TlsClient extends TlsPeer { - void init(TlsClientContext context); + /** + * Return the session this client wants to resume, if any. Note that the peer's certificate + * chain for the session (if any) may need to be periodically revalidated. + * + * @return A {@link TlsSession} representing the resumable session to be used for this + * connection, or null to use a new session. + * @see SessionParameters#getPeerCertificate() + */ + TlsSession getSessionToResume(); + ProtocolVersion getClientHelloRecordLayerVersion(); ProtocolVersion getClientVersion(); @@ -25,15 +34,18 @@ public interface TlsClient void notifyServerVersion(ProtocolVersion selectedVersion) throws IOException; + /** + * Notifies the client of the session_id sent in the ServerHello. + * + * @param sessionID + * @see {@link TlsContext#getResumableSession()} + */ void notifySessionID(byte[] sessionID); void notifySelectedCipherSuite(int selectedCipherSuite); void notifySelectedCompressionMethod(short selectedCompressionMethod); - void notifySecureRenegotiation(boolean secureNegotiation) - throws IOException; - // Hashtable is (Integer -> byte[]) void processServerExtensions(Hashtable serverExtensions) throws IOException; @@ -52,12 +64,6 @@ public interface TlsClient Vector getClientSupplementalData() throws IOException; - TlsCompression getCompression() - throws IOException; - - TlsCipher getCipher() - throws IOException; - /** * RFC 5077 3.3. NewSessionTicket Handshake Message * <p/> @@ -70,7 +76,4 @@ public interface TlsClient */ void notifyNewSessionTicket(NewSessionTicket newSessionTicket) throws IOException; - - void notifyHandshakeComplete() - throws IOException; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java index d91f7f8..b320144 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientContextImpl.java @@ -6,7 +6,6 @@ class TlsClientContextImpl extends AbstractTlsContext implements TlsClientContext { - TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) { super(secureRandom, securityParameters); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java index 33cd914..506560f 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsClientProtocol.java @@ -1,7 +1,6 @@ package org.bouncycastle.crypto.tls; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -16,19 +15,15 @@ import org.bouncycastle.util.Arrays; public class TlsClientProtocol extends TlsProtocol { - protected TlsClient tlsClient = null; protected TlsClientContextImpl tlsClientContext = null; - protected int[] offeredCipherSuites = null; - protected short[] offeredCompressionMethods = null; - protected Hashtable clientExtensions = null; - - protected int selectedCipherSuite; - protected short selectedCompressionMethod; + protected byte[] selectedSessionID = null; protected TlsKeyExchange keyExchange = null; protected TlsAuthentication authentication = null; + + protected CertificateStatus certificateStatus = null; protected CertificateRequest certificateRequest = null; private static SecureRandom createSecureRandom() @@ -61,11 +56,10 @@ public class TlsClientProtocol /** * Initiates a TLS handshake in the role of client * - * @param tlsClient + * @param tlsClient The {@link TlsClient} to use for the handshake. * @throws IOException If handshake was not successful. */ - public void connect(TlsClient tlsClient) - throws IOException + public void connect(TlsClient tlsClient) throws IOException { if (tlsClient == null) { @@ -73,7 +67,7 @@ public class TlsClientProtocol } if (this.tlsClient != null) { - throw new IllegalStateException("connect can only be called once"); + throw new IllegalStateException("'connect' can only be called once"); } this.tlsClient = tlsClient; @@ -86,12 +80,32 @@ public class TlsClientProtocol this.tlsClient.init(tlsClientContext); this.recordStream.init(tlsClientContext); + TlsSession sessionToResume = tlsClient.getSessionToResume(); + if (sessionToResume != null) + { + SessionParameters sessionParameters = sessionToResume.exportSessionParameters(); + if (sessionParameters != null) + { + this.tlsSession = sessionToResume; + this.sessionParameters = sessionParameters; + } + } + sendClientHelloMessage(); this.connection_state = CS_CLIENT_HELLO; completeHandshake(); + } - this.tlsClient.notifyHandshakeComplete(); + protected void cleanupHandshake() + { + super.cleanupHandshake(); + + this.selectedSessionID = null; + this.keyExchange = null; + this.authentication = null; + this.certificateStatus = null; + this.certificateRequest = null; } protected AbstractTlsContext getContext() @@ -104,36 +118,27 @@ public class TlsClientProtocol return tlsClient; } - protected void handleChangeCipherSpecMessage() + protected void handleHandshakeMessage(short type, byte[] data) throws IOException { + ByteArrayInputStream buf = new ByteArrayInputStream(data); - switch (this.connection_state) + if (this.resumedSession) { - case CS_CLIENT_FINISHED: - { - if (this.expectSessionTicket) + if (type != HandshakeType.finished || this.connection_state != CS_SERVER_HELLO) { - /* - * RFC 5077 3.3. This message MUST be sent if the server included a SessionTicket - * extension in the ServerHello. - */ - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } - // NB: Fall through to next case label - } - case CS_SERVER_SESSION_TICKET: - this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC; - break; - default: - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); - } - } - protected void handleHandshakeMessage(short type, byte[] data) - throws IOException - { - ByteArrayInputStream buf = new ByteArrayInputStream(data); + processFinishedMessage(buf); + this.connection_state = CS_SERVER_FINISHED; + + sendFinishedMessage(); + this.connection_state = CS_CLIENT_FINISHED; + this.connection_state = CS_END; + + return; + } switch (type) { @@ -150,72 +155,143 @@ public class TlsClientProtocol { // Parse the Certificate message and send to cipher suite - Certificate serverCertificate = Certificate.parse(buf); + this.peerCertificate = Certificate.parse(buf); assertEmpty(buf); - this.keyExchange.processServerCertificate(serverCertificate); + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (this.peerCertificate == null || this.peerCertificate.isEmpty()) + { + this.allowCertificateStatus = false; + } + + this.keyExchange.processServerCertificate(this.peerCertificate); this.authentication = tlsClient.getAuthentication(); - this.authentication.notifyServerCertificate(serverCertificate); + this.authentication.notifyServerCertificate(this.peerCertificate); break; } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } this.connection_state = CS_SERVER_CERTIFICATE; break; } + case HandshakeType.certificate_status: + { + switch (this.connection_state) + { + case CS_SERVER_CERTIFICATE: + { + if (!this.allowCertificateStatus) + { + /* + * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the + * server MUST have included an extension of type "status_request" with empty + * "extension_data" in the extended server hello.. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.certificateStatus = CertificateStatus.parse(buf); + + assertEmpty(buf); + + // TODO[RFC 3546] Figure out how to provide this to the client/authentication. + + this.connection_state = CS_CERTIFICATE_STATUS; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } case HandshakeType.finished: + { switch (this.connection_state) { - case CS_SERVER_CHANGE_CIPHER_SPEC: + case CS_CLIENT_FINISHED: + { processFinishedMessage(buf); this.connection_state = CS_SERVER_FINISHED; + this.connection_state = CS_END; break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; + } case HandshakeType.server_hello: + { switch (this.connection_state) { case CS_CLIENT_HELLO: + { receiveServerHelloMessage(buf); this.connection_state = CS_SERVER_HELLO; - securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite); - securityParameters.compressionAlgorithm = this.selectedCompressionMethod; + if (this.securityParameters.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + this.securityParameters.maxFragmentLength); + recordStream.setPlaintextLimit(plainTextLimit); + } + + this.securityParameters.prfAlgorithm = getPRFAlgorithm(getContext(), + this.securityParameters.getCipherSuite()); /* * 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. */ - securityParameters.verifyDataLength = 12; + this.securityParameters.verifyDataLength = 12; - recordStream.notifyHelloComplete(); + this.recordStream.notifyHelloComplete(); + + if (this.resumedSession) + { + this.securityParameters.masterSecret = Arrays.clone(this.sessionParameters.getMasterSecret()); + this.recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher()); + + sendChangeCipherSpecMessage(); + } + else + { + invalidateSession(); + + if (this.selectedSessionID.length > 0) + { + this.tlsSession = new TlsSessionImpl(this.selectedSessionID, null); + } + } break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; + } case HandshakeType.supplemental_data: { switch (this.connection_state) { case CS_SERVER_HELLO: + { handleSupplementalData(readSupplementalDataMessage(buf)); break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } case HandshakeType.server_hello_done: + { switch (this.connection_state) { case CS_SERVER_HELLO: @@ -225,7 +301,6 @@ public class TlsClientProtocol } case CS_SERVER_SUPPLEMENTAL_DATA: { - // There was no server certificate message; check it's OK this.keyExchange.skipServerCredentials(); this.authentication = null; @@ -233,19 +308,22 @@ public class TlsClientProtocol // NB: Fall through to next case label } case CS_SERVER_CERTIFICATE: - + case CS_CERTIFICATE_STATUS: + { // There was no server key exchange message; check it's OK this.keyExchange.skipServerKeyExchange(); // NB: Fall through to next case label - + } case CS_SERVER_KEY_EXCHANGE: case CS_CERTIFICATE_REQUEST: - + { assertEmpty(buf); this.connection_state = CS_SERVER_HELLO_DONE; + this.recordStream.getHandshakeHash().sealHashAlgorithms(); + Vector clientSupplementalData = tlsClient.getClientSupplementalData(); if (clientSupplementalData != null) { @@ -289,40 +367,56 @@ public class TlsClientProtocol * in our CipherSuite. */ sendClientKeyExchangeMessage(); + this.connection_state = CS_CLIENT_KEY_EXCHANGE; establishMasterSecret(getContext(), keyExchange); + recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher()); - /* - * Initialize our cipher suite - */ - recordStream.setPendingConnectionState(tlsClient.getCompression(), tlsClient.getCipher()); - - this.connection_state = CS_CLIENT_KEY_EXCHANGE; + TlsHandshakeHash prepareFinishHash = recordStream.prepareToFinish(); if (clientCreds != null && clientCreds instanceof TlsSignerCredentials) { + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)clientCreds; + /* - * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm - * prepended from TLS 1.2 + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 */ - TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds; - byte[] md5andsha1 = recordStream.getCurrentHash(null); - byte[] clientCertificateSignature = signerCreds.generateCertificateSignature(md5andsha1); - sendCertificateVerifyMessage(clientCertificateSignature); + SignatureAndHashAlgorithm signatureAndHashAlgorithm; + byte[] hash; + + if (TlsUtils.isTLSv12(getContext())) + { + signatureAndHashAlgorithm = signerCredentials.getSignatureAndHashAlgorithm(); + if (signatureAndHashAlgorithm == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + hash = prepareFinishHash.getFinalHash(signatureAndHashAlgorithm.getHash()); + } + else + { + signatureAndHashAlgorithm = null; + hash = getCurrentPRFHash(getContext(), prepareFinishHash, null); + } + + byte[] signature = signerCredentials.generateCertificateSignature(hash); + DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature); + sendCertificateVerifyMessage(certificateVerify); this.connection_state = CS_CERTIFICATE_VERIFY; } sendChangeCipherSpecMessage(); - this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC; - sendFinishedMessage(); this.connection_state = CS_CLIENT_FINISHED; break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + throw new TlsFatalAlert(AlertDescription.handshake_failure); } break; + } case HandshakeType.server_key_exchange: { switch (this.connection_state) @@ -334,7 +428,6 @@ public class TlsClientProtocol } case CS_SERVER_SUPPLEMENTAL_DATA: { - // There was no server certificate message; check it's OK this.keyExchange.skipServerCredentials(); this.authentication = null; @@ -342,14 +435,15 @@ public class TlsClientProtocol // NB: Fall through to next case label } case CS_SERVER_CERTIFICATE: - + case CS_CERTIFICATE_STATUS: + { this.keyExchange.processServerKeyExchange(buf); assertEmpty(buf); break; - + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } this.connection_state = CS_SERVER_KEY_EXCHANGE; @@ -360,12 +454,13 @@ public class TlsClientProtocol switch (this.connection_state) { case CS_SERVER_CERTIFICATE: - + case CS_CERTIFICATE_STATUS: + { // There was no server key exchange message; check it's OK this.keyExchange.skipServerKeyExchange(); // NB: Fall through to next case label - + } case CS_SERVER_KEY_EXCHANGE: { if (this.authentication == null) @@ -374,19 +469,26 @@ public class TlsClientProtocol * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server * to request client identification. */ - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + throw new TlsFatalAlert(AlertDescription.handshake_failure); } - this.certificateRequest = CertificateRequest.parse(buf); + this.certificateRequest = CertificateRequest.parse(getContext(), buf); assertEmpty(buf); this.keyExchange.validateCertificateRequest(this.certificateRequest); + /* + * TODO Give the client a chance to immediately select the CertificateVerify hash + * algorithm here to avoid tracking the other hash algorithms unnecessarily? + */ + TlsUtils.trackHashAlgorithms(this.recordStream.getHandshakeHash(), + this.certificateRequest.getSupportedSignatureAlgorithms()); + break; } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } this.connection_state = CS_CERTIFICATE_REQUEST; @@ -397,23 +499,32 @@ public class TlsClientProtocol switch (this.connection_state) { case CS_CLIENT_FINISHED: + { if (!this.expectSessionTicket) { /* * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a * SessionTicket extension in the ServerHello. */ - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } + + /* + * RFC 5077 3.4. If the client receives a session ticket from the server, then it + * discards any Session ID that was sent in the ServerHello. + */ + invalidateSession(); + receiveNewSessionTicketMessage(buf); this.connection_state = CS_SERVER_SESSION_TICKET; break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } } case HandshakeType.hello_request: - + { assertEmpty(buf); /* @@ -422,27 +533,34 @@ public class TlsClientProtocol * if it does not wish to renegotiate a session, or the client may, if it wishes, * respond with a no_renegotiation alert. */ - if (this.connection_state == CS_SERVER_FINISHED) + if (this.connection_state == CS_END) { + /* + * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal + * handshake_failure alert. + */ + if (TlsUtils.isSSL(getContext())) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + String message = "Renegotiation not supported"; raiseWarning(AlertDescription.no_renegotiation, message); } break; + } + case HandshakeType.client_hello: case HandshakeType.client_key_exchange: case HandshakeType.certificate_verify: - case HandshakeType.client_hello: case HandshakeType.hello_verify_request: default: - // We do not support this! - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - break; + throw new TlsFatalAlert(AlertDescription.unexpected_message); } } protected void handleSupplementalData(Vector serverSupplementalData) throws IOException { - this.tlsClient.processServerSupplementalData(serverSupplementalData); this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA; @@ -453,7 +571,6 @@ public class TlsClientProtocol protected void receiveNewSessionTicketMessage(ByteArrayInputStream buf) throws IOException { - NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf); TlsProtocol.assertEmpty(buf); @@ -464,23 +581,22 @@ public class TlsClientProtocol protected void receiveServerHelloMessage(ByteArrayInputStream buf) throws IOException { - ProtocolVersion server_version = TlsUtils.readVersion(buf); if (server_version.isDTLS()) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } // Check that this matches what the server is sending in the record layer - if (!server_version.equals(recordStream.getReadVersion())) + if (!server_version.equals(this.recordStream.getReadVersion())) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } ProtocolVersion client_version = getContext().getClientVersion(); if (!server_version.isEqualOrEarlierVersionOf(client_version)) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } this.recordStream.setWriteVersion(server_version); @@ -490,38 +606,41 @@ public class TlsClientProtocol /* * Read the server random */ - securityParameters.serverRandom = TlsUtils.readFully(32, buf); + this.securityParameters.serverRandom = TlsUtils.readFully(32, buf); - byte[] sessionID = TlsUtils.readOpaque8(buf); - if (sessionID.length > 32) + this.selectedSessionID = TlsUtils.readOpaque8(buf); + if (this.selectedSessionID.length > 32) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - this.tlsClient.notifySessionID(sessionID); + this.tlsClient.notifySessionID(this.selectedSessionID); + + this.resumedSession = this.selectedSessionID.length > 0 && this.tlsSession != null + && Arrays.areEqual(this.selectedSessionID, this.tlsSession.getSessionID()); /* * Find out which CipherSuite the server has chosen and check that it was one of the offered * ones. */ - this.selectedCipherSuite = TlsUtils.readUint16(buf); - if (!arrayContains(offeredCipherSuites, this.selectedCipherSuite) - || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL - || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + int selectedCipherSuite = TlsUtils.readUint16(buf); + if (!Arrays.contains(this.offeredCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - this.tlsClient.notifySelectedCipherSuite(this.selectedCipherSuite); + this.tlsClient.notifySelectedCipherSuite(selectedCipherSuite); /* * Find out which CompressionMethod the server has chosen and check that it was one of the * offered ones. */ short selectedCompressionMethod = TlsUtils.readUint8(buf); - if (!arrayContains(offeredCompressionMethods, selectedCompressionMethod)) + if (!Arrays.contains(this.offeredCompressionMethods, selectedCompressionMethod)) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } this.tlsClient.notifySelectedCompressionMethod(selectedCompressionMethod); @@ -534,15 +653,7 @@ public class TlsClientProtocol * possibility that the extended server hello message could "break" existing TLS 1.0 * clients. */ - - /* - * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore - * extensions appearing in the client hello, and send a server hello containing no - * extensions. - */ - - // Integer -> byte[] - Hashtable serverExtensions = readExtensions(buf); + this.serverExtensions = readExtensions(buf); /* * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an @@ -551,9 +662,9 @@ public class TlsClientProtocol * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server * Hello is always allowed. */ - if (serverExtensions != null) + if (this.serverExtensions != null) { - Enumeration e = serverExtensions.keys(); + Enumeration e = this.serverExtensions.keys(); while (e.hasMoreElements()) { Integer extType = (Integer)e.nextElement(); @@ -565,107 +676,169 @@ public class TlsClientProtocol * only allowed because the client is signaling its willingness to receive the * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. */ - if (!extType.equals(EXT_RenegotiationInfo) - && (clientExtensions == null || clientExtensions.get(extType) == null)) + if (extType.equals(EXT_RenegotiationInfo)) { - /* - * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless - * the same extension type appeared in the corresponding ClientHello. If a - * client receives an extension type in ServerHello that it did not request in - * the associated ClientHello, it MUST abort the handshake with an - * unsupported_extension fatal alert. - */ - this.failWithError(AlertLevel.fatal, AlertDescription.unsupported_extension); + continue; + } + + /* + * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions[.] + */ + if (this.resumedSession) + { + // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats + // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats + // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats +// throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + /* + * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the + * same extension type appeared in the corresponding ClientHello. If a client + * receives an extension type in ServerHello that it did not request in the + * associated ClientHello, it MUST abort the handshake with an unsupported_extension + * fatal alert. + */ + if (null == TlsUtils.getExtensionData(this.clientExtensions, extType)) + { + throw new TlsFatalAlert(AlertDescription.unsupported_extension); } } + } + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { /* - * RFC 5746 3.4. Client Behavior: Initial Handshake + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: */ + byte[] renegExtData = TlsUtils.getExtensionData(this.serverExtensions, EXT_RenegotiationInfo); + if (renegExtData != null) { /* - * When a ServerHello is received, the client MUST check if it includes the - * "renegotiation_info" extension: + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by sending a fatal + * handshake_failure alert). */ - byte[] renegExtValue = (byte[])serverExtensions.get(EXT_RenegotiationInfo); - if (renegExtValue != null) - { - /* - * If the extension is present, set the secure_renegotiation flag to TRUE. The - * client MUST then verify that the length of the "renegotiated_connection" - * field is zero, and if it is not, MUST abort the handshake (by sending a fatal - * handshake_failure alert). - */ - this.secure_renegotiation = true; + this.secure_renegotiation = true; - if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) - { - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); - } + if (!Arrays.constantTimeAreEqual(renegExtData, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); } } + } + + // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming + this.tlsClient.notifySecureRenegotiation(this.secure_renegotiation); - this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket); + Hashtable sessionClientExtensions = clientExtensions, sessionServerExtensions = serverExtensions; + if (this.resumedSession) + { + if (selectedCipherSuite != this.sessionParameters.getCipherSuite() + || selectedCompressionMethod != this.sessionParameters.getCompressionAlgorithm()) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + sessionClientExtensions = null; + sessionServerExtensions = this.sessionParameters.readServerExtensions(); } - tlsClient.notifySecureRenegotiation(this.secure_renegotiation); + this.securityParameters.cipherSuite = selectedCipherSuite; + this.securityParameters.compressionAlgorithm = selectedCompressionMethod; - if (clientExtensions != null) + if (sessionServerExtensions != null) + { + this.securityParameters.maxFragmentLength = processMaxFragmentLengthExtension(sessionClientExtensions, + sessionServerExtensions, AlertDescription.illegal_parameter); + + this.securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(sessionServerExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + this.allowCertificateStatus = !this.resumedSession + && TlsUtils.hasExpectedEmptyExtensionData(sessionServerExtensions, + TlsExtensionsUtils.EXT_status_request, AlertDescription.illegal_parameter); + + this.expectSessionTicket = !this.resumedSession + && TlsUtils.hasExpectedEmptyExtensionData(sessionServerExtensions, TlsProtocol.EXT_SessionTicket, + AlertDescription.illegal_parameter); + } + + if (sessionClientExtensions != null) { - tlsClient.processServerExtensions(serverExtensions); + this.tlsClient.processServerExtensions(sessionServerExtensions); } } - protected void sendCertificateVerifyMessage(byte[] data) + protected void sendCertificateVerifyMessage(DigitallySigned certificateVerify) throws IOException { - /* - * Send signature of handshake messages so far to prove we are the owner of the cert See RFC - * 2246 sections 4.7, 7.4.3 and 7.4.8 - */ - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.certificate_verify, bos); - TlsUtils.writeUint24(data.length + 2, bos); - TlsUtils.writeOpaque16(data, bos); - byte[] message = bos.toByteArray(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_verify); + + certificateVerify.encode(message); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected void sendClientHelloMessage() throws IOException { - - recordStream.setWriteVersion(this.tlsClient.getClientHelloRecordLayerVersion()); - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.client_hello, buf); - - // Reserve space for length - TlsUtils.writeUint24(0, buf); + this.recordStream.setWriteVersion(this.tlsClient.getClientHelloRecordLayerVersion()); ProtocolVersion client_version = this.tlsClient.getClientVersion(); if (client_version.isDTLS()) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); } getContext().setClientVersion(client_version); - TlsUtils.writeVersion(client_version, buf); - - buf.write(securityParameters.clientRandom); - - // Session id - TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); /* - * Cipher suites + * TODO RFC 5077 3.4. When presenting a ticket, the client MAY generate and include a + * Session ID in the TLS ClientHello. */ + byte[] session_id = TlsUtils.EMPTY_BYTES; + if (this.tlsSession != null) + { + session_id = this.tlsSession.getSessionID(); + if (session_id == null || session_id.length > 32) + { + session_id = TlsUtils.EMPTY_BYTES; + } + } + this.offeredCipherSuites = this.tlsClient.getCipherSuites(); - // Integer -> byte[] + this.offeredCompressionMethods = this.tlsClient.getCompressionMethods(); + + if (session_id.length > 0 && this.sessionParameters != null) + { + if (!Arrays.contains(this.offeredCipherSuites, sessionParameters.getCipherSuite()) + || !Arrays.contains(this.offeredCompressionMethods, sessionParameters.getCompressionAlgorithm())) + { + session_id = TlsUtils.EMPTY_BYTES; + } + } + this.clientExtensions = this.tlsClient.getClientExtensions(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.client_hello); + + TlsUtils.writeVersion(client_version, message); + + message.write(this.securityParameters.getClientRandom()); + + TlsUtils.writeOpaque8(session_id, message); + // Cipher Suites (and SCSV) { /* @@ -673,60 +846,39 @@ public class TlsClientProtocol * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the * ClientHello. Including both is NOT RECOMMENDED. */ - boolean noRenegExt = clientExtensions == null || clientExtensions.get(EXT_RenegotiationInfo) == null; + byte[] renegExtData = TlsUtils.getExtensionData(clientExtensions, EXT_RenegotiationInfo); + boolean noRenegExt = (null == renegExtData); - int count = offeredCipherSuites.length; - if (noRenegExt) - { - // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV - ++count; - } + boolean noSCSV = !Arrays.contains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); - TlsUtils.writeUint16(2 * count, buf); - TlsUtils.writeUint16Array(offeredCipherSuites, buf); - - if (noRenegExt) + if (noRenegExt && noSCSV) { - TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf); + // TODO Consider whether to default to a client extension instead +// this.clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.clientExtensions); +// this.clientExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)); + this.offeredCipherSuites = Arrays.append(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); } - } - // Compression methods - this.offeredCompressionMethods = this.tlsClient.getCompressionMethods(); + TlsUtils.writeUint16ArrayWithUint16Length(offeredCipherSuites, message); + } - TlsUtils.writeUint8((short)offeredCompressionMethods.length, buf); - TlsUtils.writeUint8Array(offeredCompressionMethods, buf); + TlsUtils.writeUint8ArrayWithUint8Length(offeredCompressionMethods, message); - // Extensions if (clientExtensions != null) { - writeExtensions(buf, clientExtensions); + writeExtensions(message, clientExtensions); } - byte[] message = buf.toByteArray(); - - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); - - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected void sendClientKeyExchangeMessage() throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - TlsUtils.writeUint8(HandshakeType.client_key_exchange, bos); - - // Reserve space for length - TlsUtils.writeUint24(0, bos); - - this.keyExchange.generateClientKeyExchange(bos); - byte[] message = bos.toByteArray(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.client_key_exchange); - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); + this.keyExchange.generateClientKeyExchange(message); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java index dfb1052..04781ef 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsContext.java @@ -4,7 +4,6 @@ import java.security.SecureRandom; public interface TlsContext { - SecureRandom getSecureRandom(); SecurityParameters getSecurityParameters(); @@ -15,6 +14,16 @@ public interface TlsContext ProtocolVersion getServerVersion(); + /** + * Used to get the resumable session, if any, used by this connection. Only available after the + * handshake has successfully completed. + * + * @return A {@link TlsSession} representing the resumable session used by this connection, or + * null if no resumable session available. + * @see {@link TlsPeer#notifyHandshakeComplete()} + */ + TlsSession getResumableSession(); + Object getUserObject(); void setUserObject(Object userObject); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java index 5737659..0abaee6 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java @@ -1,24 +1,17 @@ package org.bouncycastle.crypto.tls; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.math.BigInteger; import java.util.Vector; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Signer; -import org.bouncycastle.crypto.generators.DHKeyPairGenerator; -import org.bouncycastle.crypto.io.SignerInputStream; -import org.bouncycastle.crypto.params.DHKeyGenerationParameters; import org.bouncycastle.crypto.params.DHParameters; -import org.bouncycastle.crypto.params.DHPublicKeyParameters; +import org.bouncycastle.util.io.TeeInputStream; public class TlsDHEKeyExchange extends TlsDHKeyExchange { - protected TlsSignerCredentials serverCredentials = null; public TlsDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters) @@ -29,7 +22,6 @@ public class TlsDHEKeyExchange public void processServerCredentials(TlsCredentials serverCredentials) throws IOException { - if (!(serverCredentials instanceof TlsSignerCredentials)) { throw new TlsFatalAlert(AlertDescription.internal_error); @@ -43,40 +35,50 @@ public class TlsDHEKeyExchange public byte[] generateServerKeyExchange() throws IOException { - if (this.dhParameters == null) { throw new TlsFatalAlert(AlertDescription.internal_error); } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DigestInputBuffer buf = new DigestInputBuffer(); - DHKeyPairGenerator kpg = new DHKeyPairGenerator(); - kpg.init(new DHKeyGenerationParameters(context.getSecureRandom(), this.dhParameters)); - AsymmetricCipherKeyPair kp = kpg.generateKeyPair(); + this.dhAgreeServerPrivateKey = TlsDHUtils.generateEphemeralServerKeyExchange(context.getSecureRandom(), + this.dhParameters, buf); - BigInteger Ys = ((DHPublicKeyParameters)kp.getPublic()).getY(); + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm; + Digest d; - TlsDHUtils.writeDHParameter(dhParameters.getP(), buf); - TlsDHUtils.writeDHParameter(dhParameters.getG(), buf); - TlsDHUtils.writeDHParameter(Ys, buf); + if (TlsUtils.isTLSv12(context)) + { + signatureAndHashAlgorithm = serverCredentials.getSignatureAndHashAlgorithm(); + if (signatureAndHashAlgorithm == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } - byte[] digestInput = buf.toByteArray(); + d = TlsUtils.createHash(signatureAndHashAlgorithm.getHash()); + } + else + { + signatureAndHashAlgorithm = null; + d = new CombinedHash(); + } - Digest d = new CombinedHash(); SecurityParameters securityParameters = context.getSecurityParameters(); d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); - d.update(digestInput, 0, digestInput.length); + buf.updateDigest(d); byte[] hash = new byte[d.getDigestSize()]; d.doFinal(hash, 0); - byte[] sigBytes = serverCredentials.generateCertificateSignature(hash); - /* - * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended from TLS 1.2 - */ - TlsUtils.writeOpaque16(sigBytes, buf); + byte[] signature = serverCredentials.generateCertificateSignature(hash); + + DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature); + signed_params.encode(buf); return buf.toByteArray(); } @@ -84,28 +86,28 @@ public class TlsDHEKeyExchange public void processServerKeyExchange(InputStream input) throws IOException { - SecurityParameters securityParameters = context.getSecurityParameters(); - Signer signer = initVerifyer(tlsSigner, securityParameters); - InputStream sigIn = new SignerInputStream(input, signer); + SignerInputBuffer buf = new SignerInputBuffer(); + InputStream teeIn = new TeeInputStream(input, buf); + + ServerDHParams params = ServerDHParams.parse(teeIn); - BigInteger p = TlsDHUtils.readDHParameter(sigIn); - BigInteger g = TlsDHUtils.readDHParameter(sigIn); - BigInteger Ys = TlsDHUtils.readDHParameter(sigIn); + DigitallySigned signed_params = DigitallySigned.parse(context, input); - byte[] sigBytes = TlsUtils.readOpaque16(input); - if (!signer.verifySignature(sigBytes)) + Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters); + buf.updateSigner(signer); + if (!signer.verifySignature(signed_params.getSignature())) { throw new TlsFatalAlert(AlertDescription.decrypt_error); } - this.dhAgreeServerPublicKey = validateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey(params.getPublicKey()); } - protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters) { - Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey); signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); return signer; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java index 60e5105..7d79f6a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java @@ -7,7 +7,6 @@ import java.util.Vector; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.DHParameters; import org.bouncycastle.crypto.params.DHPrivateKeyParameters; @@ -20,7 +19,6 @@ import org.bouncycastle.crypto.util.PublicKeyFactory; public class TlsDHKeyExchange extends AbstractTlsKeyExchange { - protected static final BigInteger ONE = BigInteger.valueOf(1); protected static final BigInteger TWO = BigInteger.valueOf(2); @@ -32,11 +30,11 @@ public class TlsDHKeyExchange protected TlsAgreementCredentials agreementCredentials; protected DHPrivateKeyParameters dhAgreeClientPrivateKey; + protected DHPrivateKeyParameters dhAgreeServerPrivateKey; protected DHPublicKeyParameters dhAgreeClientPublicKey; public TlsDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters) { - super(keyExchange, supportedSignatureAlgorithms); switch (keyExchange) @@ -77,7 +75,6 @@ public class TlsDHKeyExchange public void processServerCertificate(Certificate serverCertificate) throws IOException { - if (serverCertificate.isEmpty()) { throw new TlsFatalAlert(AlertDescription.bad_certificate); @@ -99,7 +96,7 @@ public class TlsDHKeyExchange { try { - this.dhAgreeServerPublicKey = validateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey); + this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey); } catch (ClassCastException e) { @@ -196,27 +193,16 @@ public class TlsDHKeyExchange return agreementCredentials.generateAgreement(dhAgreeServerPublicKey); } - return calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); - } - - protected boolean areCompatibleParameters(DHParameters a, DHParameters b) - { - return a.getP().equals(b.getP()) && a.getG().equals(b.getG()); - } - - protected byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, DHPrivateKeyParameters privateKey) - { - return TlsDHUtils.calculateDHBasicAgreement(publicKey, privateKey); - } + if (dhAgreeServerPrivateKey != null) + { + return TlsDHUtils.calculateDHBasicAgreement(dhAgreeClientPublicKey, dhAgreeServerPrivateKey); + } - protected AsymmetricCipherKeyPair generateDHKeyPair(DHParameters dhParams) - { - return TlsDHUtils.generateDHKeyPair(context.getSecureRandom(), dhParams); - } + if (dhAgreeClientPrivateKey != null) + { + return TlsDHUtils.calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + } - protected DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) - throws IOException - { - return TlsDHUtils.validateDHPublicKey(key); + throw new TlsFatalAlert(AlertDescription.internal_error); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java index 014e40f..748c879 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDHUtils.java @@ -17,14 +17,16 @@ import org.bouncycastle.util.BigIntegers; public class TlsDHUtils { - static final BigInteger ONE = BigInteger.valueOf(1); static final BigInteger TWO = BigInteger.valueOf(2); - public static byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, - DHPrivateKeyParameters privateKey) + public static boolean areCompatibleParameters(DHParameters a, DHParameters b) { + return a.getP().equals(b.getP()) && a.getG().equals(b.getG()); + } + public static byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, DHPrivateKeyParameters privateKey) + { DHBasicAgreement basicAgreement = new DHBasicAgreement(); basicAgreement.init(privateKey); BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey); @@ -36,32 +38,37 @@ public class TlsDHUtils return BigIntegers.asUnsignedByteArray(agreementValue); } - public static AsymmetricCipherKeyPair generateDHKeyPair(SecureRandom random, - DHParameters dhParams) + public static AsymmetricCipherKeyPair generateDHKeyPair(SecureRandom random, DHParameters dhParams) { DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); dhGen.init(new DHKeyGenerationParameters(random, dhParams)); return dhGen.generateKeyPair(); } - public static DHPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, - DHParameters dhParams, OutputStream output) - throws IOException + public static DHPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, DHParameters dhParams, + OutputStream output) throws IOException { + AsymmetricCipherKeyPair kp = generateDHKeyPair(random, dhParams); + + DHPublicKeyParameters dh_public = (DHPublicKeyParameters) kp.getPublic(); + writeDHParameter(dh_public.getY(), output); - AsymmetricCipherKeyPair dhAgreeClientKeyPair = generateDHKeyPair(random, dhParams); - DHPrivateKeyParameters dhAgreeClientPrivateKey = (DHPrivateKeyParameters)dhAgreeClientKeyPair - .getPrivate(); + return (DHPrivateKeyParameters) kp.getPrivate(); + } + + public static DHPrivateKeyParameters generateEphemeralServerKeyExchange(SecureRandom random, DHParameters dhParams, + OutputStream output) throws IOException + { + AsymmetricCipherKeyPair kp = TlsDHUtils.generateDHKeyPair(random, dhParams); - BigInteger Yc = ((DHPublicKeyParameters)dhAgreeClientKeyPair.getPublic()).getY(); - byte[] keData = BigIntegers.asUnsignedByteArray(Yc); - TlsUtils.writeOpaque16(keData, output); + DHPublicKeyParameters dhPublicKey = (DHPublicKeyParameters)kp.getPublic(); + ServerDHParams params = new ServerDHParams(dhPublicKey); + params.encode(output); - return dhAgreeClientPrivateKey; + return (DHPrivateKeyParameters)kp.getPrivate(); } - public static DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) - throws IOException + public static DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) throws IOException { BigInteger Y = key.getY(); DHParameters params = key.getParameters(); @@ -86,14 +93,12 @@ public class TlsDHUtils return key; } - public static BigInteger readDHParameter(InputStream input) - throws IOException + public static BigInteger readDHParameter(InputStream input) throws IOException { return new BigInteger(1, TlsUtils.readOpaque16(input)); } - public static void writeDHParameter(BigInteger x, OutputStream output) - throws IOException + public static void writeDHParameter(BigInteger x, OutputStream output) throws IOException { TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(x), output); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java index b0e8957..4cb8004 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSASigner.java @@ -6,7 +6,6 @@ import org.bouncycastle.crypto.DSA; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Signer; import org.bouncycastle.crypto.digests.NullDigest; -import org.bouncycastle.crypto.digests.SHA1Digest; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.signers.DSADigestSigner; @@ -14,44 +13,73 @@ import org.bouncycastle.crypto.signers.DSADigestSigner; public abstract class TlsDSASigner extends AbstractTlsSigner { - - public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + public byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash) throws CryptoException { - - // Note: Only use the SHA1 part of the hash - Signer signer = makeSigner(new NullDigest(), true, + Signer signer = makeSigner(algorithm, true, true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); - signer.update(md5AndSha1, 16, 20); + if (algorithm == null) + { + // Note: Only use the SHA1 part of the (MD5/SHA1) hash + signer.update(hash, 16, 20); + } + else + { + signer.update(hash, 0, hash.length); + } return signer.generateSignature(); } - public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + public boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash) throws CryptoException { - - // Note: Only use the SHA1 part of the hash - Signer signer = makeSigner(new NullDigest(), false, publicKey); - signer.update(md5AndSha1, 16, 20); + Signer signer = makeSigner(algorithm, true, false, publicKey); + if (algorithm == null) + { + // Note: Only use the SHA1 part of the (MD5/SHA1) hash + signer.update(hash, 16, 20); + } + else + { + signer.update(hash, 0, hash.length); + } return signer.verifySignature(sigBytes); } - public Signer createSigner(AsymmetricKeyParameter privateKey) + public Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey) { - return makeSigner(new SHA1Digest(), true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + return makeSigner(algorithm, false, true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); } - public Signer createVerifyer(AsymmetricKeyParameter publicKey) + public Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey) { - return makeSigner(new SHA1Digest(), false, publicKey); + return makeSigner(algorithm, false, false, publicKey); } - protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp) + protected Signer makeSigner(SignatureAndHashAlgorithm algorithm, boolean raw, boolean forSigning, + CipherParameters cp) { + if ((algorithm != null) != TlsUtils.isTLSv12(context)) + { + throw new IllegalStateException(); + } + + if (algorithm != null + && (algorithm.getHash() != HashAlgorithm.sha1 || algorithm.getSignature() != getSignatureAlgorithm())) + { + throw new IllegalStateException(); + } + + Digest d = raw ? new NullDigest() : TlsUtils.createHash(HashAlgorithm.sha1); + Signer s = new DSADigestSigner(createDSAImpl(), d); s.init(forSigning, cp); return s; } + protected abstract short getSignatureAlgorithm(); + protected abstract DSA createDSAImpl(); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java index e0eeca9..cb698bf 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsDSSSigner.java @@ -8,7 +8,6 @@ import org.bouncycastle.crypto.signers.DSASigner; public class TlsDSSSigner extends TlsDSASigner { - public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) { return publicKey instanceof DSAPublicKeyParameters; @@ -18,4 +17,9 @@ public class TlsDSSSigner { return new DSASigner(); } + + protected short getSignatureAlgorithm() + { + return SignatureAlgorithm.dsa; + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java index a49f83f..3e7ef39 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECCUtils.java @@ -1,7 +1,6 @@ package org.bouncycastle.crypto.tls; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -9,7 +8,7 @@ import java.math.BigInteger; import java.security.SecureRandom; import java.util.Hashtable; -import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; @@ -19,91 +18,63 @@ import org.bouncycastle.crypto.params.ECKeyGenerationParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECFieldElement; import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.Integers; public class TlsECCUtils { - public static final Integer EXT_elliptic_curves = Integers.valueOf(ExtensionType.elliptic_curves); public static final Integer EXT_ec_point_formats = Integers.valueOf(ExtensionType.ec_point_formats); - private static final String[] curveNames = new String[]{"sect163k1", "sect163r1", "sect163r2", "sect193r1", + private static final String[] curveNames = new String[] { "sect163k1", "sect163r1", "sect163r2", "sect193r1", "sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1", "sect571k1", "sect571r1", "secp160k1", "secp160r1", "secp160r2", "secp192k1", "secp192r1", "secp224k1", - "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1",}; + "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1", + "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1"}; - public static void addSupportedEllipticCurvesExtension(Hashtable extensions, int[] namedCurves) - throws IOException + public static void addSupportedEllipticCurvesExtension(Hashtable extensions, int[] namedCurves) throws IOException { - extensions.put(EXT_elliptic_curves, createSupportedEllipticCurvesExtension(namedCurves)); } public static void addSupportedPointFormatsExtension(Hashtable extensions, short[] ecPointFormats) throws IOException { - extensions.put(EXT_ec_point_formats, createSupportedPointFormatsExtension(ecPointFormats)); } - public static int[] getSupportedEllipticCurvesExtension(Hashtable extensions) - throws IOException + public static int[] getSupportedEllipticCurvesExtension(Hashtable extensions) throws IOException { - - if (extensions == null) - { - return null; - } - byte[] extensionValue = (byte[])extensions.get(EXT_elliptic_curves); - if (extensionValue == null) - { - return null; - } - return readSupportedEllipticCurvesExtension(extensionValue); + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_elliptic_curves); + return extensionData == null ? null : readSupportedEllipticCurvesExtension(extensionData); } - public static short[] getSupportedPointFormatsExtension(Hashtable extensions) - throws IOException + public static short[] getSupportedPointFormatsExtension(Hashtable extensions) throws IOException { - - if (extensions == null) - { - return null; - } - byte[] extensionValue = (byte[])extensions.get(EXT_ec_point_formats); - if (extensionValue == null) - { - return null; - } - return readSupportedPointFormatsExtension(extensionValue); + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_ec_point_formats); + return extensionData == null ? null : readSupportedPointFormatsExtension(extensionData); } - public static byte[] createSupportedEllipticCurvesExtension(int[] namedCurves) - throws IOException + public static byte[] createSupportedEllipticCurvesExtension(int[] namedCurves) throws IOException { - if (namedCurves == null || namedCurves.length < 1) { throw new TlsFatalAlert(AlertDescription.internal_error); } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint16(2 * namedCurves.length, buf); - TlsUtils.writeUint16Array(namedCurves, buf); - return buf.toByteArray(); + return TlsUtils.encodeUint16ArrayWithUint16Length(namedCurves); } - public static byte[] createSupportedPointFormatsExtension(short[] ecPointFormats) - throws IOException + public static byte[] createSupportedPointFormatsExtension(short[] ecPointFormats) throws IOException { - if (ecPointFormats == null) { - ecPointFormats = new short[]{ECPointFormat.uncompressed}; + ecPointFormats = new short[] { ECPointFormat.uncompressed }; } - else if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed)) + else if (!Arrays.contains(ecPointFormats, ECPointFormat.uncompressed)) { /* * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST @@ -118,22 +89,17 @@ public class TlsECCUtils ecPointFormats = tmp; } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8((short)ecPointFormats.length, buf); - TlsUtils.writeUint8Array(ecPointFormats, buf); - return buf.toByteArray(); + return TlsUtils.encodeUint8ArrayWithUint8Length(ecPointFormats); } - public static int[] readSupportedEllipticCurvesExtension(byte[] extensionValue) - throws IOException + public static int[] readSupportedEllipticCurvesExtension(byte[] extensionData) throws IOException { - - if (extensionValue == null) + if (extensionData == null) { - throw new IllegalArgumentException("'extensionValue' cannot be null"); + throw new IllegalArgumentException("'extensionData' cannot be null"); } - ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); int length = TlsUtils.readUint16(buf); if (length < 2 || (length & 1) != 0) @@ -148,16 +114,14 @@ public class TlsECCUtils return namedCurves; } - public static short[] readSupportedPointFormatsExtension(byte[] extensionValue) - throws IOException + public static short[] readSupportedPointFormatsExtension(byte[] extensionData) throws IOException { - - if (extensionValue == null) + if (extensionData == null) { - throw new IllegalArgumentException("'extensionValue' cannot be null"); + throw new IllegalArgumentException("'extensionData' cannot be null"); } - ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); short length = TlsUtils.readUint8(buf); if (length < 1) @@ -169,7 +133,7 @@ public class TlsECCUtils TlsProtocol.assertEmpty(buf); - if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed)) + if (!Arrays.contains(ecPointFormats, ECPointFormat.uncompressed)) { /* * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST @@ -195,7 +159,7 @@ public class TlsECCUtils } // Lazily created the first time a particular curve is accessed - X9ECParameters ecP = SECNamedCurves.getByName(curveName); + X9ECParameters ecP = ECNamedCurveTable.getByName(curveName); if (ecP == null) { @@ -227,6 +191,9 @@ public class TlsECCUtils { switch (cipherSuite) { + /* + * RFC 4492 + */ case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: @@ -252,6 +219,10 @@ public class TlsECCUtils case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + + /* + * RFC 5289 + */ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: @@ -268,7 +239,38 @@ public class TlsECCUtils case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + + /* + * RFC 5489 + */ + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + + /* + * draft-josefsson-salsa20-tls-02 + */ + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_UMAC96: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_UMAC96: + return true; + default: return false; } @@ -307,17 +309,13 @@ public class TlsECCUtils return false; } - public static byte[] serializeECFieldElement(int fieldSize, BigInteger x) - throws IOException + public static byte[] serializeECFieldElement(int fieldSize, BigInteger x) throws IOException { - int requiredLength = (fieldSize + 7) / 8; - return BigIntegers.asUnsignedByteArray(requiredLength, x); + return BigIntegers.asUnsignedByteArray((fieldSize + 7) / 8, x); } - public static byte[] serializeECPoint(short[] ecPointFormats, ECPoint point) - throws IOException + public static byte[] serializeECPoint(short[] ecPointFormats, ECPoint point) throws IOException { - ECCurve curve = point.getCurve(); /* @@ -341,12 +339,10 @@ public class TlsECCUtils public static byte[] serializeECPublicKey(short[] ecPointFormats, ECPublicKeyParameters keyParameters) throws IOException { - return serializeECPoint(ecPointFormats, keyParameters.getQ()); } - public static BigInteger deserializeECFieldElement(int fieldSize, byte[] encoding) - throws IOException + public static BigInteger deserializeECFieldElement(int fieldSize, byte[] encoding) throws IOException { int requiredLength = (fieldSize + 7) / 8; if (encoding.length != requiredLength) @@ -356,22 +352,20 @@ public class TlsECCUtils return new BigInteger(1, encoding); } - public static ECPoint deserializeECPoint(short[] ecPointFormats, ECCurve curve, byte[] encoding) - throws IOException + public static ECPoint deserializeECPoint(short[] ecPointFormats, ECCurve curve, byte[] encoding) throws IOException { /* * NOTE: Here we implicitly decode compressed or uncompressed encodings. DefaultTlsClient by * default is set up to advertise that we can parse any encoding so this works fine, but * extra checks might be needed here if that were changed. */ + // TODO Review handling of infinity and hybrid encodings return curve.decodePoint(encoding); } public static ECPublicKeyParameters deserializeECPublicKey(short[] ecPointFormats, ECDomainParameters curve_params, - byte[] encoding) - throws IOException + byte[] encoding) throws IOException { - try { ECPoint Y = deserializeECPoint(ecPointFormats, curve_params.getCurve(), encoding); @@ -385,7 +379,6 @@ public class TlsECCUtils public static byte[] calculateECDHBasicAgreement(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey) { - ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement(); basicAgreement.init(privateKey); BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey); @@ -400,22 +393,29 @@ public class TlsECCUtils public static AsymmetricCipherKeyPair generateECKeyPair(SecureRandom random, ECDomainParameters ecParams) { - ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); - ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(ecParams, random); - keyPairGenerator.init(keyGenerationParameters); + keyPairGenerator.init(new ECKeyGenerationParameters(ecParams, random)); return keyPairGenerator.generateKeyPair(); } - public static ECPublicKeyParameters validateECPublicKey(ECPublicKeyParameters key) - throws IOException + public static ECPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, short[] ecPointFormats, + ECDomainParameters ecParams, OutputStream output) throws IOException + { + AsymmetricCipherKeyPair kp = TlsECCUtils.generateECKeyPair(random, ecParams); + + ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters) kp.getPublic(); + writeECPoint(ecPointFormats, ecPublicKey.getQ(), output); + + return (ECPrivateKeyParameters) kp.getPrivate(); + } + + public static ECPublicKeyParameters validateECPublicKey(ECPublicKeyParameters key) throws IOException { // TODO Check RFC 4492 for validation return key; } - public static int readECExponent(int fieldSize, InputStream input) - throws IOException + public static int readECExponent(int fieldSize, InputStream input) throws IOException { BigInteger K = readECParameter(input); if (K.bitLength() < 32) @@ -429,14 +429,12 @@ public class TlsECCUtils throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - public static BigInteger readECFieldElement(int fieldSize, InputStream input) - throws IOException + public static BigInteger readECFieldElement(int fieldSize, InputStream input) throws IOException { return deserializeECFieldElement(fieldSize, TlsUtils.readOpaque8(input)); } - public static BigInteger readECParameter(InputStream input) - throws IOException + public static BigInteger readECParameter(InputStream input) throws IOException { // TODO Are leading zeroes okay here? return new BigInteger(1, TlsUtils.readOpaque8(input)); @@ -445,7 +443,6 @@ public class TlsECCUtils public static ECDomainParameters readECParameters(int[] namedCurves, short[] ecPointFormats, InputStream input) throws IOException { - try { short curveType = TlsUtils.readUint8(input); @@ -454,6 +451,8 @@ public class TlsECCUtils { case ECCurveType.explicit_prime: { + checkNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_prime_curves); + BigInteger prime_p = readECParameter(input); BigInteger a = readECFieldElement(prime_p.bitLength(), input); BigInteger b = readECFieldElement(prime_p.bitLength(), input); @@ -465,11 +464,12 @@ public class TlsECCUtils } case ECCurveType.explicit_char2: { + checkNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_char2_curves); + int m = TlsUtils.readUint16(input); short basis = TlsUtils.readUint8(input); ECCurve curve; - switch (basis) - { + switch (basis) { case ECBasisType.ec_basis_trinomial: { int k = readECExponent(m, input); @@ -509,15 +509,7 @@ public class TlsECCUtils throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - if (!TlsProtocol.arrayContains(namedCurves, namedCurve)) - { - /* - * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite - * unless they can complete the handshake while respecting the choice of curves - * and compression techniques specified by the client. - */ - throw new TlsFatalAlert(AlertDescription.illegal_parameter); - } + checkNamedCurve(namedCurves, namedCurve); return TlsECCUtils.getParametersForNamedCurve(namedCurve); } @@ -531,47 +523,59 @@ public class TlsECCUtils } } - public static void writeECExponent(int k, OutputStream output) - throws IOException + private static void checkNamedCurve(int[] namedCurves, int namedCurve) throws IOException + { + if (namedCurves != null && !Arrays.contains(namedCurves, namedCurve)) + { + /* + * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite + * unless they can complete the handshake while respecting the choice of curves + * and compression techniques specified by the client. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public static void writeECExponent(int k, OutputStream output) throws IOException { BigInteger K = BigInteger.valueOf(k); writeECParameter(K, output); } - public static void writeECFieldElement(int fieldSize, BigInteger x, OutputStream output) - throws IOException + public static void writeECFieldElement(ECFieldElement x, OutputStream output) throws IOException + { + TlsUtils.writeOpaque8(x.getEncoded(), output); + } + + public static void writeECFieldElement(int fieldSize, BigInteger x, OutputStream output) throws IOException { TlsUtils.writeOpaque8(serializeECFieldElement(fieldSize, x), output); } - public static void writeECParameter(BigInteger x, OutputStream output) - throws IOException + public static void writeECParameter(BigInteger x, OutputStream output) throws IOException { TlsUtils.writeOpaque8(BigIntegers.asUnsignedByteArray(x), output); } public static void writeExplicitECParameters(short[] ecPointFormats, ECDomainParameters ecParameters, - OutputStream output) - throws IOException + OutputStream output) throws IOException { - ECCurve curve = ecParameters.getCurve(); if (curve instanceof ECCurve.Fp) { - TlsUtils.writeUint8(ECCurveType.explicit_prime, output); - ECCurve.Fp fp = (ECCurve.Fp)curve; + ECCurve.Fp fp = (ECCurve.Fp) curve; writeECParameter(fp.getQ(), output); - } else if (curve instanceof ECCurve.F2m) { - TlsUtils.writeUint8(ECCurveType.explicit_char2, output); - ECCurve.F2m f2m = (ECCurve.F2m)curve; - TlsUtils.writeUint16(f2m.getM(), output); + ECCurve.F2m f2m = (ECCurve.F2m) curve; + int m = f2m.getM(); + TlsUtils.checkUint16(m); + TlsUtils.writeUint16(m, output); if (f2m.isTrinomial()) { @@ -592,17 +596,20 @@ public class TlsECCUtils throw new IllegalArgumentException("'ecParameters' not a known curve type"); } - writeECFieldElement(curve.getFieldSize(), curve.getA().toBigInteger(), output); - writeECFieldElement(curve.getFieldSize(), curve.getB().toBigInteger(), output); + writeECFieldElement(curve.getA(), output); + writeECFieldElement(curve.getB(), output); TlsUtils.writeOpaque8(serializeECPoint(ecPointFormats, ecParameters.getG()), output); writeECParameter(ecParameters.getN(), output); writeECParameter(ecParameters.getH(), output); } - public static void writeNamedECParameters(int namedCurve, OutputStream output) - throws IOException + public static void writeECPoint(short[] ecPointFormats, ECPoint point, OutputStream output) throws IOException { + TlsUtils.writeOpaque8(TlsECCUtils.serializeECPoint(ecPointFormats, point), output); + } + public static void writeNamedECParameters(int namedCurve, OutputStream output) throws IOException + { if (!NamedCurve.refersToASpecificNamedCurve(namedCurve)) { /* @@ -614,6 +621,7 @@ public class TlsECCUtils } TlsUtils.writeUint8(ECCurveType.named_curve, output); + TlsUtils.checkUint16(namedCurve); TlsUtils.writeUint16(namedCurve, output); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java index 1124560..d346ef4 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java @@ -1,6 +1,5 @@ package org.bouncycastle.crypto.tls; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Vector; @@ -8,10 +7,11 @@ import java.util.Vector; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Signer; -import org.bouncycastle.crypto.io.SignerInputStream; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.io.TeeInputStream; /** * ECDHE key exchange (see RFC 4492) @@ -19,11 +19,10 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters; public class TlsECDHEKeyExchange extends TlsECDHKeyExchange { - protected TlsSignerCredentials serverCredentials = null; public TlsECDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves, - short[] clientECPointFormats, short[] serverECPointFormats) + short[] clientECPointFormats, short[] serverECPointFormats) { super(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, serverECPointFormats); } @@ -31,7 +30,6 @@ public class TlsECDHEKeyExchange public void processServerCredentials(TlsCredentials serverCredentials) throws IOException { - if (!(serverCredentials instanceof TlsSignerCredentials)) { throw new TlsFatalAlert(AlertDescription.internal_error); @@ -45,13 +43,13 @@ public class TlsECDHEKeyExchange public byte[] generateServerKeyExchange() throws IOException { - /* * First we try to find a supported named curve from the client's list. */ int namedCurve = -1; if (namedCurves == null) { + // TODO Let the peer choose the default named curve namedCurve = NamedCurve.secp256r1; } else @@ -77,13 +75,13 @@ public class TlsECDHEKeyExchange /* * If no named curves are suitable, check if the client supports explicit curves. */ - if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves)) + if (Arrays.contains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves)) { curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.secp256r1); } - else if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves)) + else if (Arrays.contains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves)) { - curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.sect233r1); + curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.sect283r1); } } @@ -97,12 +95,9 @@ public class TlsECDHEKeyExchange } AsymmetricCipherKeyPair kp = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), curve_params); - this.ecAgreeServerPrivateKey = (ECPrivateKeyParameters)kp.getPrivate(); + this.ecAgreePrivateKey = (ECPrivateKeyParameters)kp.getPrivate(); - byte[] publicBytes = TlsECCUtils.serializeECPublicKey(clientECPointFormats, - (ECPublicKeyParameters)kp.getPublic()); - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DigestInputBuffer buf = new DigestInputBuffer(); if (namedCurve < 0) { @@ -113,25 +108,43 @@ public class TlsECDHEKeyExchange TlsECCUtils.writeNamedECParameters(namedCurve, buf); } - TlsUtils.writeOpaque8(publicBytes, buf); + ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters) kp.getPublic(); + TlsECCUtils.writeECPoint(clientECPointFormats, ecPublicKey.getQ(), buf); + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm; + Digest d; + + if (TlsUtils.isTLSv12(context)) + { + signatureAndHashAlgorithm = serverCredentials.getSignatureAndHashAlgorithm(); + if (signatureAndHashAlgorithm == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } - byte[] digestInput = buf.toByteArray(); + d = TlsUtils.createHash(signatureAndHashAlgorithm.getHash()); + } + else + { + signatureAndHashAlgorithm = null; + d = new CombinedHash(); + } - Digest d = new CombinedHash(); SecurityParameters securityParameters = context.getSecurityParameters(); d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); - d.update(digestInput, 0, digestInput.length); + buf.updateDigest(d); byte[] hash = new byte[d.getDigestSize()]; d.doFinal(hash, 0); - byte[] sigBytes = serverCredentials.generateCertificateSignature(hash); - /* - * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended - * from TLS 1.2 - */ - TlsUtils.writeOpaque16(sigBytes, buf); + byte[] signature = serverCredentials.generateCertificateSignature(hash); + + DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature); + signed_params.encode(buf); return buf.toByteArray(); } @@ -139,23 +152,25 @@ public class TlsECDHEKeyExchange public void processServerKeyExchange(InputStream input) throws IOException { - SecurityParameters securityParameters = context.getSecurityParameters(); - Signer signer = initVerifyer(tlsSigner, securityParameters); - InputStream sigIn = new SignerInputStream(input, signer); + SignerInputBuffer buf = new SignerInputBuffer(); + InputStream teeIn = new TeeInputStream(input, buf); + + ECDomainParameters curve_params = TlsECCUtils.readECParameters(namedCurves, clientECPointFormats, teeIn); - ECDomainParameters curve_params = TlsECCUtils.readECParameters(namedCurves, clientECPointFormats, sigIn); + byte[] point = TlsUtils.readOpaque8(teeIn); - byte[] point = TlsUtils.readOpaque8(sigIn); + DigitallySigned signed_params = DigitallySigned.parse(context, input); - byte[] sigByte = TlsUtils.readOpaque16(input); - if (!signer.verifySignature(sigByte)) + Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters); + buf.updateSigner(signer); + if (!signer.verifySignature(signed_params.getSignature())) { throw new TlsFatalAlert(AlertDescription.decrypt_error); } - this.ecAgreeServerPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( + this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( clientECPointFormats, curve_params, point)); } @@ -196,9 +211,9 @@ public class TlsECDHEKeyExchange } } - protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters) { - Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey); signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); return signer; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java index 26c0975..9456352 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java @@ -7,7 +7,6 @@ import java.util.Vector; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; @@ -17,26 +16,21 @@ import org.bouncycastle.crypto.util.PublicKeyFactory; /** * ECDH key exchange (see RFC 4492) */ -public class TlsECDHKeyExchange - extends AbstractTlsKeyExchange +public class TlsECDHKeyExchange extends AbstractTlsKeyExchange { - protected TlsSigner tlsSigner; protected int[] namedCurves; protected short[] clientECPointFormats, serverECPointFormats; protected AsymmetricKeyParameter serverPublicKey; - protected ECPublicKeyParameters ecAgreeServerPublicKey; protected TlsAgreementCredentials agreementCredentials; - protected ECPrivateKeyParameters ecAgreeClientPrivateKey; - protected ECPrivateKeyParameters ecAgreeServerPrivateKey; - protected ECPublicKeyParameters ecAgreeClientPublicKey; + protected ECPrivateKeyParameters ecAgreePrivateKey; + protected ECPublicKeyParameters ecAgreePublicKey; public TlsECDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves, - short[] clientECPointFormats, short[] serverECPointFormats) + short[] clientECPointFormats, short[] serverECPointFormats) { - super(keyExchange, supportedSignatureAlgorithms); switch (keyExchange) @@ -71,16 +65,13 @@ public class TlsECDHKeyExchange } } - public void skipServerCredentials() - throws IOException + public void skipServerCredentials() throws IOException { throw new TlsFatalAlert(AlertDescription.unexpected_message); } - public void processServerCertificate(Certificate serverCertificate) - throws IOException + public void processServerCertificate(Certificate serverCertificate) throws IOException { - if (serverCertificate.isEmpty()) { throw new TlsFatalAlert(AlertDescription.bad_certificate); @@ -102,8 +93,7 @@ public class TlsECDHKeyExchange { try { - this.ecAgreeServerPublicKey = TlsECCUtils - .validateECPublicKey((ECPublicKeyParameters)this.serverPublicKey); + this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey((ECPublicKeyParameters) this.serverPublicKey); } catch (ClassCastException e) { @@ -138,8 +128,7 @@ public class TlsECDHKeyExchange } } - public void validateCertificateRequest(CertificateRequest certificateRequest) - throws IOException + public void validateCertificateRequest(CertificateRequest certificateRequest) throws IOException { /* * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with @@ -164,14 +153,13 @@ public class TlsECDHKeyExchange } } - public void processClientCredentials(TlsCredentials clientCredentials) - throws IOException + public void processClientCredentials(TlsCredentials clientCredentials) throws IOException { if (clientCredentials instanceof TlsAgreementCredentials) { // TODO Validate client cert has matching parameters (see 'TlsECCUtils.areOnSameCurve')? - this.agreementCredentials = (TlsAgreementCredentials)clientCredentials; + this.agreementCredentials = (TlsAgreementCredentials) clientCredentials; } else if (clientCredentials instanceof TlsSignerCredentials) { @@ -183,37 +171,24 @@ public class TlsECDHKeyExchange } } - public void generateClientKeyExchange(OutputStream output) - throws IOException + public void generateClientKeyExchange(OutputStream output) throws IOException { - if (agreementCredentials != null) + if (agreementCredentials == null) { - return; + this.ecAgreePrivateKey = TlsECCUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(), + serverECPointFormats, ecAgreePublicKey.getParameters(), output); } - - AsymmetricCipherKeyPair ecAgreeClientKeyPair = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), - ecAgreeServerPublicKey.getParameters()); - this.ecAgreeClientPrivateKey = (ECPrivateKeyParameters)ecAgreeClientKeyPair.getPrivate(); - - byte[] point = TlsECCUtils.serializeECPublicKey(serverECPointFormats, - (ECPublicKeyParameters)ecAgreeClientKeyPair.getPublic()); - - TlsUtils.writeOpaque8(point, output); } - public void processClientCertificate(Certificate clientCertificate) - throws IOException + public void processClientCertificate(Certificate clientCertificate) throws IOException { - // TODO Extract the public key // TODO If the certificate is 'fixed', take the public key as ecAgreeClientPublicKey } - public void processClientKeyExchange(InputStream input) - throws IOException + public void processClientKeyExchange(InputStream input) throws IOException { - - if (ecAgreeClientPublicKey != null) + if (ecAgreePublicKey != null) { // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate return; @@ -221,28 +196,22 @@ public class TlsECDHKeyExchange byte[] point = TlsUtils.readOpaque8(input); - ECDomainParameters curve_params = this.ecAgreeServerPrivateKey.getParameters(); + ECDomainParameters curve_params = this.ecAgreePrivateKey.getParameters(); - this.ecAgreeClientPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( + this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey( serverECPointFormats, curve_params, point)); } - public byte[] generatePremasterSecret() - throws IOException + public byte[] generatePremasterSecret() throws IOException { if (agreementCredentials != null) { - return agreementCredentials.generateAgreement(ecAgreeServerPublicKey); - } - - if (ecAgreeServerPrivateKey != null) - { - return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeClientPublicKey, ecAgreeServerPrivateKey); + return agreementCredentials.generateAgreement(ecAgreePublicKey); } - if (ecAgreeClientPrivateKey != null) + if (ecAgreePrivateKey != null) { - return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeServerPublicKey, ecAgreeClientPrivateKey); + return TlsECCUtils.calculateECDHBasicAgreement(ecAgreePublicKey, ecAgreePrivateKey); } throw new TlsFatalAlert(AlertDescription.internal_error); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java index 6809815..d7f8064 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsECDSASigner.java @@ -8,7 +8,6 @@ import org.bouncycastle.crypto.signers.ECDSASigner; public class TlsECDSASigner extends TlsDSASigner { - public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) { return publicKey instanceof ECPublicKeyParameters; @@ -18,4 +17,9 @@ public class TlsECDSASigner { return new ECDSASigner(); } + + protected short getSignatureAlgorithm() + { + return SignatureAlgorithm.ecdsa; + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java index 2680136..eddf684 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java @@ -5,7 +5,6 @@ import java.io.IOException; public interface TlsEncryptionCredentials extends TlsCredentials { - byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret) throws IOException; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsExtensionsUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsExtensionsUtils.java new file mode 100644 index 0000000..fbc39dd --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsExtensionsUtils.java @@ -0,0 +1,240 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.util.Integers; + +public class TlsExtensionsUtils +{ + public static final Integer EXT_heartbeat = Integers.valueOf(ExtensionType.heartbeat); + public static final Integer EXT_max_fragment_length = Integers.valueOf(ExtensionType.max_fragment_length); + public static final Integer EXT_server_name = Integers.valueOf(ExtensionType.server_name); + public static final Integer EXT_status_request = Integers.valueOf(ExtensionType.status_request); + public static final Integer EXT_truncated_hmac = Integers.valueOf(ExtensionType.truncated_hmac); + + public static Hashtable ensureExtensionsInitialised(Hashtable extensions) + { + return extensions == null ? new Hashtable() : extensions; + } + + public static void addHeartbeatExtension(Hashtable extensions, HeartbeatExtension heartbeatExtension) + throws IOException + { + extensions.put(EXT_heartbeat, createHeartbeatExtension(heartbeatExtension)); + } + + public static void addMaxFragmentLengthExtension(Hashtable extensions, short maxFragmentLength) + throws IOException + { + extensions.put(EXT_max_fragment_length, createMaxFragmentLengthExtension(maxFragmentLength)); + } + + public static void addServerNameExtension(Hashtable extensions, ServerNameList serverNameList) + throws IOException + { + extensions.put(EXT_server_name, createServerNameExtension(serverNameList)); + } + + public static void addStatusRequestExtension(Hashtable extensions, CertificateStatusRequest statusRequest) + throws IOException + { + extensions.put(EXT_status_request, createStatusRequestExtension(statusRequest)); + } + + public static void addTruncatedHMacExtension(Hashtable extensions) + { + extensions.put(EXT_truncated_hmac, createTruncatedHMacExtension()); + } + + public static HeartbeatExtension getHeartbeatExtension(Hashtable extensions) + throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_heartbeat); + return extensionData == null ? null : readHeartbeatExtension(extensionData); + } + + public static short getMaxFragmentLengthExtension(Hashtable extensions) + throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_max_fragment_length); + return extensionData == null ? -1 : readMaxFragmentLengthExtension(extensionData); + } + + public static ServerNameList getServerNameExtension(Hashtable extensions) + throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_server_name); + return extensionData == null ? null : readServerNameExtension(extensionData); + } + + public static CertificateStatusRequest getStatusRequestExtension(Hashtable extensions) + throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_status_request); + return extensionData == null ? null : readStatusRequestExtension(extensionData); + } + + public static boolean hasTruncatedHMacExtension(Hashtable extensions) throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_truncated_hmac); + return extensionData == null ? false : readTruncatedHMacExtension(extensionData); + } + + public static byte[] createEmptyExtensionData() + { + return TlsUtils.EMPTY_BYTES; + } + + public static byte[] createHeartbeatExtension(HeartbeatExtension heartbeatExtension) + throws IOException + { + if (heartbeatExtension == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + heartbeatExtension.encode(buf); + + return buf.toByteArray(); + } + + public static byte[] createMaxFragmentLengthExtension(short maxFragmentLength) + throws IOException + { + if (!MaxFragmentLength.isValid(maxFragmentLength)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return new byte[]{ (byte)maxFragmentLength }; + } + + public static byte[] createServerNameExtension(ServerNameList serverNameList) + throws IOException + { + if (serverNameList == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + serverNameList.encode(buf); + + return buf.toByteArray(); + } + + public static byte[] createStatusRequestExtension(CertificateStatusRequest statusRequest) + throws IOException + { + if (statusRequest == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + statusRequest.encode(buf); + + return buf.toByteArray(); + } + + public static byte[] createTruncatedHMacExtension() + { + return createEmptyExtensionData(); + } + + public static HeartbeatExtension readHeartbeatExtension(byte[] extensionData) + throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); + + HeartbeatExtension heartbeatExtension = HeartbeatExtension.parse(buf); + + TlsProtocol.assertEmpty(buf); + + return heartbeatExtension; + } + + public static short readMaxFragmentLengthExtension(byte[] extensionData) + throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + if (extensionData.length != 1) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + short maxFragmentLength = (short)extensionData[0]; + + if (!MaxFragmentLength.isValid(maxFragmentLength)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return maxFragmentLength; + } + + public static ServerNameList readServerNameExtension(byte[] extensionData) + throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); + + ServerNameList serverNameList = ServerNameList.parse(buf); + + TlsProtocol.assertEmpty(buf); + + return serverNameList; + } + + public static CertificateStatusRequest readStatusRequestExtension(byte[] extensionData) + throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); + + CertificateStatusRequest statusRequest = CertificateStatusRequest.parse(buf); + + TlsProtocol.assertEmpty(buf); + + return statusRequest; + } + + private static boolean readTruncatedHMacExtension(byte[] extensionData) throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + if (extensionData.length != 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return true; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java index b17b8d7..1cb0f4d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsHandshakeHash.java @@ -5,10 +5,17 @@ import org.bouncycastle.crypto.Digest; interface TlsHandshakeHash extends Digest { - void init(TlsContext context); - TlsHandshakeHash commit(); + TlsHandshakeHash notifyPRFDetermined(); + + void trackHashAlgorithm(short hashAlgorithm); + + void sealHashAlgorithms(); + + TlsHandshakeHash stopTracking(); + + Digest forkPRFHash(); - TlsHandshakeHash fork(); + byte[] getFinalHash(short hashAlgorithm); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java index ec11130..20dfef8 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsMac.java @@ -1,8 +1,5 @@ package org.bouncycastle.crypto.tls; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.digests.LongDigest; @@ -15,19 +12,19 @@ import org.bouncycastle.util.Arrays; */ public class TlsMac { - protected TlsContext context; protected byte[] secret; protected Mac mac; protected int digestBlockSize; protected int digestOverhead; + protected int macLength; /** * Generate a new instance of an TlsMac. * * @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 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 len The length of the key. */ @@ -51,7 +48,7 @@ public class TlsMac this.digestOverhead = 8; } - if (context.getServerVersion().isSSL()) + if (TlsUtils.isSSL(context)) { this.mac = new SSL3Mac(digest); @@ -73,6 +70,12 @@ public class TlsMac } this.mac.init(keyParameter); + + this.macLength = mac.getMacSize(); + if (context.getSecurityParameters().truncatedHMac) + { + this.macLength = Math.min(this.macLength, 10); + } } /** @@ -84,11 +87,11 @@ public class TlsMac } /** - * @return The Keysize of the mac. + * @return The output length of this MAC. */ public int getSize() { - return mac.getMacSize(); + return macLength; } /** @@ -102,42 +105,38 @@ public class TlsMac */ public byte[] calculateMac(long seqNo, short type, byte[] message, int offset, int length) { + /* + * TODO[draft-josefsson-salsa20-tls-02] 3. Moreover, in order to accommodate MAC algorithms + * like UMAC that require a nonce as part of their operation, the document extends the MAC + * algorithm as specified in the TLS protocol. The extended MAC includes a nonce as a second + * parameter. MAC algorithms that do not require a nonce, such as HMAC, are assumed to + * ignore the nonce input value. The MAC in a GenericStreamCipher is then calculated as + * follows. + */ ProtocolVersion serverVersion = context.getServerVersion(); boolean isSSL = serverVersion.isSSL(); - ByteArrayOutputStream bosMac = new ByteArrayOutputStream(isSSL ? 11 : 13); - try + byte[] macHeader = new byte[isSSL ? 11 : 13]; + TlsUtils.writeUint64(seqNo, macHeader, 0); + TlsUtils.writeUint8(type, macHeader, 8); + if (!isSSL) { - TlsUtils.writeUint64(seqNo, bosMac); - TlsUtils.writeUint8(type, bosMac); - - if (!isSSL) - { - TlsUtils.writeVersion(serverVersion, bosMac); - } - - TlsUtils.writeUint16(length, bosMac); - } - catch (IOException e) - { - // This should never happen - throw new IllegalStateException("Internal error during mac calculation"); + TlsUtils.writeVersion(serverVersion, macHeader, 9); } + TlsUtils.writeUint16(length, macHeader, macHeader.length - 2); - byte[] macHeader = bosMac.toByteArray(); mac.update(macHeader, 0, macHeader.length); mac.update(message, offset, length); byte[] result = new byte[mac.getMacSize()]; mac.doFinal(result, 0); - return result; + return truncate(result); } public byte[] calculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length, - int fullLength, byte[] dummyData) + int fullLength, byte[] dummyData) { - /* * Actual MAC only calculated on 'length' bytes... */ @@ -147,7 +146,7 @@ public class TlsMac * ...but ensure a constant number of complete digest blocks are processed (as many as would * be needed for 'fullLength' bytes of input). */ - int headerLength = context.getServerVersion().isSSL() ? 11 : 13; + int headerLength = TlsUtils.isSSL(context) ? 11 : 13; // How many extra full blocks do we need to calculate? int extra = getDigestBlockCount(headerLength + fullLength) - getDigestBlockCount(headerLength + length); @@ -164,9 +163,19 @@ public class TlsMac return result; } - private int getDigestBlockCount(int inputLength) + protected int getDigestBlockCount(int inputLength) { // NOTE: This calculation assumes a minimum of 1 pad byte return (inputLength + digestOverhead) / digestBlockSize; } + + protected byte[] truncate(byte[] bs) + { + if (bs.length <= macLength) + { + return bs; + } + + return Arrays.copyOf(bs, macLength); + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java index d5b2b98..d1f6986 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsNullCipher.java @@ -26,7 +26,6 @@ public class TlsNullCipher public TlsNullCipher(TlsContext context, Digest clientWriteDigest, Digest serverWriteDigest) throws IOException { - if ((clientWriteDigest == null) != (serverWriteDigest == null)) { throw new TlsFatalAlert(AlertDescription.internal_error); @@ -38,7 +37,6 @@ public class TlsNullCipher if (clientWriteDigest != null) { - int key_block_size = clientWriteDigest.getDigestSize() + serverWriteDigest.getDigestSize(); byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size); @@ -84,7 +82,6 @@ public class TlsNullCipher public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) throws IOException { - if (writeMac == null) { return Arrays.copyOfRange(plaintext, offset, offset + len); @@ -100,7 +97,6 @@ public class TlsNullCipher public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) throws IOException { - if (readMac == null) { return Arrays.copyOfRange(ciphertext, offset, offset + len); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java index cfabb76..7217bac 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java @@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.math.BigInteger; import java.util.Vector; import org.bouncycastle.asn1.x509.KeyUsage; @@ -22,33 +21,42 @@ import org.bouncycastle.crypto.util.PublicKeyFactory; public class TlsPSKKeyExchange extends AbstractTlsKeyExchange { - protected TlsPSKIdentity pskIdentity; + protected DHParameters dhParameters; + protected int[] namedCurves; + protected short[] clientECPointFormats, serverECPointFormats; protected byte[] psk_identity_hint = null; - protected DHPublicKeyParameters dhAgreeServerPublicKey = null; - protected DHPrivateKeyParameters dhAgreeClientPrivateKey = null; + protected DHPrivateKeyParameters dhAgreePrivateKey = null; + protected DHPublicKeyParameters dhAgreePublicKey = null; protected AsymmetricKeyParameter serverPublicKey = null; protected RSAKeyParameters rsaServerPublicKey = null; + protected TlsEncryptionCredentials serverCredentials = null; protected byte[] premasterSecret; - public TlsPSKKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, TlsPSKIdentity pskIdentity) + public TlsPSKKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, TlsPSKIdentity pskIdentity, + DHParameters dhParameters, int[] namedCurves, short[] clientECPointFormats, short[] serverECPointFormats) { super(keyExchange, supportedSignatureAlgorithms); switch (keyExchange) { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: case KeyExchangeAlgorithm.PSK: case KeyExchangeAlgorithm.RSA_PSK: - case KeyExchangeAlgorithm.DHE_PSK: break; default: throw new IllegalArgumentException("unsupported key exchange algorithm"); } this.pskIdentity = pskIdentity; + this.dhParameters = dhParameters; + this.namedCurves = namedCurves; + this.clientECPointFormats = clientECPointFormats; + this.serverECPointFormats = serverECPointFormats; } public void skipServerCredentials() @@ -60,10 +68,61 @@ public class TlsPSKKeyExchange } } - public void processServerCertificate(Certificate serverCertificate) + public void processServerCredentials(TlsCredentials serverCredentials) throws IOException { + if (!(serverCredentials instanceof TlsEncryptionCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + processServerCertificate(serverCredentials.getCertificate()); + + this.serverCredentials = (TlsEncryptionCredentials)serverCredentials; + } + + public byte[] generateServerKeyExchange() throws IOException + { + // TODO[RFC 4279] Need a server-side PSK API to determine hint and resolve identities to keys + this.psk_identity_hint = null; + + if (this.psk_identity_hint == null && !requiresServerKeyExchange()) + { + return null; + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + if (this.psk_identity_hint == null) + { + TlsUtils.writeOpaque16(TlsUtils.EMPTY_BYTES, buf); + } + else + { + TlsUtils.writeOpaque16(this.psk_identity_hint, buf); + } + + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + if (this.dhParameters == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.dhAgreePrivateKey = TlsDHUtils.generateEphemeralServerKeyExchange(context.getSecureRandom(), + this.dhParameters, buf); + } + else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + // TODO[RFC 5489] + } + + return buf.toByteArray(); + } + public void processServerCertificate(Certificate serverCertificate) + throws IOException + { if (keyExchange != KeyExchangeAlgorithm.RSA_PSK) { throw new TlsFatalAlert(AlertDescription.unexpected_message); @@ -100,27 +159,30 @@ public class TlsPSKKeyExchange public boolean requiresServerKeyExchange() { - return keyExchange == KeyExchangeAlgorithm.DHE_PSK; + switch (keyExchange) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + return true; + default: + return false; + } } public void processServerKeyExchange(InputStream input) throws IOException { - this.psk_identity_hint = TlsUtils.readOpaque16(input); if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) { - byte[] pBytes = TlsUtils.readOpaque16(input); - byte[] gBytes = TlsUtils.readOpaque16(input); - byte[] YsBytes = TlsUtils.readOpaque16(input); + ServerDHParams serverDHParams = ServerDHParams.parse(input); - BigInteger p = new BigInteger(1, pBytes); - BigInteger g = new BigInteger(1, gBytes); - BigInteger Ys = new BigInteger(1, YsBytes); - - this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey(new DHPublicKeyParameters(Ys, - new DHParameters(p, g))); + this.dhAgreePublicKey = TlsDHUtils.validateDHPublicKey(serverDHParams.getPublicKey()); + } + else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + // TODO[RFC 5489] } } @@ -139,7 +201,6 @@ public class TlsPSKKeyExchange public void generateClientKeyExchange(OutputStream output) throws IOException { - if (psk_identity_hint == null) { pskIdentity.skipIdentityHint(); @@ -153,22 +214,26 @@ public class TlsPSKKeyExchange TlsUtils.writeOpaque16(psk_identity, output); - if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) { - this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, - output); + this.dhAgreePrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(), + dhAgreePublicKey.getParameters(), output); + } + else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + // TODO[RFC 5489] + throw new TlsFatalAlert(AlertDescription.internal_error); } - else if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + else if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) { - this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(), - dhAgreeServerPublicKey.getParameters(), output); + this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, + output); } } public byte[] generatePremasterSecret() throws IOException { - byte[] psk = pskIdentity.getPSK(); byte[] other_secret = generateOtherSecret(psk.length); @@ -178,12 +243,22 @@ public class TlsPSKKeyExchange return buf.toByteArray(); } - protected byte[] generateOtherSecret(int pskLength) + protected byte[] generateOtherSecret(int pskLength) throws IOException { - if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) { - return TlsDHUtils.calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + if (dhAgreePrivateKey != null) + { + return TlsDHUtils.calculateDHBasicAgreement(dhAgreePublicKey, dhAgreePrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + // TODO[RFC 5489] + throw new TlsFatalAlert(AlertDescription.internal_error); } if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java index e408002..88780ea 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsPeer.java @@ -1,7 +1,14 @@ package org.bouncycastle.crypto.tls; +import java.io.IOException; + public interface TlsPeer { + void notifySecureRenegotiation(boolean secureNegotiation) throws IOException; + + TlsCompression getCompression() throws IOException; + + TlsCipher getCipher() throws IOException; /** * This method will be called when an alert is raised by the protocol. @@ -20,4 +27,9 @@ public interface TlsPeer * @param alertDescription {@link AlertDescription} */ void notifyAlertReceived(short alertLevel, short alertDescription); + + /** + * Notifies the peer that the handshake has been successfully completed. + */ + void notifyHandshakeComplete() throws IOException; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java index 6d8e3d3..2c3b094 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsProtocol.java @@ -2,6 +2,7 @@ package org.bouncycastle.crypto.tls; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -10,6 +11,7 @@ import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; +import org.bouncycastle.crypto.Digest; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Integers; @@ -18,7 +20,6 @@ import org.bouncycastle.util.Integers; */ public abstract class TlsProtocol { - protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info); protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket); @@ -32,25 +33,24 @@ public abstract class TlsProtocol protected static final short CS_SERVER_HELLO = 2; protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 3; protected static final short CS_SERVER_CERTIFICATE = 4; - protected static final short CS_SERVER_KEY_EXCHANGE = 5; - protected static final short CS_CERTIFICATE_REQUEST = 6; - protected static final short CS_SERVER_HELLO_DONE = 7; - protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 8; - protected static final short CS_CLIENT_CERTIFICATE = 9; - protected static final short CS_CLIENT_KEY_EXCHANGE = 10; - protected static final short CS_CERTIFICATE_VERIFY = 11; - protected static final short CS_CLIENT_CHANGE_CIPHER_SPEC = 12; + protected static final short CS_CERTIFICATE_STATUS = 5; + protected static final short CS_SERVER_KEY_EXCHANGE = 6; + protected static final short CS_CERTIFICATE_REQUEST = 7; + protected static final short CS_SERVER_HELLO_DONE = 8; + protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 9; + protected static final short CS_CLIENT_CERTIFICATE = 10; + protected static final short CS_CLIENT_KEY_EXCHANGE = 11; + protected static final short CS_CERTIFICATE_VERIFY = 12; protected static final short CS_CLIENT_FINISHED = 13; protected static final short CS_SERVER_SESSION_TICKET = 14; - protected static final short CS_SERVER_CHANGE_CIPHER_SPEC = 15; - protected static final short CS_SERVER_FINISHED = 16; + protected static final short CS_SERVER_FINISHED = 15; + protected static final short CS_END = 16; /* * Queues for data from some protocols. */ private ByteQueue applicationDataQueue = new ByteQueue(); - private ByteQueue changeCipherSpecQueue = new ByteQueue(); - private ByteQueue alertQueue = new ByteQueue(); + private ByteQueue alertQueue = new ByteQueue(2); private ByteQueue handshakeQueue = new ByteQueue(); /* @@ -65,13 +65,24 @@ public abstract class TlsProtocol private volatile boolean closed = false; private volatile boolean failedWithError = false; private volatile boolean appDataReady = false; - private volatile boolean writeExtraEmptyRecords = true; + private volatile boolean splitApplicationDataRecords = true; private byte[] expected_verify_data = null; + protected TlsSession tlsSession = null; + protected SessionParameters sessionParameters = null; protected SecurityParameters securityParameters = null; + protected Certificate peerCertificate = null; + + protected int[] offeredCipherSuites = null; + protected short[] offeredCompressionMethods = null; + protected Hashtable clientExtensions = null; + protected Hashtable serverExtensions = null; protected short connection_state = CS_START; + protected boolean resumedSession = false; + protected boolean receivedChangeCipherSpec = false; protected boolean secure_renegotiation = false; + protected boolean allowCertificateStatus = false; protected boolean expectSessionTicket = false; public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom) @@ -84,8 +95,9 @@ public abstract class TlsProtocol protected abstract TlsPeer getPeer(); - protected abstract void handleChangeCipherSpecMessage() - throws IOException; + protected void handleChangeCipherSpecMessage() throws IOException + { + } protected abstract void handleHandshakeMessage(short type, byte[] buf) throws IOException; @@ -93,37 +105,88 @@ public abstract class TlsProtocol protected void handleWarningMessage(short description) throws IOException { + } + + protected void cleanupHandshake() + { + if (this.expected_verify_data != null) + { + Arrays.fill(this.expected_verify_data, (byte)0); + this.expected_verify_data = null; + } + + this.securityParameters.clear(); + this.peerCertificate = null; + this.offeredCipherSuites = null; + this.offeredCompressionMethods = null; + this.clientExtensions = null; + this.serverExtensions = null; + + this.resumedSession = false; + this.receivedChangeCipherSpec = false; + this.secure_renegotiation = false; + this.allowCertificateStatus = false; + this.expectSessionTicket = false; } protected void completeHandshake() throws IOException { + try + { + /* + * We will now read data, until we have completed the handshake. + */ + while (this.connection_state != CS_END) + { + if (this.closed) + { + // TODO What kind of exception/alert? + } - this.expected_verify_data = null; + safeReadRecord(); + } - /* - * We will now read data, until we have completed the handshake. - */ - while (this.connection_state != CS_SERVER_FINISHED) - { - safeReadRecord(); - } + this.recordStream.finaliseHandshake(); - this.recordStream.finaliseHandshake(); + this.splitApplicationDataRecords = !TlsUtils.isTLSv11(getContext()); - ProtocolVersion version = getContext().getServerVersion(); - this.writeExtraEmptyRecords = version.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10); + /* + * If this was an initial handshake, we are now ready to send and receive application data. + */ + if (!appDataReady) + { + this.appDataReady = true; - /* - * If this was an initial handshake, we are now ready to send and receive application data. - */ - if (!appDataReady) - { - this.appDataReady = true; + this.tlsInputStream = new TlsInputStream(this); + this.tlsOutputStream = new TlsOutputStream(this); + } - this.tlsInputStream = new TlsInputStream(this); - this.tlsOutputStream = new TlsOutputStream(this); + if (this.tlsSession != null) + { + if (this.sessionParameters == null) + { + this.sessionParameters = new SessionParameters.Builder() + .setCipherSuite(this.securityParameters.cipherSuite) + .setCompressionAlgorithm(this.securityParameters.compressionAlgorithm) + .setMasterSecret(this.securityParameters.masterSecret) + .setPeerCertificate(this.peerCertificate) + // TODO Consider filtering extensions that aren't relevant to resumed sessions + .setServerExtensions(this.serverExtensions) + .build(); + + this.tlsSession = new TlsSessionImpl(this.tlsSession.getSessionID(), this.sessionParameters); + } + + getContext().setResumableSession(this.tlsSession); + } + + getPeer().notifyHandshakeComplete(); + } + finally + { + cleanupHandshake(); } } @@ -135,26 +198,37 @@ public abstract class TlsProtocol */ switch (protocol) { - case ContentType.change_cipher_spec: - changeCipherSpecQueue.addData(buf, offset, len); - processChangeCipherSpec(); - break; case ContentType.alert: + { alertQueue.addData(buf, offset, len); processAlert(); break; - case ContentType.handshake: - handshakeQueue.addData(buf, offset, len); - processHandshake(); - break; + } case ContentType.application_data: + { if (!appDataReady) { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } applicationDataQueue.addData(buf, offset, len); processApplicationData(); break; + } + case ContentType.change_cipher_spec: + { + processChangeCipherSpec(buf, offset, len); + break; + } + case ContentType.handshake: + { + handshakeQueue.addData(buf, offset, len); + processHandshake(); + break; + } + case ContentType.heartbeat: + { + // TODO[RFC 6520] + } default: /* * Uh, we don't know this protocol. @@ -190,9 +264,7 @@ public abstract class TlsProtocol /* * Read the message. */ - byte[] buf = new byte[len]; - handshakeQueue.read(buf, 0, len, 4); - handshakeQueue.removeData(len + 4); + byte[] buf = handshakeQueue.removeData(len, 4); /* * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages @@ -205,7 +277,6 @@ public abstract class TlsProtocol break; case HandshakeType.finished: { - if (this.expected_verify_data == null) { this.expected_verify_data = createVerifyData(!getContext().isServer()); @@ -247,9 +318,7 @@ public abstract class TlsProtocol /* * An alert is always 2 bytes. Read the alert. */ - byte[] tmp = new byte[2]; - alertQueue.read(tmp, 0, 2, 0); - alertQueue.removeData(2); + byte[] tmp = alertQueue.removeData(2, 0); short level = tmp[0]; short description = tmp[1]; @@ -257,20 +326,17 @@ public abstract class TlsProtocol if (level == AlertLevel.fatal) { + /* + * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated + * without proper close_notify messages with level equal to warning. + */ + invalidateSession(); this.failedWithError = true; this.closed = true; - /* - * Now try to close the stream, ignore errors. - */ - try - { - recordStream.close(); - } - catch (Exception e) - { - } + recordStream.safeClose(); + throw new IOException(TLS_ERROR_MESSAGE); } else @@ -300,27 +366,27 @@ public abstract class TlsProtocol * @throws IOException If the message has an invalid content or the handshake is not in the correct * state. */ - private void processChangeCipherSpec() + private void processChangeCipherSpec(byte[] buf, int off, int len) throws IOException { - while (changeCipherSpecQueue.size() > 0) + for (int i = 0; i < len; ++i) { - /* - * A change cipher spec message is only one byte with the value 1. - */ - byte[] b = new byte[1]; - changeCipherSpecQueue.read(b, 0, 1, 0); - changeCipherSpecQueue.removeData(1); - if (b[0] != 1) + short message = TlsUtils.readUint8(buf, off + i); + + if (message != ChangeCipherSpec.change_cipher_spec) { - /* - * This should never happen. - */ - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.decode_error); } - recordStream.receivedReadCipherSpec(); + if (this.receivedChangeCipherSpec) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + this.receivedChangeCipherSpec = true; + + recordStream.receivedReadCipherSpec(); + handleChangeCipherSpecMessage(); } } @@ -338,7 +404,6 @@ public abstract class TlsProtocol protected int readApplicationData(byte[] buf, int offset, int len) throws IOException { - if (len < 1) { return 0; @@ -367,9 +432,9 @@ public abstract class TlsProtocol safeReadRecord(); } + len = Math.min(len, applicationDataQueue.size()); - applicationDataQueue.read(buf, offset, len, 0); - applicationDataQueue.removeData(len); + applicationDataQueue.removeData(buf, offset, len, 0); return len; } @@ -378,13 +443,18 @@ public abstract class TlsProtocol { try { - recordStream.readRecord(); + if (!recordStream.readRecord()) + { + // TODO It would be nicer to allow graceful connection close if between records +// this.failWithError(AlertLevel.warning, AlertDescription.close_notify); + throw new EOFException(); + } } catch (TlsFatalAlert e) { if (!this.closed) { - this.failWithError(AlertLevel.fatal, e.getAlertDescription()); + this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to read record", e); } throw e; } @@ -392,7 +462,7 @@ public abstract class TlsProtocol { if (!this.closed) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e); } throw e; } @@ -400,7 +470,7 @@ public abstract class TlsProtocol { if (!this.closed) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e); } throw e; } @@ -417,7 +487,7 @@ public abstract class TlsProtocol { if (!this.closed) { - this.failWithError(AlertLevel.fatal, e.getAlertDescription()); + this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to write record", e); } throw e; } @@ -425,7 +495,7 @@ public abstract class TlsProtocol { if (!closed) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e); } throw e; } @@ -433,7 +503,7 @@ public abstract class TlsProtocol { if (!closed) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e); } throw e; } @@ -467,25 +537,41 @@ public abstract class TlsProtocol /* * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are * potentially useful as a traffic analysis countermeasure. + * + * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting. */ - if (this.writeExtraEmptyRecords) + + if (this.splitApplicationDataRecords) { /* * Protect against known IV attack! * - * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. + * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. */ - safeWriteRecord(ContentType.application_data, TlsUtils.EMPTY_BYTES, 0, 0); + safeWriteRecord(ContentType.application_data, buf, offset, 1); + ++offset; + --len; } - /* - * We are only allowed to write fragments up to 2^14 bytes. - */ - int toWrite = Math.min(len, 1 << 14); - - safeWriteRecord(ContentType.application_data, buf, offset, toWrite); + if (len > 0) + { + // Fragment data according to the current fragment limit. + int toWrite = Math.min(len, recordStream.getPlaintextLimit()); + safeWriteRecord(ContentType.application_data, buf, offset, toWrite); + offset += toWrite; + len -= toWrite; + } + } + } - offset += toWrite; + protected void writeHandshakeMessage(byte[] buf, int off, int len) throws IOException + { + while (len > 0) + { + // Fragment data according to the current fragment limit. + int toWrite = Math.min(len, recordStream.getPlaintextLimit()); + safeWriteRecord(ContentType.handshake, buf, off, toWrite); + off += toWrite; len -= toWrite; } } @@ -507,15 +593,16 @@ public abstract class TlsProtocol } /** - * Terminate this connection with an alert. - * <p/> - * Can be used for normal closure too. - * - * @param alertLevel The level of the alert, an be AlertLevel.fatal or AL_warning. - * @param alertDescription The exact alert message. - * @throws IOException If alert was fatal. + * Terminate this connection with an alert. Can be used for normal closure too. + * + * @param alertLevel + * See {@link AlertLevel} for values. + * @param alertDescription + * See {@link AlertDescription} for values. + * @throws IOException + * If alert was fatal. */ - protected void failWithError(short alertLevel, short alertDescription) + protected void failWithError(short alertLevel, short alertDescription, String message, Exception cause) throws IOException { /* @@ -531,27 +618,43 @@ public abstract class TlsProtocol if (alertLevel == AlertLevel.fatal) { /* - * This is a fatal message. + * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated + * without proper close_notify messages with level equal to warning. */ + // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete. + invalidateSession(); + this.failedWithError = true; } - raiseAlert(alertLevel, alertDescription, null, null); - recordStream.close(); - if (alertLevel == AlertLevel.fatal) + raiseAlert(alertLevel, alertDescription, message, cause); + recordStream.safeClose(); + if (alertLevel != AlertLevel.fatal) { - throw new IOException(TLS_ERROR_MESSAGE); + return; } } - else + + throw new IOException(TLS_ERROR_MESSAGE); + } + + protected void invalidateSession() + { + if (this.sessionParameters != null) { - throw new IOException(TLS_ERROR_MESSAGE); + this.sessionParameters.clear(); + this.sessionParameters = null; + } + + if (this.tlsSession != null) + { + this.tlsSession.invalidate(); + this.tlsSession = null; } } protected void processFinishedMessage(ByteArrayInputStream buf) throws IOException { - byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf); assertEmpty(buf); @@ -564,14 +667,13 @@ public abstract class TlsProtocol /* * Wrong checksum in the finished message. */ - this.failWithError(AlertLevel.fatal, AlertDescription.decrypt_error); + throw new TlsFatalAlert(AlertDescription.decrypt_error); } } protected void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause) throws IOException { - getPeer().notifyAlertRaised(alertLevel, alertDescription, message, cause); byte[] error = new byte[2]; @@ -590,7 +692,6 @@ public abstract class TlsProtocol protected void sendCertificateMessage(Certificate certificate) throws IOException { - if (certificate == null) { certificate = Certificate.EMPTY_CHAIN; @@ -611,25 +712,17 @@ public abstract class TlsProtocol } } - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.certificate, bos); - - // Reserve space for length - TlsUtils.writeUint24(0, bos); - - certificate.encode(bos); - byte[] message = bos.toByteArray(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate); - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); + certificate.encode(message); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected void sendChangeCipherSpecMessage() throws IOException { - byte[] message = new byte[]{1}; + byte[] message = new byte[]{ 1 }; safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length); recordStream.sentWriteCipherSpec(); } @@ -639,33 +732,21 @@ public abstract class TlsProtocol { byte[] verify_data = createVerifyData(getContext().isServer()); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.finished, bos); - TlsUtils.writeUint24(verify_data.length, bos); - bos.write(verify_data); - byte[] message = bos.toByteArray(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.length); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.write(verify_data); + + message.writeToRecordStream(); } protected void sendSupplementalDataMessage(Vector supplementalData) throws IOException { + HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.supplemental_data, buf); - - // Reserve space for length - TlsUtils.writeUint24(0, buf); + writeSupplementalData(message, supplementalData); - writeSupplementalData(buf, supplementalData); - - byte[] message = buf.toByteArray(); - - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); - - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected byte[] createVerifyData(boolean isServer) @@ -674,12 +755,12 @@ public abstract class TlsProtocol if (isServer) { - return TlsUtils.calculateVerifyData(context, "server finished", - recordStream.getCurrentHash(TlsUtils.SSL_SERVER)); + return TlsUtils.calculateVerifyData(context, ExporterLabel.server_finished, + getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_SERVER)); } - return TlsUtils.calculateVerifyData(context, "client finished", - recordStream.getCurrentHash(TlsUtils.SSL_CLIENT)); + return TlsUtils.calculateVerifyData(context, ExporterLabel.client_finished, + getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_CLIENT)); } /** @@ -702,7 +783,7 @@ public abstract class TlsProtocol { raiseWarning(AlertDescription.user_canceled, "User canceled handshake"); } - this.failWithError(AlertLevel.warning, AlertDescription.close_notify); + this.failWithError(AlertLevel.warning, AlertDescription.close_notify, "Connection closed", null); } } @@ -712,28 +793,18 @@ public abstract class TlsProtocol recordStream.flush(); } - protected static boolean arrayContains(short[] a, short n) - { - for (int i = 0; i < a.length; ++i) - { - if (a[i] == n) - { - return true; - } - } - return false; - } - - protected static boolean arrayContains(int[] a, int n) + protected short processMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions, short alertDescription) + throws IOException { - for (int i = 0; i < a.length; ++i) + short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions); + if (maxFragmentLength >= 0 && !this.resumedSession) { - if (a[i] == n) + if (maxFragmentLength != TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions)) { - return true; + throw new TlsFatalAlert(alertDescription); } } - return false; + return maxFragmentLength; } /** @@ -753,25 +824,28 @@ public abstract class TlsProtocol protected static byte[] createRandomBlock(SecureRandom random) { + random.setSeed(System.currentTimeMillis()); + byte[] result = new byte[32]; random.nextBytes(result); - TlsUtils.writeGMTUnixTime(result, 0); + /* + * The consensus seems to be that using the time here is neither all that useful, nor + * secure. Perhaps there could be an option to (re-)enable it. Instead, we seed the random + * source with the current time to retain it's main benefit. + */ +// TlsUtils.writeGMTUnixTime(result, 0); return result; } protected static byte[] createRenegotiationInfo(byte[] renegotiated_connection) throws IOException { - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeOpaque8(renegotiated_connection, buf); - return buf.toByteArray(); + return TlsUtils.encodeOpaque8(renegotiated_connection); } protected static void establishMasterSecret(TlsContext context, TlsKeyExchange keyExchange) throws IOException { - byte[] pre_master_secret = keyExchange.generatePremasterSecret(); try @@ -792,10 +866,26 @@ public abstract class TlsProtocol } } + /** + * 'sender' only relevant to SSLv3 + */ + protected static byte[] getCurrentPRFHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender) + { + Digest d = handshakeHash.forkPRFHash(); + + if (sslSender != null && TlsUtils.isSSL(context)) + { + d.update(sslSender, 0, sslSender.length); + } + + byte[] bs = new byte[d.getDigestSize()]; + d.doFinal(bs, 0); + return bs; + } + protected static Hashtable readExtensions(ByteArrayInputStream input) throws IOException { - if (input.available() < 1) { return null; @@ -812,13 +902,13 @@ public abstract class TlsProtocol while (buf.available() > 0) { - Integer extType = Integers.valueOf(TlsUtils.readUint16(buf)); - byte[] extValue = TlsUtils.readOpaque16(buf); + Integer extension_type = Integers.valueOf(TlsUtils.readUint16(buf)); + byte[] extension_data = TlsUtils.readOpaque16(buf); /* * RFC 3546 2.3 There MUST NOT be more than one extension of the same type. */ - if (null != extensions.put(extType, extValue)) + if (null != extensions.put(extension_type, extension_data)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); } @@ -830,7 +920,6 @@ public abstract class TlsProtocol protected static Vector readSupplementalDataMessage(ByteArrayInputStream input) throws IOException { - byte[] supp_data = TlsUtils.readOpaque24(input); assertEmpty(input); @@ -853,17 +942,18 @@ public abstract class TlsProtocol protected static void writeExtensions(OutputStream output, Hashtable extensions) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); Enumeration keys = extensions.keys(); while (keys.hasMoreElements()) { - Integer extType = (Integer)keys.nextElement(); - byte[] extValue = (byte[])extensions.get(extType); + Integer key = (Integer)keys.nextElement(); + int extension_type = key.intValue(); + byte[] extension_data = (byte[])extensions.get(key); - TlsUtils.writeUint16(extType.intValue(), buf); - TlsUtils.writeOpaque16(extValue, buf); + TlsUtils.checkUint16(extension_type); + TlsUtils.writeUint16(extension_type, buf); + TlsUtils.writeOpaque16(extension_data, buf); } byte[] extBytes = buf.toByteArray(); @@ -874,14 +964,15 @@ public abstract class TlsProtocol protected static void writeSupplementalData(OutputStream output, Vector supplementalData) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); for (int i = 0; i < supplementalData.size(); ++i) { SupplementalDataEntry entry = (SupplementalDataEntry)supplementalData.elementAt(i); - TlsUtils.writeUint16(entry.getDataType(), buf); + int supp_data_type = entry.getDataType(); + TlsUtils.checkUint16(supp_data_type); + TlsUtils.writeUint16(supp_data_type, buf); TlsUtils.writeOpaque16(entry.getData(), buf); } @@ -890,54 +981,137 @@ public abstract class TlsProtocol TlsUtils.writeOpaque24(supp_data, output); } - protected static int getPRFAlgorithm(int ciphersuite) + protected static int getPRFAlgorithm(TlsContext context, int ciphersuite) throws IOException { + boolean isTLSv12 = TlsUtils.isTLSv12(context); switch (ciphersuite) { case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: - case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: - case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: - case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: - case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: - case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_NULL_SHA256: - return PRFAlgorithm.tls_prf_sha256; + { + if (isTLSv12) + { + return PRFAlgorithm.tls_prf_sha256; + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } - case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: - case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: - case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: - case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: - return PRFAlgorithm.tls_prf_sha384; + { + if (isTLSv12) + { + return PRFAlgorithm.tls_prf_sha384; + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + { + if (isTLSv12) + { + return PRFAlgorithm.tls_prf_sha384; + } + return PRFAlgorithm.tls_prf_legacy; + } default: + { + if (isTLSv12) + { + return PRFAlgorithm.tls_prf_sha256; + } return PRFAlgorithm.tls_prf_legacy; } + } + } + + class HandshakeMessage extends ByteArrayOutputStream + { + HandshakeMessage(short handshakeType) throws IOException + { + this(handshakeType, 60); + } + + HandshakeMessage(short handshakeType, int length) throws IOException + { + super(length + 4); + TlsUtils.writeUint8(handshakeType, this); + // Reserve space for length + count += 3; + } + + void writeToRecordStream() throws IOException + { + // Patch actual length back in + int length = count - 4; + TlsUtils.checkUint24(length); + TlsUtils.writeUint24(length, buf, 1); + writeHandshakeMessage(buf, 0, count); + buf = null; + } } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java index 24eec53..8970968 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java @@ -40,7 +40,6 @@ public class TlsRSAKeyExchange public void processServerCredentials(TlsCredentials serverCredentials) throws IOException { - if (!(serverCredentials instanceof TlsEncryptionCredentials)) { throw new TlsFatalAlert(AlertDescription.internal_error); @@ -54,7 +53,6 @@ public class TlsRSAKeyExchange public void processServerCertificate(Certificate serverCertificate) throws IOException { - if (serverCertificate.isEmpty()) { throw new TlsFatalAlert(AlertDescription.bad_certificate); @@ -115,15 +113,14 @@ public class TlsRSAKeyExchange public void generateClientKeyExchange(OutputStream output) throws IOException { - this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, output); + this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, rsaServerPublicKey, output); } public void processClientKeyExchange(InputStream input) throws IOException { - byte[] encryptedPreMasterSecret; - if (context.getServerVersion().isSSL()) + if (TlsUtils.isSSL(context)) { // TODO Do any SSLv3 clients actually include the length? encryptedPreMasterSecret = Streams.readAll(input); @@ -133,68 +130,7 @@ public class TlsRSAKeyExchange encryptedPreMasterSecret = TlsUtils.readOpaque16(input); } - ProtocolVersion clientVersion = context.getClientVersion(); - - /* - * RFC 5246 7.4.7.1. - */ - { - // TODO Provide as configuration option? - boolean versionNumberCheckDisabled = false; - - /* - * See notes regarding Bleichenbacher/Klima attack. The code here implements the first - * construction proposed there, which is RECOMMENDED. - */ - byte[] R = new byte[48]; - this.context.getSecureRandom().nextBytes(R); - - byte[] M = TlsUtils.EMPTY_BYTES; - try - { - M = serverCredentials.decryptPreMasterSecret(encryptedPreMasterSecret); - } - catch (Exception e) - { - /* - * In any case, a TLS server MUST NOT generate an alert if processing an - * RSA-encrypted premaster secret message fails, or the version number is not as - * expected. Instead, it MUST continue the handshake with a randomly generated - * premaster secret. - */ - } - - if (M.length != 48) - { - TlsUtils.writeVersion(clientVersion, R, 0); - this.premasterSecret = R; - } - else - { - /* - * If ClientHello.client_version is TLS 1.1 or higher, server implementations MUST - * check the version number [..]. - */ - if (versionNumberCheckDisabled && clientVersion.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10)) - { - /* - * If the version number is TLS 1.0 or earlier, server implementations SHOULD - * check the version number, but MAY have a configuration option to disable the - * check. - */ - } - else - { - /* - * Note that explicitly constructing the pre_master_secret with the - * ClientHello.client_version produces an invalid master_secret if the client - * has sent the wrong version in the original pre_master_secret. - */ - TlsUtils.writeVersion(clientVersion, M, 0); - } - this.premasterSecret = M; - } - } + this.premasterSecret = TlsRSAUtils.safeDecryptPreMasterSecret(context, serverCredentials, encryptedPreMasterSecret); } public byte[] generatePremasterSecret() diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java index d9f7975..35538a7 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSASigner.java @@ -5,6 +5,7 @@ import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.digests.NullDigest; import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.RSABlindedEngine; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; @@ -12,40 +13,37 @@ import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.signers.GenericSigner; import org.bouncycastle.crypto.signers.RSADigestSigner; -import org.bouncycastle.util.Arrays; public class TlsRSASigner extends AbstractTlsSigner { - - public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + public byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash) throws CryptoException { - - AsymmetricBlockCipher engine = createRSAImpl(); - engine.init(true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); - return engine.processBlock(md5AndSha1, 0, md5AndSha1.length); + Signer signer = makeSigner(algorithm, true, true, + new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + signer.update(hash, 0, hash.length); + return signer.generateSignature(); } - public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + public boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash) throws CryptoException { - - AsymmetricBlockCipher engine = createRSAImpl(); - engine.init(false, publicKey); - byte[] signed = engine.processBlock(sigBytes, 0, sigBytes.length); - return Arrays.constantTimeAreEqual(signed, md5AndSha1); + Signer signer = makeSigner(algorithm, true, false, publicKey); + signer.update(hash, 0, hash.length); + return signer.verifySignature(sigBytes); } - public Signer createSigner(AsymmetricKeyParameter privateKey) + public Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey) { - return makeSigner(new CombinedHash(), true, - new ParametersWithRandom(privateKey, this.context.getSecureRandom())); + return makeSigner(algorithm, false, true, new ParametersWithRandom(privateKey, this.context.getSecureRandom())); } - public Signer createVerifyer(AsymmetricKeyParameter publicKey) + public Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey) { - return makeSigner(new CombinedHash(), false, publicKey); + return makeSigner(algorithm, false, false, publicKey); } public boolean isValidPublicKey(AsymmetricKeyParameter publicKey) @@ -53,16 +51,41 @@ public class TlsRSASigner return publicKey instanceof RSAKeyParameters && !publicKey.isPrivate(); } - protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp) + protected Signer makeSigner(SignatureAndHashAlgorithm algorithm, boolean raw, boolean forSigning, + CipherParameters cp) { + if ((algorithm != null) != TlsUtils.isTLSv12(context)) + { + throw new IllegalStateException(); + } + + if (algorithm != null && algorithm.getSignature() != SignatureAlgorithm.rsa) + { + throw new IllegalStateException(); + } + + Digest d; + if (raw) + { + d = new NullDigest(); + } + else if (algorithm == null) + { + d = new CombinedHash(); + } + else + { + d = TlsUtils.createHash(algorithm.getHash()); + } + Signer s; - if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion())) + if (algorithm != null) { /* * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1]. */ - s = new RSADigestSigner(d); + s = new RSADigestSigner(d, TlsUtils.getOIDForHashAlgorithm(algorithm.getHash())); } else { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java index f67e572..e3856bd 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsRSAUtils.java @@ -12,8 +12,7 @@ import org.bouncycastle.crypto.params.RSAKeyParameters; public class TlsRSAUtils { public static byte[] generateEncryptedPreMasterSecret(TlsContext context, RSAKeyParameters rsaServerPublicKey, - OutputStream output) - throws IOException + OutputStream output) throws IOException { /* * Choose a PremasterSecret and send it encrypted to the server @@ -29,7 +28,7 @@ public class TlsRSAUtils { byte[] encryptedPreMasterSecret = encoding.processBlock(premasterSecret, 0, premasterSecret.length); - if (context.getServerVersion().isSSL()) + if (TlsUtils.isSSL(context)) { // TODO Do any SSLv3 servers actually expect the length? output.write(encryptedPreMasterSecret); @@ -49,4 +48,69 @@ public class TlsRSAUtils return premasterSecret; } + + public static byte[] safeDecryptPreMasterSecret(TlsContext context, TlsEncryptionCredentials encryptionCredentials, + byte[] encryptedPreMasterSecret) + { + /* + * RFC 5246 7.4.7.1. + */ + + ProtocolVersion clientVersion = context.getClientVersion(); + + // TODO Provide as configuration option? + boolean versionNumberCheckDisabled = false; + + /* + * See notes regarding Bleichenbacher/Klima attack. The code here implements the first + * construction proposed there, which is RECOMMENDED. + */ + byte[] R = new byte[48]; + context.getSecureRandom().nextBytes(R); + + byte[] M = TlsUtils.EMPTY_BYTES; + try + { + M = encryptionCredentials.decryptPreMasterSecret(encryptedPreMasterSecret); + } + catch (Exception e) + { + /* + * In any case, a TLS server MUST NOT generate an alert if processing an + * RSA-encrypted premaster secret message fails, or the version number is not as + * expected. Instead, it MUST continue the handshake with a randomly generated + * premaster secret. + */ + } + + if (M.length != 48) + { + TlsUtils.writeVersion(clientVersion, R, 0); + return R; + } + + /* + * If ClientHello.client_version is TLS 1.1 or higher, server implementations MUST + * check the version number [..]. + */ + if (versionNumberCheckDisabled && clientVersion.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10)) + { + /* + * If the version number is TLS 1.0 or earlier, server implementations SHOULD + * check the version number, but MAY have a configuration option to disable the + * check. + */ + } + else + { + /* + * Note that explicitly constructing the pre_master_secret with the + * ClientHello.client_version produces an invalid master_secret if the client + * has sent the wrong version in the original pre_master_secret. + */ + TlsUtils.writeVersion(clientVersion, M, 0); + } + + return M; + } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java index b928b91..452fbf9 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java @@ -13,18 +13,16 @@ import org.bouncycastle.crypto.Signer; import org.bouncycastle.crypto.agreement.srp.SRP6Client; import org.bouncycastle.crypto.agreement.srp.SRP6Util; import org.bouncycastle.crypto.digests.SHA1Digest; -import org.bouncycastle.crypto.io.SignerInputStream; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.PublicKeyFactory; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.io.TeeInputStream; /** * TLS 1.1 SRP key exchange (RFC 5054). */ -public class TlsSRPKeyExchange - extends AbstractTlsKeyExchange +public class TlsSRPKeyExchange extends AbstractTlsKeyExchange { - protected TlsSigner tlsSigner; protected byte[] identity; protected byte[] password; @@ -37,7 +35,6 @@ public class TlsSRPKeyExchange public TlsSRPKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, byte[] identity, byte[] password) { - super(keyExchange, supportedSignatureAlgorithms); switch (keyExchange) @@ -64,14 +61,12 @@ public class TlsSRPKeyExchange { super.init(context); - if (this.tlsSigner != null) - { + if (this.tlsSigner != null) { this.tlsSigner.init(context); } } - public void skipServerCredentials() - throws IOException + public void skipServerCredentials() throws IOException { if (tlsSigner != null) { @@ -79,10 +74,8 @@ public class TlsSRPKeyExchange } } - public void processServerCertificate(Certificate serverCertificate) - throws IOException + public void processServerCertificate(Certificate serverCertificate) throws IOException { - if (tlsSigner == null) { throw new TlsFatalAlert(AlertDescription.unexpected_message); @@ -119,31 +112,31 @@ public class TlsSRPKeyExchange return true; } - public void processServerKeyExchange(InputStream input) - throws IOException + public void processServerKeyExchange(InputStream input) throws IOException { - SecurityParameters securityParameters = context.getSecurityParameters(); - InputStream sigIn = input; - Signer signer = null; + SignerInputBuffer buf = null; + InputStream teeIn = input; if (tlsSigner != null) { - signer = initVerifyer(tlsSigner, securityParameters); - sigIn = new SignerInputStream(input, signer); + buf = new SignerInputBuffer(); + teeIn = new TeeInputStream(input, buf); } - byte[] NBytes = TlsUtils.readOpaque16(sigIn); - byte[] gBytes = TlsUtils.readOpaque16(sigIn); - byte[] sBytes = TlsUtils.readOpaque8(sigIn); - byte[] BBytes = TlsUtils.readOpaque16(sigIn); + byte[] NBytes = TlsUtils.readOpaque16(teeIn); + byte[] gBytes = TlsUtils.readOpaque16(teeIn); + byte[] sBytes = TlsUtils.readOpaque8(teeIn); + byte[] BBytes = TlsUtils.readOpaque16(teeIn); - if (signer != null) + if (buf != null) { - byte[] sigByte = TlsUtils.readOpaque16(input); + DigitallySigned signed_params = DigitallySigned.parse(context, input); - if (!signer.verifySignature(sigByte)) + Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters); + buf.updateSigner(signer); + if (!signer.verifySignature(signed_params.getSignature())) { throw new TlsFatalAlert(AlertDescription.decrypt_error); } @@ -153,7 +146,7 @@ public class TlsSRPKeyExchange BigInteger g = new BigInteger(1, gBytes); // TODO Validate group parameters (see RFC 5054) - // handler.failWithError(AlertLevel.fatal, AlertDescription.insufficient_security); +// throw new TlsFatalAlert(AlertDescription.insufficient_security); this.s = sBytes; @@ -173,28 +166,23 @@ public class TlsSRPKeyExchange this.srpClient.init(N, g, new SHA1Digest(), context.getSecureRandom()); } - public void validateCertificateRequest(CertificateRequest certificateRequest) - throws IOException + public void validateCertificateRequest(CertificateRequest certificateRequest) throws IOException { throw new TlsFatalAlert(AlertDescription.unexpected_message); } - public void processClientCredentials(TlsCredentials clientCredentials) - throws IOException + public void processClientCredentials(TlsCredentials clientCredentials) throws IOException { throw new TlsFatalAlert(AlertDescription.internal_error); } - public void generateClientKeyExchange(OutputStream output) - throws IOException + public void generateClientKeyExchange(OutputStream output) throws IOException { - byte[] keData = BigIntegers.asUnsignedByteArray(srpClient.generateClientCredentials(s, this.identity, - this.password)); - TlsUtils.writeOpaque16(keData, output); + BigInteger A = srpClient.generateClientCredentials(s, this.identity, this.password); + TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(A), output); } - public byte[] generatePremasterSecret() - throws IOException + public byte[] generatePremasterSecret() throws IOException { try { @@ -207,9 +195,9 @@ public class TlsSRPKeyExchange } } - protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters) + protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters) { - Signer signer = tlsSigner.createVerifyer(this.serverPublicKey); + Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey); signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length); signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length); return signer; diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPUtils.java new file mode 100644 index 0000000..7fe5fb8 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRPUtils.java @@ -0,0 +1,49 @@ +package org.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.util.Integers; + +public class TlsSRPUtils +{ + public static final Integer EXT_SRP = Integers.valueOf(ExtensionType.srp); + + public static void addSRPExtension(Hashtable extensions, byte[] identity) throws IOException + { + extensions.put(EXT_SRP, createSRPExtension(identity)); + } + + public static byte[] getSRPExtension(Hashtable extensions) throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_SRP); + return extensionData == null ? null : readSRPExtension(extensionData); + } + + public static byte[] createSRPExtension(byte[] identity) throws IOException + { + if (identity == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return TlsUtils.encodeOpaque8(identity); + } + + public static byte[] readSRPExtension(byte[] extensionData) throws IOException + { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); + byte[] identity = TlsUtils.readOpaque8(buf); + + TlsProtocol.assertEmpty(buf); + + return identity; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java index f82f94d..da98b7a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSRTPUtils.java @@ -12,36 +12,24 @@ import org.bouncycastle.util.Integers; */ public class TlsSRTPUtils { - public static final Integer EXT_use_srtp = Integers.valueOf(ExtensionType.use_srtp); public static void addUseSRTPExtension(Hashtable extensions, UseSRTPData useSRTPData) throws IOException { - extensions.put(EXT_use_srtp, createUseSRTPExtension(useSRTPData)); } public static UseSRTPData getUseSRTPExtension(Hashtable extensions) throws IOException { - - if (extensions == null) - { - return null; - } - byte[] extensionValue = (byte[])extensions.get(EXT_use_srtp); - if (extensionValue == null) - { - return null; - } - return readUseSRTPExtension(extensionValue); + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_use_srtp); + return extensionData == null ? null : readUseSRTPExtension(extensionData); } public static byte[] createUseSRTPExtension(UseSRTPData useSRTPData) throws IOException { - if (useSRTPData == null) { throw new IllegalArgumentException("'useSRTPData' cannot be null"); @@ -50,9 +38,7 @@ public class TlsSRTPUtils ByteArrayOutputStream buf = new ByteArrayOutputStream(); // SRTPProtectionProfiles - int[] protectionProfiles = useSRTPData.getProtectionProfiles(); - TlsUtils.writeUint16(2 * protectionProfiles.length, buf); - TlsUtils.writeUint16Array(protectionProfiles, buf); + TlsUtils.writeUint16ArrayWithUint16Length(useSRTPData.getProtectionProfiles(), buf); // srtp_mki TlsUtils.writeOpaque8(useSRTPData.getMki(), buf); @@ -60,16 +46,15 @@ public class TlsSRTPUtils return buf.toByteArray(); } - public static UseSRTPData readUseSRTPExtension(byte[] extensionValue) + public static UseSRTPData readUseSRTPExtension(byte[] extensionData) throws IOException { - - if (extensionValue == null) + if (extensionData == null) { - throw new IllegalArgumentException("'extensionValue' cannot be null"); + throw new IllegalArgumentException("'extensionData' cannot be null"); } - ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); // SRTPProtectionProfiles int length = TlsUtils.readUint16(buf); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java index 0b46391..85c0a9a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServer.java @@ -7,11 +7,9 @@ import java.util.Vector; public interface TlsServer extends TlsPeer { - void init(TlsServerContext context); - void notifyClientVersion(ProtocolVersion clientVersion) - throws IOException; + void notifyClientVersion(ProtocolVersion clientVersion) throws IOException; void notifyOfferedCipherSuites(int[] offeredCipherSuites) throws IOException; @@ -19,9 +17,6 @@ public interface TlsServer void notifyOfferedCompressionMethods(short[] offeredCompressionMethods) throws IOException; - void notifySecureRenegotiation(boolean secureNegotiation) - throws IOException; - // Hashtable is (Integer -> byte[]) void processClientExtensions(Hashtable clientExtensions) throws IOException; @@ -46,32 +41,41 @@ public interface TlsServer TlsCredentials getCredentials() throws IOException; + /** + * This method will be called (only) if the server included an extension of type + * "status_request" with empty "extension_data" in the extended server hello. See <i>RFC 3546 + * 3.6. Certificate Status Request</i>. If a non-null {@link CertificateStatus} is returned, it + * is sent to the client as a handshake message of type "certificate_status". + * + * @return A {@link CertificateStatus} to be sent to the client (or null for none). + * @throws IOException + */ + CertificateStatus getCertificateStatus() + throws IOException; + TlsKeyExchange getKeyExchange() throws IOException; - CertificateRequest getCertificateRequest(); + CertificateRequest getCertificateRequest() + throws IOException; // Vector is (SupplementalDataEntry) void processClientSupplementalData(Vector clientSupplementalData) throws IOException; /** - * Called by the protocol handler to report the client certificate, only if a Certificate - * {@link #getCertificateRequest()} returned non-null. Note: this method is responsible for - * certificate verification and validation. - * - * @param clientCertificate the effective client certificate (may be an empty chain). + * Called by the protocol handler to report the client certificate, only if + * {@link #getCertificateRequest()} returned non-null. + * + * Note: this method is responsible for certificate verification and validation. + * + * @param clientCertificate + * the effective client certificate (may be an empty chain). * @throws IOException */ void notifyClientCertificate(Certificate clientCertificate) throws IOException; - TlsCompression getCompression() - throws IOException; - - TlsCipher getCipher() - throws IOException; - /** * RFC 5077 3.3. NewSessionTicket Handshake Message. * <p/> @@ -83,7 +87,4 @@ public interface TlsServer */ NewSessionTicket getNewSessionTicket() throws IOException; - - void notifyHandshakeComplete() - throws IOException; } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java index 2fa4029..48f028a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerContextImpl.java @@ -6,7 +6,6 @@ class TlsServerContextImpl extends AbstractTlsContext implements TlsServerContext { - TlsServerContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) { super(secureRandom, securityParameters); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java index 961669f..056d22a 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsServerProtocol.java @@ -1,12 +1,10 @@ package org.bouncycastle.crypto.tls; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; -import java.util.Hashtable; import java.util.Vector; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; @@ -17,25 +15,15 @@ import org.bouncycastle.util.Arrays; public class TlsServerProtocol extends TlsProtocol { - protected TlsServer tlsServer = null; protected TlsServerContextImpl tlsServerContext = null; - protected int[] offeredCipherSuites; - protected short[] offeredCompressionMethods; - protected Hashtable clientExtensions; - - protected int selectedCipherSuite; - protected short selectedCompressionMethod; - protected Hashtable serverExtensions; - protected TlsKeyExchange keyExchange = null; protected TlsCredentials serverCredentials = null; protected CertificateRequest certificateRequest = null; protected short clientCertificateType = -1; - protected Certificate clientCertificate = null; - protected byte[] certificateVerifyHash = null; + protected TlsHandshakeHash prepareFinishHash = null; public TlsServerProtocol(InputStream input, OutputStream output, SecureRandom secureRandom) { @@ -51,14 +39,13 @@ public class TlsServerProtocol public void accept(TlsServer tlsServer) throws IOException { - if (tlsServer == null) { throw new IllegalArgumentException("'tlsServer' cannot be null"); } if (this.tlsServer != null) { - throw new IllegalStateException("accept can only be called once"); + throw new IllegalStateException("'accept' can only be called once"); } this.tlsServer = tlsServer; @@ -74,8 +61,16 @@ public class TlsServerProtocol this.recordStream.setRestrictReadVersion(false); completeHandshake(); + } - this.tlsServer.notifyHandshakeComplete(); + protected void cleanupHandshake() + { + super.cleanupHandshake(); + + this.keyExchange = null; + this.serverCredentials = null; + this.certificateRequest = null; + this.prepareFinishHash = null; } protected AbstractTlsContext getContext() @@ -88,36 +83,9 @@ public class TlsServerProtocol return tlsServer; } - protected void handleChangeCipherSpecMessage() - throws IOException - { - - switch (this.connection_state) - { - case CS_CLIENT_KEY_EXCHANGE: - { - if (this.certificateVerifyHash != null) - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } - // NB: Fall through to next case label - } - case CS_CERTIFICATE_VERIFY: - { - this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC; - break; - } - default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); - } - } - } - protected void handleHandshakeMessage(short type, byte[] data) throws IOException { - ByteArrayInputStream buf = new ByteArrayInputStream(data); switch (type) @@ -134,21 +102,6 @@ public class TlsServerProtocol sendServerHelloMessage(); this.connection_state = CS_SERVER_HELLO; - // TODO This block could really be done before actually sending the hello - { - securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite); - securityParameters.compressionAlgorithm = this.selectedCompressionMethod; - - /* - * 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. - */ - securityParameters.verifyDataLength = 12; - - recordStream.notifyHelloComplete(); - } - Vector serverSupplementalData = tlsServer.getServerSupplementalData(); if (serverSupplementalData != null) { @@ -160,6 +113,9 @@ public class TlsServerProtocol this.keyExchange.init(getContext()); this.serverCredentials = tlsServer.getCredentials(); + + Certificate serverCertificate = null; + if (this.serverCredentials == null) { this.keyExchange.skipServerCredentials(); @@ -167,10 +123,29 @@ public class TlsServerProtocol else { this.keyExchange.processServerCredentials(this.serverCredentials); - sendCertificateMessage(this.serverCredentials.getCertificate()); + + serverCertificate = this.serverCredentials.getCertificate(); + sendCertificateMessage(serverCertificate); } this.connection_state = CS_SERVER_CERTIFICATE; + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.isEmpty()) + { + this.allowCertificateStatus = false; + } + + if (this.allowCertificateStatus) + { + CertificateStatus certificateStatus = tlsServer.getCertificateStatus(); + if (certificateStatus != null) + { + sendCertificateStatusMessage(certificateStatus); + } + } + + this.connection_state = CS_CERTIFICATE_STATUS; + byte[] serverKeyExchange = this.keyExchange.generateServerKeyExchange(); if (serverKeyExchange != null) { @@ -184,7 +159,11 @@ public class TlsServerProtocol if (this.certificateRequest != null) { this.keyExchange.validateCertificateRequest(certificateRequest); + sendCertificateRequestMessage(certificateRequest); + + TlsUtils.trackHashAlgorithms(this.recordStream.getHandshakeHash(), + this.certificateRequest.getSupportedSignatureAlgorithms()); } } this.connection_state = CS_CERTIFICATE_REQUEST; @@ -192,12 +171,12 @@ public class TlsServerProtocol sendServerHelloDoneMessage(); this.connection_state = CS_SERVER_HELLO_DONE; + this.recordStream.getHandshakeHash().sealHashAlgorithms(); + break; } default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -212,9 +191,7 @@ public class TlsServerProtocol break; } default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -231,16 +208,14 @@ public class TlsServerProtocol { if (this.certificateRequest == null) { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } receiveCertificateMessage(buf); this.connection_state = CS_CLIENT_CERTIFICATE; break; } default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -261,10 +236,7 @@ public class TlsServerProtocol } else { - - ProtocolVersion equivalentTLSVersion = getContext().getServerVersion().getEquivalentTLSVersion(); - - if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion)) + if (TlsUtils.isTLSv12(getContext())) { /* * RFC 5246 If no suitable certificate is available, the client MUST send a @@ -272,13 +244,13 @@ public class TlsServerProtocol * * NOTE: In previous RFCs, this was SHOULD instead of MUST. */ - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } - else if (equivalentTLSVersion.isSSL()) + else if (TlsUtils.isSSL(getContext())) { - if (clientCertificate == null) + if (this.peerCertificate == null) { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } } else @@ -295,9 +267,7 @@ public class TlsServerProtocol break; } default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -312,18 +282,18 @@ public class TlsServerProtocol * signing capability (i.e., all certificates except those containing fixed * Diffie-Hellman parameters). */ - if (this.certificateVerifyHash == null) + if (!expectCertificateVerifyMessage()) { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } + receiveCertificateVerifyMessage(buf); this.connection_state = CS_CERTIFICATE_VERIFY; + break; } default: - { - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - } + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -331,24 +301,33 @@ public class TlsServerProtocol { switch (this.connection_state) { - case CS_CLIENT_CHANGE_CIPHER_SPEC: + case CS_CLIENT_KEY_EXCHANGE: + { + if (expectCertificateVerifyMessage()) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + // NB: Fall through to next case label + } + case CS_CERTIFICATE_VERIFY: + { processFinishedMessage(buf); this.connection_state = CS_CLIENT_FINISHED; - if (expectSessionTicket) + if (this.expectSessionTicket) { sendNewSessionTicketMessage(tlsServer.getNewSessionTicket()); + sendChangeCipherSpecMessage(); } this.connection_state = CS_SERVER_SESSION_TICKET; - sendChangeCipherSpecMessage(); - this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC; - sendFinishedMessage(); this.connection_state = CS_SERVER_FINISHED; + this.connection_state = CS_END; break; + } default: - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + throw new TlsFatalAlert(AlertDescription.unexpected_message); } break; } @@ -360,9 +339,7 @@ public class TlsServerProtocol case HandshakeType.server_hello_done: case HandshakeType.session_ticket: default: - // We do not support this! - this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message); - break; + throw new TlsFatalAlert(AlertDescription.unexpected_message); } } @@ -377,7 +354,7 @@ public class TlsServerProtocol * SSL 3.0 If the server has sent a certificate request Message, the client must send * either the certificate message or a no_certificate alert. */ - if (getContext().getServerVersion().isSSL() && certificateRequest != null) + if (TlsUtils.isSSL(getContext()) && certificateRequest != null) { notifyClientCertificate(Certificate.EMPTY_CHAIN); } @@ -393,18 +370,17 @@ public class TlsServerProtocol protected void notifyClientCertificate(Certificate clientCertificate) throws IOException { - if (certificateRequest == null) { throw new IllegalStateException(); } - if (this.clientCertificate != null) + if (this.peerCertificate != null) { throw new TlsFatalAlert(AlertDescription.unexpected_message); } - this.clientCertificate = clientCertificate; + this.peerCertificate = clientCertificate; if (clientCertificate.isEmpty()) { @@ -439,7 +415,6 @@ public class TlsServerProtocol protected void receiveCertificateMessage(ByteArrayInputStream buf) throws IOException { - Certificate clientCertificate = Certificate.parse(buf); assertEmpty(buf); @@ -450,22 +425,24 @@ public class TlsServerProtocol protected void receiveCertificateVerifyMessage(ByteArrayInputStream buf) throws IOException { - - byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf); + DigitallySigned clientCertificateVerify = DigitallySigned.parse(getContext(), buf); assertEmpty(buf); // Verify the CertificateVerify message contains a correct signature. try { - TlsSigner tlsSigner = TlsUtils.createTlsSigner(this.clientCertificateType); - tlsSigner.init(getContext()); + // TODO For TLS 1.2, this needs to be the hash specified in the DigitallySigned + byte[] certificateVerifyHash = getCurrentPRFHash(getContext(), prepareFinishHash, null); - org.bouncycastle.asn1.x509.Certificate x509Cert = this.clientCertificate.getCertificateAt(0); + org.bouncycastle.asn1.x509.Certificate x509Cert = this.peerCertificate.getCertificateAt(0); SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo); - tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, this.certificateVerifyHash); + TlsSigner tlsSigner = TlsUtils.createTlsSigner(this.clientCertificateType); + tlsSigner.init(getContext()); + tlsSigner.verifyRawSignature(clientCertificateVerify.getAlgorithm(), + clientCertificateVerify.getSignature(), publicKey, certificateVerifyHash); } catch (Exception e) { @@ -476,42 +453,45 @@ public class TlsServerProtocol protected void receiveClientHelloMessage(ByteArrayInputStream buf) throws IOException { - ProtocolVersion client_version = TlsUtils.readVersion(buf); if (client_version.isDTLS()) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - /* - * Read the client random - */ byte[] client_random = TlsUtils.readFully(32, buf); + /* + * TODO RFC 5077 3.4. If a ticket is presented by the client, the server MUST NOT attempt to + * use the Session ID in the ClientHello for stateful session resumption. + */ byte[] sessionID = TlsUtils.readOpaque8(buf); if (sessionID.length > 32) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } + /* + * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session + * resumption request), this vector MUST include at least the cipher_suite from that + * session. + */ int cipher_suites_length = TlsUtils.readUint16(buf); if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) { - this.failWithError(AlertLevel.fatal, AlertDescription.decode_error); + throw new TlsFatalAlert(AlertDescription.decode_error); } + this.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf); /* - * NOTE: "If the session_id field is not empty (implying a session resumption request) this - * vector must include at least the cipher_suite from that session." + * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session + * resumption request), it MUST include the compression_method from that session. */ - this.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf); - int compression_methods_length = TlsUtils.readUint8(buf); if (compression_methods_length < 1) { - this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + throw new TlsFatalAlert(AlertDescription.illegal_parameter); } - this.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf); /* @@ -545,7 +525,7 @@ public class TlsServerProtocol * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag * to TRUE. */ - if (arrayContains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + if (Arrays.contains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) { this.secure_renegotiation = true; } @@ -554,22 +534,19 @@ public class TlsServerProtocol * The server MUST check if the "renegotiation_info" extension is included in the * ClientHello. */ - if (clientExtensions != null) + byte[] renegExtData = TlsUtils.getExtensionData(clientExtensions, EXT_RenegotiationInfo); + if (renegExtData != null) { - byte[] renegExtValue = (byte[])clientExtensions.get(EXT_RenegotiationInfo); - if (renegExtValue != null) + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + this.secure_renegotiation = true; + + if (!Arrays.constantTimeAreEqual(renegExtData, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) { - /* - * If the extension is present, set secure_renegotiation flag to TRUE. The - * server MUST then verify that the length of the "renegotiated_connection" - * field is zero, and if it is not, MUST abort the handshake. - */ - this.secure_renegotiation = true; - - if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES))) - { - this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure); - } + throw new TlsFatalAlert(AlertDescription.handshake_failure); } } } @@ -585,81 +562,65 @@ public class TlsServerProtocol protected void receiveClientKeyExchangeMessage(ByteArrayInputStream buf) throws IOException { - this.keyExchange.processClientKeyExchange(buf); assertEmpty(buf); establishMasterSecret(getContext(), keyExchange); + recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher()); - /* - * Initialize our cipher suite - */ - recordStream.setPendingConnectionState(tlsServer.getCompression(), tlsServer.getCipher()); + this.prepareFinishHash = recordStream.prepareToFinish(); - if (expectCertificateVerifyMessage()) + if (!expectSessionTicket) { - this.certificateVerifyHash = recordStream.getCurrentHash(null); + sendChangeCipherSpecMessage(); } } protected void sendCertificateRequestMessage(CertificateRequest certificateRequest) throws IOException { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_request); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.certificate_request, buf); + certificateRequest.encode(message); - // Reserve space for length - TlsUtils.writeUint24(0, buf); + message.writeToRecordStream(); + } - certificateRequest.encode(buf); - byte[] message = buf.toByteArray(); + protected void sendCertificateStatusMessage(CertificateStatus certificateStatus) + throws IOException + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_status); - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); + certificateStatus.encode(message); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected void sendNewSessionTicketMessage(NewSessionTicket newSessionTicket) throws IOException { - if (newSessionTicket == null) { throw new TlsFatalAlert(AlertDescription.internal_error); } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.session_ticket, buf); - - // Reserve space for length - TlsUtils.writeUint24(0, buf); + HandshakeMessage message = new HandshakeMessage(HandshakeType.session_ticket); - newSessionTicket.encode(buf); - byte[] message = buf.toByteArray(); + newSessionTicket.encode(message); - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); - - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected void sendServerHelloMessage() throws IOException { - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - TlsUtils.writeUint8(HandshakeType.server_hello, buf); - - // Reserve space for length - TlsUtils.writeUint24(0, buf); + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_hello); ProtocolVersion server_version = tlsServer.getServerVersion(); if (!server_version.isEqualOrEarlierVersionOf(getContext().getClientVersion())) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); } recordStream.setReadVersion(server_version); @@ -667,32 +628,34 @@ public class TlsServerProtocol recordStream.setRestrictReadVersion(true); getContext().setServerVersion(server_version); - TlsUtils.writeVersion(server_version, buf); + TlsUtils.writeVersion(server_version, message); - buf.write(this.securityParameters.serverRandom); + message.write(this.securityParameters.serverRandom); /* * The server may return an empty session_id to indicate that the session will not be cached * and therefore cannot be resumed. */ - TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf); + TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, message); - this.selectedCipherSuite = tlsServer.getSelectedCipherSuite(); - if (!arrayContains(this.offeredCipherSuites, this.selectedCipherSuite) - || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL - || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + int selectedCipherSuite = tlsServer.getSelectedCipherSuite(); + if (!Arrays.contains(this.offeredCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); } + securityParameters.cipherSuite = selectedCipherSuite; - this.selectedCompressionMethod = tlsServer.getSelectedCompressionMethod(); - if (!arrayContains(this.offeredCompressionMethods, this.selectedCompressionMethod)) + short selectedCompressionMethod = tlsServer.getSelectedCompressionMethod(); + if (!Arrays.contains(this.offeredCompressionMethods, selectedCompressionMethod)) { - this.failWithError(AlertLevel.fatal, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error); } + securityParameters.compressionAlgorithm = selectedCompressionMethod; - TlsUtils.writeUint16(this.selectedCipherSuite, buf); - TlsUtils.writeUint8(this.selectedCompressionMethod, buf); + TlsUtils.writeUint16(selectedCipherSuite, message); + TlsUtils.writeUint8(selectedCompressionMethod, message); this.serverExtensions = tlsServer.getServerExtensions(); @@ -701,9 +664,8 @@ public class TlsServerProtocol */ if (this.secure_renegotiation) { - - boolean noRenegExt = this.serverExtensions == null - || !this.serverExtensions.containsKey(EXT_RenegotiationInfo); + byte[] renegExtData = TlsUtils.getExtensionData(this.serverExtensions, EXT_RenegotiationInfo); + boolean noRenegExt = (null == renegExtData); if (noRenegExt) { @@ -714,55 +676,81 @@ public class TlsServerProtocol * because the client is signaling its willingness to receive the extension via the * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. */ - if (this.serverExtensions == null) - { - this.serverExtensions = new Hashtable(); - } /* * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty * "renegotiation_info" extension in the ServerHello message. */ + this.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.serverExtensions); this.serverExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)); } } + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + if (this.serverExtensions != null) { - this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket); - writeExtensions(buf, this.serverExtensions); + this.securityParameters.maxFragmentLength = processMaxFragmentLengthExtension(clientExtensions, + this.serverExtensions, AlertDescription.internal_error); + + this.securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(this.serverExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + this.allowCertificateStatus = !this.resumedSession + && TlsUtils.hasExpectedEmptyExtensionData(this.serverExtensions, TlsExtensionsUtils.EXT_status_request, + AlertDescription.internal_error); + + this.expectSessionTicket = !this.resumedSession + && TlsUtils.hasExpectedEmptyExtensionData(this.serverExtensions, TlsProtocol.EXT_SessionTicket, + AlertDescription.internal_error); + + writeExtensions(message, this.serverExtensions); + } + + if (this.securityParameters.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + this.securityParameters.maxFragmentLength); + recordStream.setPlaintextLimit(plainTextLimit); } - byte[] message = buf.toByteArray(); + securityParameters.prfAlgorithm = getPRFAlgorithm(getContext(), securityParameters.getCipherSuite()); - // Patch actual length back in - TlsUtils.writeUint24(message.length - 4, message, 1); + /* + * 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. + */ + securityParameters.verifyDataLength = 12; - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); + + this.recordStream.notifyHelloComplete(); } protected void sendServerHelloDoneMessage() throws IOException { - byte[] message = new byte[4]; TlsUtils.writeUint8(HandshakeType.server_hello_done, message, 0); TlsUtils.writeUint24(0, message, 1); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + writeHandshakeMessage(message, 0, message.length); } protected void sendServerKeyExchangeMessage(byte[] serverKeyExchange) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_key_exchange, serverKeyExchange.length); - TlsUtils.writeUint8(HandshakeType.server_key_exchange, bos); - TlsUtils.writeUint24(serverKeyExchange.length, bos); - bos.write(serverKeyExchange); - byte[] message = bos.toByteArray(); + message.write(serverKeyExchange); - safeWriteRecord(ContentType.handshake, message, 0, message.length); + message.writeToRecordStream(); } protected boolean expectCertificateVerifyMessage() diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSession.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSession.java new file mode 100644 index 0000000..9b5ad46 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSession.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto.tls; + +public interface TlsSession +{ + SessionParameters exportSessionParameters(); + + byte[] getSessionID(); + + void invalidate(); + + boolean isResumable(); +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSessionImpl.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSessionImpl.java new file mode 100644 index 0000000..615c442 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSessionImpl.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto.tls; + +import org.bouncycastle.util.Arrays; + +class TlsSessionImpl implements TlsSession +{ + final byte[] sessionID; + SessionParameters sessionParameters; + + TlsSessionImpl(byte[] sessionID, SessionParameters sessionParameters) + { + if (sessionID == null) + { + throw new IllegalArgumentException("'sessionID' cannot be null"); + } + if (sessionID.length < 1 || sessionID.length > 32) + { + throw new IllegalArgumentException("'sessionID' must have length between 1 and 32 bytes, inclusive"); + } + + this.sessionID = Arrays.clone(sessionID); + this.sessionParameters = sessionParameters; + } + + public synchronized SessionParameters exportSessionParameters() + { + return this.sessionParameters == null ? null : this.sessionParameters.copy(); + } + + public synchronized byte[] getSessionID() + { + return sessionID; + } + + public synchronized void invalidate() + { + if (this.sessionParameters != null) + { + this.sessionParameters.clear(); + this.sessionParameters = null; + } + } + + public synchronized boolean isResumable() + { + return this.sessionParameters != null; + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java index 2b61507..68826d2 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSigner.java @@ -6,18 +6,29 @@ import org.bouncycastle.crypto.params.AsymmetricKeyParameter; public interface TlsSigner { - void init(TlsContext context); byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) throws CryptoException; + byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash) + throws CryptoException; + boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) throws CryptoException; + boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash) + throws CryptoException; + Signer createSigner(AsymmetricKeyParameter privateKey); + Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey); + Signer createVerifyer(AsymmetricKeyParameter publicKey); + Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey); + boolean isValidPublicKey(AsymmetricKeyParameter publicKey); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java index 7067fa2..39ee99c 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsSignerCredentials.java @@ -5,6 +5,8 @@ import java.io.IOException; public interface TlsSignerCredentials extends TlsCredentials { - byte[] generateCertificateSignature(byte[] md5andsha1) + byte[] generateCertificateSignature(byte[] hash) throws IOException; + + SignatureAndHashAlgorithm getSignatureAndHashAlgorithm(); } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java index 1755c2d..178731d 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsStreamCipher.java @@ -11,6 +11,8 @@ import org.bouncycastle.util.Arrays; public class TlsStreamCipher implements TlsCipher { + private static boolean encryptThenMAC = false; + protected TlsContext context; protected StreamCipher encryptCipher; @@ -20,11 +22,9 @@ public class TlsStreamCipher protected TlsMac readMac; public TlsStreamCipher(TlsContext context, StreamCipher clientWriteCipher, - StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest, - int cipherKeySize) - throws IOException + StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest, + int cipherKeySize) throws IOException { - boolean isServer = context.isServer(); this.context = context; @@ -89,38 +89,75 @@ public class TlsStreamCipher public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) { - byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + /* + * TODO[draft-josefsson-salsa20-tls-02] 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. + */ - byte[] outbuf = new byte[len + mac.length]; + byte[] outBuf = new byte[len + writeMac.getSize()]; - encryptCipher.processBytes(plaintext, offset, len, outbuf, 0); - encryptCipher.processBytes(mac, 0, mac.length, outbuf, len); + encryptCipher.processBytes(plaintext, offset, len, outBuf, 0); + + if (encryptThenMAC) + { + byte[] mac = writeMac.calculateMac(seqNo, type, outBuf, 0, len); + System.arraycopy(mac, 0, outBuf, len, mac.length); + } + else + { + byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len); + encryptCipher.processBytes(mac, 0, mac.length, outBuf, len); + } - return outbuf; + return outBuf; } public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) throws IOException { + /* + * TODO[draft-josefsson-salsa20-tls-02] 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. + */ + int macSize = readMac.getSize(); if (len < macSize) { throw new TlsFatalAlert(AlertDescription.decode_error); } - byte[] deciphered = new byte[len]; - decryptCipher.processBytes(ciphertext, offset, len, deciphered, 0); + int plaintextLength = len - macSize; - int macInputLen = len - macSize; + if (encryptThenMAC) + { + int ciphertextEnd = offset + len; + checkMAC(seqNo, type, ciphertext, ciphertextEnd - macSize, ciphertextEnd, ciphertext, offset, plaintextLength); + byte[] deciphered = new byte[plaintextLength]; + decryptCipher.processBytes(ciphertext, offset, plaintextLength, deciphered, 0); + return deciphered; + } + else + { + 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); + } + } - byte[] receivedMac = Arrays.copyOfRange(deciphered, macInputLen, len); - byte[] computedMac = readMac.calculateMac(seqNo, type, deciphered, 0, macInputLen); + private void checkMAC(long seqNo, short type, byte[] recBuf, int recStart, int recEnd, byte[] calcBuf, int calcOff, int calcLen) + throws IOException + { + 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); } - - return Arrays.copyOfRange(deciphered, 0, macInputLen); } } diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java index 8b16210..dae9ff5 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/TlsUtils.java @@ -9,7 +9,10 @@ import java.io.OutputStream; import java.util.Hashtable; import java.util.Vector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.Extensions; @@ -44,11 +47,72 @@ public class TlsUtils public static final Integer EXT_signature_algorithms = Integers.valueOf(ExtensionType.signature_algorithms); + public static void checkUint8(short i) throws IOException + { + if (!isValidUint8(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint8(int i) throws IOException + { + if (!isValidUint8(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint16(int i) throws IOException + { + if (!isValidUint16(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint24(int i) throws IOException + { + if (!isValidUint24(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint32(long i) throws IOException + { + if (!isValidUint32(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint48(long i) throws IOException + { + if (!isValidUint48(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static void checkUint64(long i) throws IOException + { + if (!isValidUint64(i)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + public static boolean isValidUint8(short i) { return (i & 0xFF) == i; } + public static boolean isValidUint8(int i) + { + return (i & 0xFF) == i; + } + public static boolean isValidUint16(int i) { return (i & 0xFFFF) == i; @@ -74,17 +138,43 @@ public class TlsUtils return true; } + public static boolean isSSL(TlsContext context) + { + return context.getServerVersion().isSSL(); + } + + public static boolean isTLSv11(TlsContext context) + { + return ProtocolVersion.TLSv11.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion()); + } + + public static boolean isTLSv12(TlsContext context) + { + return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion()); + } + public static void writeUint8(short i, OutputStream output) throws IOException { output.write(i); } + public static void writeUint8(int i, OutputStream output) + throws IOException + { + output.write(i); + } + public static void writeUint8(short i, byte[] buf, int offset) { buf[offset] = (byte)i; } + public static void writeUint8(int i, byte[] buf, int offset) + { + buf[offset] = (byte)i; + } + public static void writeUint16(int i, OutputStream output) throws IOException { @@ -130,6 +220,17 @@ public class TlsUtils buf[offset + 3] = (byte)(i); } + public static void writeUint48(long i, OutputStream output) + throws IOException + { + output.write((byte)(i >> 40)); + output.write((byte)(i >> 32)); + output.write((byte)(i >> 24)); + output.write((byte)(i >> 16)); + output.write((byte)(i >> 8)); + output.write((byte)(i)); + } + public static void writeUint48(long i, byte[] buf, int offset) { buf[offset] = (byte)(i >> 40); @@ -143,14 +244,14 @@ public class TlsUtils public static void writeUint64(long i, OutputStream output) throws IOException { - output.write((int)(i >> 56)); - output.write((int)(i >> 48)); - output.write((int)(i >> 40)); - output.write((int)(i >> 32)); - output.write((int)(i >> 24)); - output.write((int)(i >> 16)); - output.write((int)(i >> 8)); - output.write((int)(i)); + output.write((byte)(i >> 56)); + output.write((byte)(i >> 48)); + output.write((byte)(i >> 40)); + output.write((byte)(i >> 32)); + output.write((byte)(i >> 24)); + output.write((byte)(i >> 16)); + output.write((byte)(i >> 8)); + output.write((byte)(i)); } public static void writeUint64(long i, byte[] buf, int offset) @@ -168,13 +269,15 @@ public class TlsUtils public static void writeOpaque8(byte[] buf, OutputStream output) throws IOException { - writeUint8((short)buf.length, output); + checkUint8(buf.length); + writeUint8(buf.length, output); output.write(buf); } public static void writeOpaque16(byte[] buf, OutputStream output) throws IOException { + checkUint16(buf.length); writeUint16(buf.length, output); output.write(buf); } @@ -182,6 +285,7 @@ public class TlsUtils public static void writeOpaque24(byte[] buf, OutputStream output) throws IOException { + checkUint24(buf.length); writeUint24(buf.length, output); output.write(buf); } @@ -195,6 +299,32 @@ public class TlsUtils } } + public static void writeUint8Array(short[] uints, byte[] buf, int offset) + throws IOException + { + for (int i = 0; i < uints.length; ++i) + { + writeUint8(uints[i], buf, offset); + ++offset; + } + } + + public static void writeUint8ArrayWithUint8Length(short[] uints, OutputStream output) + throws IOException + { + checkUint8(uints.length); + writeUint8(uints.length, output); + writeUint8Array(uints, output); + } + + public static void writeUint8ArrayWithUint8Length(short[] uints, byte[] buf, int offset) + throws IOException + { + checkUint8(uints.length); + writeUint8(uints.length, buf, offset); + writeUint8Array(uints, buf, offset + 1); + } + public static void writeUint16Array(int[] uints, OutputStream output) throws IOException { @@ -204,6 +334,56 @@ public class TlsUtils } } + public static void writeUint16Array(int[] uints, byte[] buf, int offset) + throws IOException + { + for (int i = 0; i < uints.length; ++i) + { + writeUint16(uints[i], buf, offset); + offset += 2; + } + } + + public static void writeUint16ArrayWithUint16Length(int[] uints, OutputStream output) + throws IOException + { + int length = 2 * uints.length; + checkUint16(length); + writeUint16(length, output); + writeUint16Array(uints, output); + } + + public static void writeUint16ArrayWithUint16Length(int[] uints, byte[] buf, int offset) + throws IOException + { + int length = 2 * uints.length; + checkUint16(length); + writeUint16(length, buf, offset); + writeUint16Array(uints, buf, offset + 2); + } + + public static byte[] encodeOpaque8(byte[] buf) + throws IOException + { + checkUint8(buf.length); + return Arrays.prepend(buf, (byte)buf.length); + } + + public static byte[] encodeUint8ArrayWithUint8Length(short[] uints) throws IOException + { + byte[] result = new byte[1 + uints.length]; + writeUint8ArrayWithUint8Length(uints, result, 0); + return result; + } + + public static byte[] encodeUint16ArrayWithUint16Length(int[] uints) throws IOException + { + int length = 2 * uints.length; + byte[] result = new byte[2 + length]; + writeUint16ArrayWithUint16Length(uints, result, 0); + return result; + } + public static short readUint8(InputStream input) throws IOException { @@ -297,6 +477,26 @@ public class TlsUtils return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL); } + public static byte[] readAllOrNothing(int length, InputStream input) + throws IOException + { + if (length < 1) + { + return EMPTY_BYTES; + } + byte[] buf = new byte[length]; + int read = Streams.readFully(input, buf); + if (read == 0) + { + return null; + } + if (read != length) + { + throw new EOFException(); + } + return buf; + } + public static byte[] readFully(int length, InputStream input) throws IOException { @@ -383,6 +583,12 @@ public class TlsUtils return ProtocolVersion.get(i1, i2); } + public static int readVersionRaw(byte[] buf, int offset) + throws IOException + { + return (buf[offset] << 8) | buf[offset + 1]; + } + public static int readVersionRaw(InputStream input) throws IOException { @@ -395,6 +601,36 @@ public class TlsUtils return (i1 << 8) | i2; } + public static ASN1Primitive readASN1Object(byte[] encoding) throws IOException + { + ASN1InputStream asn1 = new ASN1InputStream(encoding); + ASN1Primitive result = asn1.readObject(); + if (null == result) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + if (null != asn1.readObject()) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + return result; + } + + public static ASN1Primitive readDERObject(byte[] encoding) throws IOException + { + /* + * NOTE: The current ASN.1 parsing code can't enforce DER-only parsing, but since DER is + * canonical, we can check it by re-encoding the result and comparing to the original. + */ + ASN1Primitive result = readASN1Object(encoding); + byte[] check = result.getEncoded(ASN1Encoding.DER); + if (!Arrays.areEqual(check, encoding)) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + return result; + } + public static void writeGMTUnixTime(byte[] buf, int offset) { int t = (int)(System.currentTimeMillis() / 1000L); @@ -412,7 +648,6 @@ public class TlsUtils } public static void writeVersion(ProtocolVersion version, byte[] buf, int offset) - throws IOException { buf[offset] = (byte)version.getMajorVersion(); buf[offset + 1] = (byte)version.getMinorVersion(); @@ -433,6 +668,31 @@ public class TlsUtils return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa)); } + public static byte[] getExtensionData(Hashtable extensions, Integer extensionType) + { + return extensions == null ? null : (byte[])extensions.get(extensionType); + } + + public static boolean hasExpectedEmptyExtensionData(Hashtable extensions, Integer extensionType, + short alertDescription) throws IOException + { + byte[] extension_data = getExtensionData(extensions, extensionType); + if (extension_data == null) + { + return false; + } + if (extension_data.length != 0) + { + throw new TlsFatalAlert(alertDescription); + } + return true; + } + + public static TlsSession importSession(byte[] sessionID, SessionParameters sessionParameters) + { + return new TlsSessionImpl(sessionID, sessionParameters); + } + public static boolean isSignatureAlgorithmsExtensionAllowed(ProtocolVersion clientVersion) { return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(clientVersion.getEquivalentTLSVersion()); @@ -461,17 +721,8 @@ public class TlsUtils public static Vector getSignatureAlgorithmsExtension(Hashtable extensions) throws IOException { - - if (extensions == null) - { - return null; - } - byte[] extensionValue = (byte[])extensions.get(EXT_signature_algorithms); - if (extensionValue == null) - { - return null; - } - return readSignatureAlgorithmsExtension(extensionValue); + byte[] extensionData = getExtensionData(extensions, EXT_signature_algorithms); + return extensionData == null ? null : readSignatureAlgorithmsExtension(extensionData); } /** @@ -484,61 +735,94 @@ public class TlsUtils public static byte[] createSignatureAlgorithmsExtension(Vector supportedSignatureAlgorithms) throws IOException { - - if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.size() < 1 || supportedSignatureAlgorithms.size() >= (1 << 15)) - { - throw new IllegalArgumentException( - "'supportedSignatureAlgorithms' must have length from 1 to (2^15 - 1)"); - } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); // supported_signature_algorithms - TlsUtils.writeUint16(2 * supportedSignatureAlgorithms.size(), buf); - for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i) - { - SignatureAndHashAlgorithm entry = (SignatureAndHashAlgorithm)supportedSignatureAlgorithms.elementAt(i); - entry.encode(buf); - } + encodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, false, buf); return buf.toByteArray(); } /** - * Read a 'signature_algorithms' extension value. + * Read 'signature_algorithms' extension data. * - * @param extensionValue The extension value. + * @param extensionData The extension data. * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. * @throws IOException */ - public static Vector readSignatureAlgorithmsExtension(byte[] extensionValue) + public static Vector readSignatureAlgorithmsExtension(byte[] extensionData) throws IOException { + if (extensionData == null) + { + throw new IllegalArgumentException("'extensionData' cannot be null"); + } + + ByteArrayInputStream buf = new ByteArrayInputStream(extensionData); - if (extensionValue == null) + // supported_signature_algorithms + Vector supported_signature_algorithms = parseSupportedSignatureAlgorithms(false, buf); + + TlsProtocol.assertEmpty(buf); + + return supported_signature_algorithms; + } + + public static void encodeSupportedSignatureAlgorithms(Vector supportedSignatureAlgorithms, boolean allowAnonymous, + OutputStream output) throws IOException + { + if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.size() < 1 + || supportedSignatureAlgorithms.size() >= (1 << 15)) { - throw new IllegalArgumentException("'extensionValue' cannot be null"); + throw new IllegalArgumentException( + "'supportedSignatureAlgorithms' must have length from 1 to (2^15 - 1)"); } - ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue); + // supported_signature_algorithms + int length = 2 * supportedSignatureAlgorithms.size(); + TlsUtils.checkUint16(length); + TlsUtils.writeUint16(length, output); + for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i) + { + SignatureAndHashAlgorithm entry = (SignatureAndHashAlgorithm)supportedSignatureAlgorithms.elementAt(i); + if (!allowAnonymous && entry.getSignature() == SignatureAlgorithm.anonymous) + { + /* + * RFC 5246 7.4.1.4.1 The "anonymous" value is meaningless in this context but used + * in Section 7.4.3. It MUST NOT appear in this extension. + */ + throw new IllegalArgumentException( + "SignatureAlgorithm.anonymous MUST NOT appear in the signature_algorithms extension"); + } + entry.encode(output); + } + } + public static Vector parseSupportedSignatureAlgorithms(boolean allowAnonymous, InputStream input) + throws IOException + { // supported_signature_algorithms - int length = TlsUtils.readUint16(buf); + int length = TlsUtils.readUint16(input); if (length < 2 || (length & 1) != 0) { throw new TlsFatalAlert(AlertDescription.decode_error); } int count = length / 2; - Vector result = new Vector(count); + Vector supportedSignatureAlgorithms = new Vector(count); for (int i = 0; i < count; ++i) { - SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.parse(buf); - result.addElement(entry); + SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.parse(input); + if (!allowAnonymous && entry.getSignature() == SignatureAlgorithm.anonymous) + { + /* + * RFC 5246 7.4.1.4.1 The "anonymous" value is meaningless in this context but used + * in Section 7.4.3. It MUST NOT appear in this extension. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + supportedSignatureAlgorithms.addElement(entry); } - - TlsProtocol.assertEmpty(buf); - - return result; + return supportedSignatureAlgorithms; } public static byte[] PRF(TlsContext context, byte[] secret, String asciiLabel, byte[] seed, int size) @@ -557,12 +841,7 @@ public class TlsUtils if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy) { - if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(version.getEquivalentTLSVersion())) - { - return PRF_legacy(secret, label, labelSeed, size); - } - - prfAlgorithm = PRFAlgorithm.tls_prf_sha256; + return PRF_legacy(secret, label, labelSeed, size); } Digest prfDigest = createPRFHash(prfAlgorithm); @@ -646,7 +925,7 @@ public class TlsUtils byte[] seed = concat(securityParameters.getServerRandom(), securityParameters.getClientRandom()); - if (context.getServerVersion().isSSL()) + if (isSSL(context)) { return calculateKeyBlock_SSL(master_secret, seed, size); } @@ -690,7 +969,7 @@ public class TlsUtils SecurityParameters securityParameters = context.getSecurityParameters(); byte[] seed = concat(securityParameters.getClientRandom(), securityParameters.getServerRandom()); - if (context.getServerVersion().isSSL()) + if (isSSL(context)) { return calculateMasterSecret_SSL(pre_master_secret, seed); } @@ -729,7 +1008,7 @@ public class TlsUtils static byte[] calculateVerifyData(TlsContext context, String asciiLabel, byte[] handshakeHash) { - if (context.getServerVersion().isSSL()) + if (isSSL(context)) { return handshakeHash; } @@ -741,7 +1020,7 @@ public class TlsUtils return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length); } - public static final Digest createHash(int hashAlgorithm) + public static final Digest createHash(short hashAlgorithm) { switch (hashAlgorithm) { @@ -762,7 +1041,7 @@ public class TlsUtils } } - public static final Digest cloneHash(int hashAlgorithm, Digest hash) + public static final Digest cloneHash(short hashAlgorithm, Digest hash) { switch (hashAlgorithm) { @@ -820,7 +1099,7 @@ public class TlsUtils } } - public static ASN1ObjectIdentifier getOIDForHashAlgorithm(int hashAlgorithm) + public static ASN1ObjectIdentifier getOIDForHashAlgorithm(short hashAlgorithm) { switch (hashAlgorithm) { @@ -912,6 +1191,20 @@ public class TlsUtils throw new TlsFatalAlert(AlertDescription.unsupported_certificate); } + static void trackHashAlgorithms(TlsHandshakeHash handshakeHash, Vector supportedSignatureAlgorithms) + { + if (supportedSignatureAlgorithms != null) + { + for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i) + { + SignatureAndHashAlgorithm signatureAndHashAlgorithm = (SignatureAndHashAlgorithm) + supportedSignatureAlgorithms.elementAt(i); + short hashAlgorithm = signatureAndHashAlgorithm.getHash(); + handshakeHash.trackHashAlgorithm(hashAlgorithm); + } + } + } + public static boolean hasSigningCapability(short clientCertificateType) { switch (clientCertificateType) diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java index f3dd59e..d5f0769 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/UDPTransport.java @@ -7,18 +7,16 @@ import java.net.DatagramSocket; public class UDPTransport implements DatagramTransport { + protected final static int MIN_IP_OVERHEAD = 20; + protected final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64; + protected final static int UDP_OVERHEAD = 8; - private final static int MIN_IP_OVERHEAD = 20; - private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64; - private final static int UDP_OVERHEAD = 8; - - private final DatagramSocket socket; - private final int receiveLimit, sendLimit; + protected final DatagramSocket socket; + protected final int receiveLimit, sendLimit; public UDPTransport(DatagramSocket socket, int mtu) throws IOException { - if (!socket.isBound() || !socket.isConnected()) { throw new IllegalArgumentException("'socket' must be bound and connected"); @@ -62,7 +60,7 @@ public class UDPTransport * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet * which will be fragmented." */ - // TODO Exception + throw new TlsFatalAlert(AlertDescription.internal_error); } DatagramPacket packet = new DatagramPacket(buf, off, len); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/tls/URLAndHash.java b/bcprov/src/main/java/org/bouncycastle/crypto/tls/URLAndHash.java new file mode 100644 index 0000000..c32a904 --- /dev/null +++ b/bcprov/src/main/java/org/bouncycastle/crypto/tls/URLAndHash.java @@ -0,0 +1,104 @@ +package org.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bouncycastle.util.Strings; + +/** + * RFC 6066 5. + */ +public class URLAndHash +{ + protected String url; + protected byte[] sha1Hash; + + public URLAndHash(String url, byte[] sha1Hash) + { + if (url == null || url.length() < 1 || url.length() >= (1 << 16)) + { + throw new IllegalArgumentException("'url' must have length from 1 to (2^16 - 1)"); + } + if (sha1Hash != null && sha1Hash.length != 20) + { + throw new IllegalArgumentException("'sha1Hash' must have length == 20, if present"); + } + + this.url = url; + this.sha1Hash = sha1Hash; + } + + public String getURL() + { + return url; + } + + public byte[] getSHA1Hash() + { + return sha1Hash; + } + + /** + * Encode this {@link URLAndHash} to an {@link OutputStream}. + * + * @param output the {@link OutputStream} to encode to. + * @throws IOException + */ + public void encode(OutputStream output) + throws IOException + { + byte[] urlEncoding = Strings.toByteArray(this.url); + TlsUtils.writeOpaque16(urlEncoding, output); + + if (this.sha1Hash == null) + { + TlsUtils.writeUint8(0, output); + } + else + { + TlsUtils.writeUint8(1, output); + output.write(this.sha1Hash); + } + } + + /** + * Parse a {@link URLAndHash} from an {@link InputStream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link InputStream} to parse from. + * @return a {@link URLAndHash} object. + * @throws IOException + */ + public static URLAndHash parse(TlsContext context, InputStream input) + throws IOException + { + byte[] urlEncoding = TlsUtils.readOpaque16(input); + if (urlEncoding.length < 1) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + String url = Strings.fromByteArray(urlEncoding); + + byte[] sha1Hash = null; + short padding = TlsUtils.readUint8(input); + switch (padding) + { + case 0: + if (TlsUtils.isTLSv12(context)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + break; + case 1: + sha1Hash = TlsUtils.readFully(20, input); + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return new URLAndHash(url, sha1Hash); + } +} diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java index bfa304b..6bf3399 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java @@ -10,7 +10,6 @@ import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; -import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.oiw.ElGamalParameter; import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; import org.bouncycastle.asn1.pkcs.DHParameter; @@ -18,11 +17,9 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.pkcs.RSAPrivateKey; import org.bouncycastle.asn1.sec.ECPrivateKey; -import org.bouncycastle.asn1.sec.SECNamedCurves; -import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DSAParameter; -import org.bouncycastle.asn1.x9.X962NamedCurves; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; @@ -130,22 +127,7 @@ public class PrivateKeyFactory if (params.isNamedCurve()) { ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters()); - x9 = X962NamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = SECNamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = NISTNamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = TeleTrusTNamedCurves.getByOID(oid); - } - } - } + x9 = ECNamedCurveTable.getByOID(oid); } else { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java index ab52802..7b06c3f 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java @@ -2,17 +2,23 @@ package org.bouncycastle.crypto.util; import java.io.IOException; +import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.pkcs.RSAPrivateKey; +import org.bouncycastle.asn1.sec.ECPrivateKey; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.DSAParameters; import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; @@ -43,6 +49,31 @@ public class PrivateKeyInfoFactory return new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(params.getP(), params.getQ(), params.getG())), new ASN1Integer(priv.getX())); } + else if (privateKey instanceof ECPrivateKeyParameters) + { + ECPrivateKeyParameters priv = (ECPrivateKeyParameters)privateKey; + ECDomainParameters domainParams = priv.getParameters(); + ASN1Encodable params; + + // TODO: need to handle named curves + if (domainParams == null) + { + params = new X962Parameters(DERNull.INSTANCE); // Implicitly CA + } + else + { + X9ECParameters ecP = new X9ECParameters( + domainParams.getCurve(), + domainParams.getG(), + domainParams.getN(), + domainParams.getH(), + domainParams.getSeed()); + + params = new X962Parameters(ecP); + } + + return new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), new ECPrivateKey(priv.getD(), params)); + } else { throw new IOException("key parameters not recognised."); diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java index 343bbd3..2d2927b 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java @@ -12,14 +12,11 @@ import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.oiw.ElGamalParameter; import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; import org.bouncycastle.asn1.pkcs.DHParameter; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.RSAPublicKey; -import org.bouncycastle.asn1.sec.SECNamedCurves; -import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DSAParameter; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; @@ -27,7 +24,7 @@ import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; import org.bouncycastle.asn1.x9.DHDomainParameters; import org.bouncycastle.asn1.x9.DHPublicKey; import org.bouncycastle.asn1.x9.DHValidationParms; -import org.bouncycastle.asn1.x9.X962NamedCurves; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECPoint; @@ -160,29 +157,13 @@ public class PublicKeyFactory } else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey)) { - X962Parameters params = new X962Parameters( - (ASN1Primitive)algId.getParameters()); + X962Parameters params = X962Parameters.getInstance(algId.getParameters()); X9ECParameters x9; if (params.isNamedCurve()) { ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters(); - x9 = X962NamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = SECNamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = NISTNamedCurves.getByOID(oid); - - if (x9 == null) - { - x9 = TeleTrusTNamedCurves.getByOID(oid); - } - } - } + x9 = ECNamedCurveTable.getByOID(oid); } else { diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/package.html b/bcprov/src/main/java/org/bouncycastle/crypto/util/package.html deleted file mode 100644 index 787b892..0000000 --- a/bcprov/src/main/java/org/bouncycastle/crypto/util/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body bgcolor="#ffffff"> -Some general utility/conversion classes. -</body> -</html> |