8140466: ChaCha20 and Poly1305 TLS Cipher Suites

Reviewed-by: xuelei, mullan
This commit is contained in:
Jamil Nimeh 2018-09-17 15:25:42 -07:00
parent b72ab42e49
commit 962e755c3a
5 changed files with 596 additions and 4 deletions

View File

@ -70,6 +70,9 @@ enum CipherSuite {
TLS_AES_256_GCM_SHA384(
0x1302, true, "TLS_AES_256_GCM_SHA384",
ProtocolVersion.PROTOCOLS_OF_13, B_AES_256_GCM_IV, H_SHA384),
TLS_CHACHA20_POLY1305_SHA256(
0x1303, true, "TLS_CHACHA20_POLY1305_SHA256",
ProtocolVersion.PROTOCOLS_OF_13, B_CC20_P1305, H_SHA256),
// Suite B compliant cipher suites, see RFC 6460.
//
@ -87,11 +90,22 @@ enum CipherSuite {
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_ECDSA, B_AES_128_GCM, M_NULL, H_SHA256),
// Not suite B, but we want it to position the suite early in the list
// of 1.2 suites.
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(
0xCCA9, true, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_ECDSA, B_CC20_P1305, M_NULL, H_SHA256),
// AES_256(GCM)
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(
0xC030, true, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
0xCCA8, true, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
TLS_RSA_WITH_AES_256_GCM_SHA384(
0x009D, true, "TLS_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
@ -108,6 +122,10 @@ enum CipherSuite {
0x009F, true, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_DHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
0xCCAA, true, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_DHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(
0x00A3, true, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
@ -480,8 +498,6 @@ enum CipherSuite {
// Definition of the CipherSuites that are not supported but the names
// are known.
TLS_CHACHA20_POLY1305_SHA256( // TLS 1.3
"TLS_CHACHA20_POLY1305_SHA256", 0x1303),
TLS_AES_128_CCM_SHA256( // TLS 1.3
"TLS_AES_128_CCM_SHA256", 0x1304),
TLS_AES_128_CCM_8_SHA256( // TLS 1.3

View File

@ -129,6 +129,11 @@ final class JsseJce {
*/
static final String CIPHER_AES_GCM = "AES/GCM/NoPadding";
/**
* JCE transformation string for ChaCha20-Poly1305
*/
static final String CIPHER_CHACHA20_POLY1305 = "ChaCha20-Poly1305";
/**
* JCA identifier string for DSA, i.e. a DSA with SHA-1.
*/

View File

@ -329,6 +329,32 @@ enum SSLCipher {
new T13GcmWriteCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_13
)
})),
@SuppressWarnings({"unchecked", "rawtypes"})
B_CC20_P1305(CIPHER_CHACHA20_POLY1305, AEAD_CIPHER, 32, 32, 12,
12, true, false,
(Map.Entry<ReadCipherGenerator,
ProtocolVersion[]>[])(new Map.Entry[] {
new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
new T12CC20P1305ReadCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_12
),
new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
new T13CC20P1305ReadCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_13
)
}),
(Map.Entry<WriteCipherGenerator,
ProtocolVersion[]>[])(new Map.Entry[] {
new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
new T12CC20P1305WriteCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_12
),
new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
new T13CC20P1305WriteCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_13
)
}));
// descriptive name including key size, e.g. AES/128
@ -2082,6 +2108,549 @@ enum SSLCipher {
}
}
private static final class T12CC20P1305ReadCipherGenerator
implements ReadCipherGenerator {
@Override
public SSLReadCipher createCipher(SSLCipher sslCipher,
Authenticator authenticator, ProtocolVersion protocolVersion,
String algorithm, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
return new CC20P1305ReadCipher(authenticator, protocolVersion,
sslCipher, algorithm, key, params, random);
}
static final class CC20P1305ReadCipher extends SSLReadCipher {
private final Cipher cipher;
private final int tagSize;
private final Key key;
private final byte[] iv;
private final SecureRandom random;
CC20P1305ReadCipher(Authenticator authenticator,
ProtocolVersion protocolVersion,
SSLCipher sslCipher, String algorithm,
Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
super(authenticator, protocolVersion);
this.cipher = JsseJce.getCipher(algorithm);
this.tagSize = sslCipher.tagSize;
this.key = key;
this.iv = ((IvParameterSpec)params).getIV();
this.random = random;
// DON'T initialize the cipher for AEAD!
}
@Override
public Plaintext decrypt(byte contentType, ByteBuffer bb,
byte[] sequence) throws GeneralSecurityException {
if (bb.remaining() <= tagSize) {
throw new BadPaddingException(
"Insufficient buffer remaining for AEAD cipher " +
"fragment (" + bb.remaining() + "). Needs to be " +
"more than tag size (" + tagSize + ")");
}
byte[] sn = sequence;
if (sn == null) {
sn = authenticator.sequenceNumber();
}
byte[] nonce = new byte[iv.length];
System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
sn.length);
for (int i = 0; i < nonce.length; i++) {
nonce[i] ^= iv[i];
}
// initialize the AEAD cipher with the unique IV
AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
try {
cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
} catch (InvalidKeyException |
InvalidAlgorithmParameterException ikae) {
// unlikely to happen
throw new RuntimeException(
"invalid key or spec in AEAD mode", ikae);
}
// update the additional authentication data
byte[] aad = authenticator.acquireAuthenticationBytes(
contentType, bb.remaining() - tagSize, sequence);
cipher.updateAAD(aad);
// DON'T decrypt the nonce_explicit for AEAD mode. The buffer
// position has moved out of the nonce_explicit range.
int len = bb.remaining();
int pos = bb.position();
ByteBuffer dup = bb.duplicate();
try {
len = cipher.doFinal(dup, bb);
} catch (IllegalBlockSizeException ibse) {
// unlikely to happen
throw new RuntimeException(
"Cipher error in AEAD mode \"" + ibse.getMessage() +
" \"in JCE provider " + cipher.getProvider().getName());
} catch (ShortBufferException sbe) {
// catch BouncyCastle buffering error
throw new RuntimeException("Cipher buffering error in " +
"JCE provider " + cipher.getProvider().getName(), sbe);
}
// reset the limit to the end of the decrypted data
bb.position(pos);
bb.limit(pos + len);
if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
SSLLogger.fine(
"Plaintext after DECRYPTION", bb.duplicate());
}
return new Plaintext(contentType,
ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
-1, -1L, bb.slice());
}
@Override
void dispose() {
if (cipher != null) {
try {
cipher.doFinal();
} catch (Exception e) {
// swallow all types of exceptions.
}
}
}
@Override
int estimateFragmentSize(int packetSize, int headerSize) {
return packetSize - headerSize - tagSize;
}
}
}
private static final class T12CC20P1305WriteCipherGenerator
implements WriteCipherGenerator {
@Override
public SSLWriteCipher createCipher(SSLCipher sslCipher,
Authenticator authenticator, ProtocolVersion protocolVersion,
String algorithm, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
return new CC20P1305WriteCipher(authenticator, protocolVersion,
sslCipher, algorithm, key, params, random);
}
private static final class CC20P1305WriteCipher extends SSLWriteCipher {
private final Cipher cipher;
private final int tagSize;
private final Key key;
private final byte[] iv;
private final SecureRandom random;
CC20P1305WriteCipher(Authenticator authenticator,
ProtocolVersion protocolVersion,
SSLCipher sslCipher, String algorithm,
Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
super(authenticator, protocolVersion);
this.cipher = JsseJce.getCipher(algorithm);
this.tagSize = sslCipher.tagSize;
this.key = key;
this.iv = ((IvParameterSpec)params).getIV();
this.random = random;
keyLimitCountdown = cipherLimits.getOrDefault(
algorithm.toUpperCase() + ":" + tag[0], 0L);
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
":" + tag[0] + "\ncountdown value = " +
keyLimitCountdown);
}
if (keyLimitCountdown > 0) {
keyLimitEnabled = true;
}
// DON'T initialize the cipher for AEAD!
}
@Override
public int encrypt(byte contentType,
ByteBuffer bb) {
byte[] sn = authenticator.sequenceNumber();
byte[] nonce = new byte[iv.length];
System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
sn.length);
for (int i = 0; i < nonce.length; i++) {
nonce[i] ^= iv[i];
}
// initialize the AEAD cipher for the unique IV
AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
try {
cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
} catch (InvalidKeyException |
InvalidAlgorithmParameterException ikae) {
// unlikely to happen
throw new RuntimeException(
"invalid key or spec in AEAD mode", ikae);
}
// Update the additional authentication data, using the
// implicit sequence number of the authenticator.
byte[] aad = authenticator.acquireAuthenticationBytes(
contentType, bb.remaining(), null);
cipher.updateAAD(aad);
// DON'T encrypt the nonce for AEAD mode.
int len = bb.remaining();
int pos = bb.position();
if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
SSLLogger.fine(
"Plaintext before ENCRYPTION",
bb.duplicate());
}
ByteBuffer dup = bb.duplicate();
int outputSize = cipher.getOutputSize(dup.remaining());
if (outputSize > bb.remaining()) {
// Need to expand the limit of the output buffer for
// the authentication tag.
//
// DON'T worry about the buffer's capacity, we have
// reserved space for the authentication tag.
bb.limit(pos + outputSize);
}
try {
len = cipher.doFinal(dup, bb);
} catch (IllegalBlockSizeException |
BadPaddingException | ShortBufferException ibse) {
// unlikely to happen
throw new RuntimeException(
"Cipher error in AEAD mode in JCE provider " +
cipher.getProvider().getName(), ibse);
}
if (len != outputSize) {
throw new RuntimeException(
"Cipher buffering error in JCE provider " +
cipher.getProvider().getName());
}
return len;
}
@Override
void dispose() {
if (cipher != null) {
try {
cipher.doFinal();
} catch (Exception e) {
// swallow all types of exceptions.
}
}
}
@Override
int getExplicitNonceSize() {
return 0;
}
@Override
int calculateFragmentSize(int packetLimit, int headerSize) {
return packetLimit - headerSize - tagSize;
}
@Override
int calculatePacketSize(int fragmentSize, int headerSize) {
return fragmentSize + headerSize + tagSize;
}
}
}
private static final class T13CC20P1305ReadCipherGenerator
implements ReadCipherGenerator {
@Override
public SSLReadCipher createCipher(SSLCipher sslCipher,
Authenticator authenticator, ProtocolVersion protocolVersion,
String algorithm, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
return new CC20P1305ReadCipher(authenticator, protocolVersion,
sslCipher, algorithm, key, params, random);
}
static final class CC20P1305ReadCipher extends SSLReadCipher {
private final Cipher cipher;
private final int tagSize;
private final Key key;
private final byte[] iv;
private final SecureRandom random;
CC20P1305ReadCipher(Authenticator authenticator,
ProtocolVersion protocolVersion,
SSLCipher sslCipher, String algorithm,
Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
super(authenticator, protocolVersion);
this.cipher = JsseJce.getCipher(algorithm);
this.tagSize = sslCipher.tagSize;
this.key = key;
this.iv = ((IvParameterSpec)params).getIV();
this.random = random;
// DON'T initialize the cipher for AEAD!
}
@Override
public Plaintext decrypt(byte contentType, ByteBuffer bb,
byte[] sequence) throws GeneralSecurityException {
// An implementation may receive an unencrypted record of type
// change_cipher_spec consisting of the single byte value 0x01
// at any time after the first ClientHello message has been
// sent or received and before the peer's Finished message has
// been received and MUST simply drop it without further
// processing.
if (contentType == ContentType.CHANGE_CIPHER_SPEC.id) {
return new Plaintext(contentType,
ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
-1, -1L, bb.slice());
}
if (bb.remaining() <= tagSize) {
throw new BadPaddingException(
"Insufficient buffer remaining for AEAD cipher " +
"fragment (" + bb.remaining() + "). Needs to be " +
"more than tag size (" + tagSize + ")");
}
byte[] sn = sequence;
if (sn == null) {
sn = authenticator.sequenceNumber();
}
byte[] nonce = new byte[iv.length];
System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
sn.length);
for (int i = 0; i < nonce.length; i++) {
nonce[i] ^= iv[i];
}
// initialize the AEAD cipher with the unique IV
AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
try {
cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
} catch (InvalidKeyException |
InvalidAlgorithmParameterException ikae) {
// unlikely to happen
throw new RuntimeException(
"invalid key or spec in AEAD mode", ikae);
}
// Update the additional authentication data, using the
// implicit sequence number of the authenticator.
byte[] aad = authenticator.acquireAuthenticationBytes(
contentType, bb.remaining(), sn);
cipher.updateAAD(aad);
int len = bb.remaining();
int pos = bb.position();
ByteBuffer dup = bb.duplicate();
try {
len = cipher.doFinal(dup, bb);
} catch (IllegalBlockSizeException ibse) {
// unlikely to happen
throw new RuntimeException(
"Cipher error in AEAD mode \"" + ibse.getMessage() +
" \"in JCE provider " + cipher.getProvider().getName());
} catch (ShortBufferException sbe) {
// catch BouncyCastle buffering error
throw new RuntimeException("Cipher buffering error in " +
"JCE provider " + cipher.getProvider().getName(), sbe);
}
// reset the limit to the end of the decrypted data
bb.position(pos);
bb.limit(pos + len);
// remove inner plaintext padding
int i = bb.limit() - 1;
for (; i > 0 && bb.get(i) == 0; i--) {
// blank
}
if (i < (pos + 1)) {
throw new BadPaddingException(
"Incorrect inner plaintext: no content type");
}
contentType = bb.get(i);
bb.limit(i);
if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
SSLLogger.fine(
"Plaintext after DECRYPTION", bb.duplicate());
}
return new Plaintext(contentType,
ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
-1, -1L, bb.slice());
}
@Override
void dispose() {
if (cipher != null) {
try {
cipher.doFinal();
} catch (Exception e) {
// swallow all types of exceptions.
}
}
}
@Override
int estimateFragmentSize(int packetSize, int headerSize) {
return packetSize - headerSize - tagSize;
}
}
}
private static final class T13CC20P1305WriteCipherGenerator
implements WriteCipherGenerator {
@Override
public SSLWriteCipher createCipher(SSLCipher sslCipher,
Authenticator authenticator, ProtocolVersion protocolVersion,
String algorithm, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
return new CC20P1305WriteCipher(authenticator, protocolVersion,
sslCipher, algorithm, key, params, random);
}
private static final class CC20P1305WriteCipher extends SSLWriteCipher {
private final Cipher cipher;
private final int tagSize;
private final Key key;
private final byte[] iv;
private final SecureRandom random;
CC20P1305WriteCipher(Authenticator authenticator,
ProtocolVersion protocolVersion,
SSLCipher sslCipher, String algorithm,
Key key, AlgorithmParameterSpec params,
SecureRandom random) throws GeneralSecurityException {
super(authenticator, protocolVersion);
this.cipher = JsseJce.getCipher(algorithm);
this.tagSize = sslCipher.tagSize;
this.key = key;
this.iv = ((IvParameterSpec)params).getIV();
this.random = random;
keyLimitCountdown = cipherLimits.getOrDefault(
algorithm.toUpperCase() + ":" + tag[0], 0L);
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
":" + tag[0] + "\ncountdown value = " +
keyLimitCountdown);
}
if (keyLimitCountdown > 0) {
keyLimitEnabled = true;
}
// DON'T initialize the cipher for AEAD!
}
@Override
public int encrypt(byte contentType,
ByteBuffer bb) {
byte[] sn = authenticator.sequenceNumber();
byte[] nonce = new byte[iv.length];
System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
sn.length);
for (int i = 0; i < nonce.length; i++) {
nonce[i] ^= iv[i];
}
// initialize the AEAD cipher for the unique IV
AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
try {
cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
} catch (InvalidKeyException |
InvalidAlgorithmParameterException ikae) {
// unlikely to happen
throw new RuntimeException(
"invalid key or spec in AEAD mode", ikae);
}
// Update the additional authentication data, using the
// implicit sequence number of the authenticator.
int outputSize = cipher.getOutputSize(bb.remaining());
byte[] aad = authenticator.acquireAuthenticationBytes(
contentType, outputSize, sn);
cipher.updateAAD(aad);
int len = bb.remaining();
int pos = bb.position();
if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
SSLLogger.fine(
"Plaintext before ENCRYPTION",
bb.duplicate());
}
ByteBuffer dup = bb.duplicate();
if (outputSize > bb.remaining()) {
// Need to expand the limit of the output buffer for
// the authentication tag.
//
// DON'T worry about the buffer's capacity, we have
// reserved space for the authentication tag.
bb.limit(pos + outputSize);
}
try {
len = cipher.doFinal(dup, bb);
} catch (IllegalBlockSizeException |
BadPaddingException | ShortBufferException ibse) {
// unlikely to happen
throw new RuntimeException(
"Cipher error in AEAD mode in JCE provider " +
cipher.getProvider().getName(), ibse);
}
if (len != outputSize) {
throw new RuntimeException(
"Cipher buffering error in JCE provider " +
cipher.getProvider().getName());
}
if (keyLimitEnabled) {
keyLimitCountdown -= len;
}
return len;
}
@Override
void dispose() {
if (cipher != null) {
try {
cipher.doFinal();
} catch (Exception e) {
// swallow all types of exceptions.
}
}
}
@Override
int getExplicitNonceSize() {
return 0;
}
@Override
int calculateFragmentSize(int packetLimit, int headerSize) {
return packetLimit - headerSize - tagSize;
}
@Override
int calculatePacketSize(int fragmentSize, int headerSize) {
return fragmentSize + headerSize + tagSize;
}
}
}
private static void addMac(MAC signer,
ByteBuffer destination, byte contentType) {
if (signer.macAlg().size != 0) {
@ -2367,4 +2936,3 @@ enum SSLCipher {
return results;
}
}

View File

@ -27,6 +27,8 @@ public enum CipherSuite {
0x1302, Protocol.TLSV1_3, Protocol.TLSV1_3),
TLS_AES_128_GCM_SHA256(
0x1301, Protocol.TLSV1_3, Protocol.TLSV1_3),
TLS_CHACHA20_POLY1305_SHA256(
0x1303, Protocol.TLSV1_3, Protocol.TLSV1_3),
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
0xCCAA, Protocol.TLSV1_2, Protocol.TLSV1_2),
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(

View File

@ -182,7 +182,8 @@ abstract public class SSLEngineTestCase {
private static final String[] TLS13_CIPHERS = {
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256"
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256"
};
private static final String[] SUPPORTED_NON_KRB_CIPHERS;