diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java b/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java index 84bd6521345..857cf52d3b6 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,6 +87,7 @@ abstract class ChaCha20Cipher extends CipherSpi { // The counter private static final long MAX_UINT32 = 0x00000000FFFFFFFFL; private long finalCounterValue; + private long initCounterValue; private long counter; // The base state is created at initialization time as a 16-int array @@ -336,7 +337,9 @@ abstract class ChaCha20Cipher extends CipherSpi { } ChaCha20ParameterSpec chaParams = (ChaCha20ParameterSpec)params; newNonce = chaParams.getNonce(); - counter = ((long)chaParams.getCounter()) & 0x00000000FFFFFFFFL; + initCounterValue = ((long)chaParams.getCounter()) & + 0x00000000FFFFFFFFL; + counter = initCounterValue; break; case MODE_AEAD: if (!(params instanceof IvParameterSpec)) { @@ -545,9 +548,12 @@ abstract class ChaCha20Cipher extends CipherSpi { } // Make sure that the provided key and nonce are unique before - // assigning them to the object. + // assigning them to the object. Key and nonce uniqueness + // protection is for encryption operations only. byte[] newKeyBytes = getEncodedKey(key); - checkKeyAndNonce(newKeyBytes, newNonce); + if (opmode == Cipher.ENCRYPT_MODE) { + checkKeyAndNonce(newKeyBytes, newNonce); + } if (this.keyBytes != null) { Arrays.fill(this.keyBytes, (byte)0); } @@ -704,9 +710,8 @@ abstract class ChaCha20Cipher extends CipherSpi { } catch (ShortBufferException | KeyException exc) { throw new RuntimeException(exc); } finally { - // Regardless of what happens, the cipher cannot be used for - // further processing until it has been freshly initialized. - initialized = false; + // Reset the cipher's state to post-init values. + resetStartState(); } return output; } @@ -742,9 +747,8 @@ abstract class ChaCha20Cipher extends CipherSpi { } catch (KeyException ke) { throw new RuntimeException(ke); } finally { - // Regardless of what happens, the cipher cannot be used for - // further processing until it has been freshly initialized. - initialized = false; + // Reset the cipher's state to post-init values. + resetStartState(); } return bytesUpdated; } @@ -1170,6 +1174,23 @@ abstract class ChaCha20Cipher extends CipherSpi { asLongLittleEndian.set(buf, Long.BYTES, dLen); } + /** + * reset the Cipher's state to the values it had after + * the initial init() call. + * + * Note: The cipher's internal "initialized" field is set differently + * for ENCRYPT_MODE and DECRYPT_MODE in order to allow DECRYPT_MODE + * ciphers to reuse the key/nonce/counter values. This kind of reuse + * is disallowed in ENCRYPT_MODE. + */ + private void resetStartState() { + keyStrLimit = 0; + keyStrOffset = 0; + counter = initCounterValue; + aadDone = false; + initialized = (direction == Cipher.DECRYPT_MODE); + } + /** * Interface for the underlying processing engines for ChaCha20 */ @@ -1275,7 +1296,8 @@ abstract class ChaCha20Cipher extends CipherSpi { private EngineAEADEnc() throws InvalidKeyException { initAuthenticator(); - counter = 1; + initCounterValue = 1; + counter = initCounterValue; } @Override @@ -1347,7 +1369,8 @@ abstract class ChaCha20Cipher extends CipherSpi { private EngineAEADDec() throws InvalidKeyException { initAuthenticator(); - counter = 1; + initCounterValue = 1; + counter = initCounterValue; cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE); tag = new byte[TAG_LENGTH]; } diff --git a/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java b/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java index 345aee8d8af..779ff36e42c 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /** * @test - * @bug 8153029 + * @bug 8153029 8305091 * @library /test/lib * @run main ChaCha20NoReuse * @summary ChaCha20 Cipher Implementation (key/nonce reuse protection) @@ -376,7 +376,7 @@ public class ChaCha20NoReuse { } SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); - // Initialize and encrypt + // Initialize and decrypt cipher.init(testData.direction, key, spec); if (algorithm.equals(ALG_CC20_P1305)) { cipher.updateAAD(testData.aad); @@ -384,18 +384,12 @@ public class ChaCha20NoReuse { cipher.doFinal(testData.input); System.out.println("First decryption complete"); - // Now attempt to encrypt again without changing the key/IV - // This should fail. - try { - if (algorithm.equals(ALG_CC20_P1305)) { - cipher.updateAAD(testData.aad); - } - cipher.doFinal(testData.input); - throw new RuntimeException( - "Expected IllegalStateException not thrown"); - } catch (IllegalStateException ise) { - // Do nothing, this is what we expected to happen + // Now attempt to decrypt again without changing the key/IV + // We allow this scenario. + if (algorithm.equals(ALG_CC20_P1305)) { + cipher.updateAAD(testData.aad); } + cipher.doFinal(testData.input); } catch (Exception exc) { System.out.println("Unexpected exception: " + exc); exc.printStackTrace(); @@ -408,7 +402,8 @@ public class ChaCha20NoReuse { /** * Perform an AEAD decryption with corrupted data so the tag does not - * match. Then attempt to reuse the cipher without initialization. + * match. Then use the uncorrupted test vector input and attempt to + * reuse the cipher without initialization. */ public static final TestMethod decFailNoInit = new TestMethod() { @Override @@ -441,16 +436,16 @@ public class ChaCha20NoReuse { System.out.println("Expected decryption failure occurred"); } - // Make sure that despite the exception, the Cipher object is - // not in a state that would leave it initialized and able - // to process future decryption operations without init. - try { - cipher.updateAAD(testData.aad); - cipher.doFinal(testData.input); - throw new RuntimeException( - "Expected IllegalStateException not thrown"); - } catch (IllegalStateException ise) { - // Do nothing, this is what we expected to happen + // Even though an exception occurred during decryption, the + // Cipher object should be returned to its post-init state. + // Since this is a decryption operation, we should allow + // key/nonce reuse. It should properly decrypt the uncorrupted + // input. + cipher.updateAAD(testData.aad); + byte[] pText = cipher.doFinal(testData.input); + if (!Arrays.equals(pText, testData.expOutput)) { + throw new RuntimeException("FAIL: Attempted decryption " + + "did not match expected plaintext"); } } catch (Exception exc) { System.out.println("Unexpected exception: " + exc); @@ -562,18 +557,17 @@ public class ChaCha20NoReuse { if (algorithm.equals(ALG_CC20_P1305)) { cipher.updateAAD(testData.aad); } - cipher.doFinal(testData.input); + byte[] pText = cipher.doFinal(testData.input); + if (!Arrays.equals(pText, testData.expOutput)) { + throw new RuntimeException("FAIL: Attempted decryption " + + "did not match expected plaintext"); + } System.out.println("First decryption complete"); // Initializing after the completed decryption with - // the same key and nonce should fail. - try { - cipher.init(testData.direction, key, spec); - throw new RuntimeException( - "Expected InvalidKeyException not thrown"); - } catch (InvalidKeyException ike) { - // Do nothing, this is what we expected to happen - } + // the same key and nonce is allowed. + cipher.init(testData.direction, key, spec); + System.out.println("Successful reinit in DECRYPT_MODE"); } catch (Exception exc) { System.out.println("Unexpected exception: " + exc); exc.printStackTrace();