diff --git a/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java b/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java index 7cd2b74..5d1711b 100644 --- a/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java +++ b/src/main/java/com/southernstorm/noise/protocol/AESGCMFallbackCipherState.java @@ -109,10 +109,6 @@ public boolean hasKey() { */ private void setup(byte[] ad) { - // Check for nonce wrap-around. - if (n == -1L) - throw new IllegalStateException("Nonce has wrapped around"); - // Format the counter/IV block. iv[0] = 0; iv[1] = 0; @@ -262,4 +258,9 @@ public CipherState fork(byte[] key, int offset) { public void setNonce(long nonce) { n = nonce; } + + @Override + public long getNonce() { + return n; + } } diff --git a/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java b/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java index deada67..23cf10b 100644 --- a/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java +++ b/src/main/java/com/southernstorm/noise/protocol/AESGCMOnCtrCipherState.java @@ -169,10 +169,6 @@ public boolean hasKey() { */ private void setup(byte[] ad) throws InvalidKeyException, InvalidAlgorithmParameterException { - // Check for nonce wrap-around. - if (n == -1L) - throw new IllegalStateException("Nonce has wrapped around"); - // Format the counter/IV block for AES/CTR/NoPadding. iv[0] = 0; iv[1] = 0; @@ -332,4 +328,9 @@ public CipherState fork(byte[] key, int offset) { public void setNonce(long nonce) { n = nonce; } + + @Override + public long getNonce() { + return n; + } } diff --git a/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java b/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java index 25b1a4c..9ae3981 100644 --- a/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java +++ b/src/main/java/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java @@ -136,8 +136,6 @@ private static void xorBlock(byte[] input, int inputOffset, byte[] output, int o */ private void setup(byte[] ad) { - if (n == -1L) - throw new IllegalStateException("Nonce has wrapped around"); ChaChaCore.initIV(input, n++); ChaChaCore.hash(output, input); Arrays.fill(polyKey, (byte)0); @@ -287,4 +285,9 @@ public CipherState fork(byte[] key, int offset) { public void setNonce(long nonce) { n = nonce; } + + @Override + public long getNonce() { + return n; + } } diff --git a/src/main/java/com/southernstorm/noise/protocol/CipherState.java b/src/main/java/com/southernstorm/noise/protocol/CipherState.java index 3168139..e8e7a3a 100644 --- a/src/main/java/com/southernstorm/noise/protocol/CipherState.java +++ b/src/main/java/com/southernstorm/noise/protocol/CipherState.java @@ -155,4 +155,28 @@ public interface CipherState extends Destroyable { * value goes backwards then security may be compromised. */ void setNonce(long nonce); + + /** + * Gets the nonce value. + * + * @return The current nonce in this cipher state. + */ + long getNonce(); + + /** + * Executes a rekey in-place for this cipher. + */ + default void rekey() throws ShortBufferException { + if (getKeyLength() != 32) { + throw new UnsupportedOperationException("rekey is only supported by default for 32 byte keys"); + } + long originalNonce = getNonce(); // Save the nonce for after the rekey + byte[] newKey = new byte[32+getMACLength()]; + setNonce(-1); // Set to 2^64-1 + encryptWithAd(new byte[] {}, + new byte[] { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 }, 0, + newKey, 0, 32); + initializeKey(newKey, 0); + setNonce(originalNonce); // Restore nonce per specification + } } diff --git a/src/main/java/com/southernstorm/noise/protocol/NonceCheckCipherState.java b/src/main/java/com/southernstorm/noise/protocol/NonceCheckCipherState.java new file mode 100644 index 0000000..e180038 --- /dev/null +++ b/src/main/java/com/southernstorm/noise/protocol/NonceCheckCipherState.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import javax.crypto.BadPaddingException; +import javax.crypto.ShortBufferException; + +/** + * A CipherState implementation that checks + * the nonce not to be the reserved rekey() + * value on encrypt and decrypt. + */ +public final class NonceCheckCipherState implements CipherState { + private final CipherState delegate; + + public NonceCheckCipherState(CipherState delegate) { + this.delegate = delegate; + } + + @Override + public String getCipherName() { + return delegate.getCipherName(); + } + + @Override + public int getKeyLength() { + return delegate.getKeyLength(); + } + + @Override + public int getMACLength() { + return delegate.getMACLength(); + } + + @Override + public void initializeKey(byte[] key, int offset) { + delegate.initializeKey(key, offset); + } + + @Override + public boolean hasKey() { + return delegate.hasKey(); + } + + @Override + public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException { + // Check for nonce wrap-around. + if (getNonce() == -1L) + throw new IllegalStateException("Nonce has wrapped around"); + + return delegate.encryptWithAd(ad, plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); + } + + @Override + public int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException { + // Check for nonce wrap-around. + if (getNonce() == -1L) + throw new IllegalStateException("Nonce has wrapped around"); + + return delegate.decryptWithAd(ad, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + + @Override + public CipherState fork(byte[] key, int offset) { + return delegate.fork(key, offset); + } + + @Override + public void setNonce(long nonce) { + delegate.setNonce(nonce); + } + + @Override + public long getNonce() { + return delegate.getNonce(); + } + + @Override + public void rekey() throws ShortBufferException { + delegate.rekey(); + } + + @Override + public void destroy() { + delegate.destroy(); + } +} diff --git a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java index ae54fd3..cec41ac 100644 --- a/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java +++ b/src/main/java/com/southernstorm/noise/protocol/SymmetricState.java @@ -277,7 +277,7 @@ public CipherStatePair split(byte[] secondaryKey, int offset, int length) try { c1 = cipher.fork(k1, 0); c2 = cipher.fork(k2, 0); - pair = new CipherStatePair(c1, c2); + pair = new CipherStatePair(new NonceCheckCipherState(c1), new NonceCheckCipherState(c2)); } finally { if (c1 == null || c2 == null || pair == null) { // Could not create some of the objects. Clean up the others