diff --git a/NoiseJava/src/com/southernstorm/noise/protocol/AESGCMCipherState.java b/NoiseJava/src/com/southernstorm/noise/protocol/AESGCMCipherState.java deleted file mode 100644 index b0eaf17..0000000 --- a/NoiseJava/src/com/southernstorm/noise/protocol/AESGCMCipherState.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 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 java.lang.reflect.InvocationTargetException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.AlgorithmParameterSpec; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.SecretKeySpec; - -/** - * Implements the "AESGCM" cipher for Noise using JCA/JCE. - */ -class AESGCMCipherState implements CipherState { - - private Cipher cipher; - private SecretKeySpec keySpec; - private long n; - private byte[] nf; - private Class gcmClass; - - /** - * Constructs a new cipher state for the "AESGCM" algorithm. - * - * @throws NoSuchAlgorithmException The system does not have a - * provider for this algorithm. - */ - public AESGCMCipherState() throws NoSuchAlgorithmException - { - try { - cipher = Cipher.getInstance("AES/GCM/NoPadding"); - } catch (NoSuchPaddingException e) { - // AES/GCM is available, but not the unpadded version? Huh? - throw new NoSuchAlgorithmException("AES/GCM/NoPadding not available", e); - } - keySpec = null; - n = 0; - nf = new byte [12]; - try { - gcmClass = Class.forName("javax.crypto.spec.GCMParameterSpec"); - } catch (ClassNotFoundException e) { - gcmClass = null; - } - - // Try to set a 256-bit key on the cipher. Some JCE's are - // configured to disallow 256-bit AES if an extra policy - // file has not been installed. - try { - SecretKeySpec spec = new SecretKeySpec(new byte [32], "AES"); - AlgorithmParameterSpec params = createGCMParams(); - cipher.init(Cipher.ENCRYPT_MODE, spec, params); - } catch (InvalidKeyException e) { - throw new NoSuchAlgorithmException("AES/GCM/NoPadding does not support 256-bit keys", e); - } catch (InvalidAlgorithmParameterException e) { - throw new NoSuchAlgorithmException("AES/GCM/NoPadding does not support 256-bit keys", e); - } finally { - n = 0; - } - } - - @Override - public void destroy() { - // There doesn't seem to be a standard API to clean out a Cipher. - // So we instead set the key and IV to all-zeroes to hopefully - // destroy the sensitive data in the cipher instances. - keySpec = new SecretKeySpec(new byte [32], "AES"); - n = 0; - try { - cipher.init(Cipher.ENCRYPT_MODE, keySpec, createGCMParams()); - } catch (InvalidKeyException e) { - // Shouldn't happen. - } catch (InvalidAlgorithmParameterException e) { - // Shouldn't happen. - } - } - - @Override - public String getCipherName() { - return "AESGCM"; - } - - @Override - public int getKeyLength() { - return 32; - } - - @Override - public int getMACLength() { - return keySpec != null ? 16 : 0; - } - - /** - * Creates a GCM parameter block for a new packet encrypt/decrypt operation. - * - * @return The GCM parameters for the current nonce. - */ - private AlgorithmParameterSpec createGCMParams() - { - nf[0] = (byte)0; - nf[1] = (byte)0; - nf[2] = (byte)0; - nf[3] = (byte)0; - nf[4] = (byte)(n >> 56); - nf[5] = (byte)(n >> 48); - nf[6] = (byte)(n >> 40); - nf[7] = (byte)(n >> 32); - nf[8] = (byte)(n >> 24); - nf[9] = (byte)(n >> 16); - nf[10] = (byte)(n >> 8); - nf[11] = (byte)n; - ++n; - if (gcmClass != null) { - // Equivalent of "return new GCMParameterSpec(128, nf)" without - // linking against GCMParameterSpec at compile time, which doesn't - // exist in older JDK's. - try { - return (AlgorithmParameterSpec)gcmClass.getConstructor(int.class, byte[].class).newInstance(128, nf); - } catch (NoSuchMethodException e) { - } catch (SecurityException e) { - } catch (InstantiationException e) { - } catch (IllegalAccessException e) { - } catch (IllegalArgumentException e) { - } catch (InvocationTargetException e) { - } - } - return null; - } - - @Override - public void initializeKey(byte[] key, int offset) { - keySpec = new SecretKeySpec(key, offset, 32, "AES"); - n = 0; - } - - @Override - public boolean hasKey() { - return keySpec != null; - } - - @Override - public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, - byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException { - int space; - if (ciphertextOffset > ciphertext.length) - space = 0; - else - space = ciphertext.length - ciphertextOffset; - if (keySpec == null) { - // The key is not set yet - return the plaintext as-is. - if (length > space) - throw new ShortBufferException(); - if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) - System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length); - return length; - } - if (space < 16 || length > (space - 16)) - throw new ShortBufferException(); - if (n < 0) - throw new IllegalStateException("Nonce has wrapped around"); - try { - cipher.init(Cipher.ENCRYPT_MODE, keySpec, createGCMParams()); - } catch (InvalidKeyException e) { - // Shouldn't happen. - return -1; - } catch (InvalidAlgorithmParameterException e) { - // Shouldn't happen. - return -1; - } - if (ad != null) - cipher.updateAAD(ad); - try { - int result = cipher.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset); - result += cipher.doFinal(ciphertext, ciphertextOffset + result); - return result; - } catch (IllegalBlockSizeException e) { - return -1; - } catch (BadPaddingException e) { - return -1; - } - } - - @Override - public int decryptWithAd(byte[] ad, byte[] ciphertext, - int ciphertextOffset, byte[] plaintext, int plaintextOffset, - int length) throws ShortBufferException, BadPaddingException { - int space; - if (ciphertextOffset > ciphertext.length) - space = 0; - else - space = ciphertext.length - ciphertextOffset; - if (length > space) - throw new ShortBufferException(); - if (plaintextOffset > plaintext.length) - space = 0; - else - space = plaintext.length - plaintextOffset; - if (keySpec == null) { - // The key is not set yet - return the ciphertext as-is. - if (plaintext != ciphertext || plaintextOffset != ciphertextOffset) - System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); - return length; - } - if (length < 16) - Noise.throwBadTagException(); - int dataLen = length - 16; - if (dataLen > space) - throw new ShortBufferException(); - if (n < 0) - throw new IllegalStateException("Nonce has wrapped around"); - try { - cipher.init(Cipher.DECRYPT_MODE, keySpec, createGCMParams()); - } catch (InvalidKeyException e) { - // Shouldn't happen. - Noise.throwBadTagException(); - } catch (InvalidAlgorithmParameterException e) { - // Shouldn't happen. - Noise.throwBadTagException(); - } - if (ad != null) - cipher.updateAAD(ad); - try { - int result = cipher.update(ciphertext, ciphertextOffset, length, plaintext, plaintextOffset); - result += cipher.doFinal(plaintext, plaintextOffset + result); - return result; - } catch (IllegalBlockSizeException e) { - Noise.throwBadTagException(); - return -1; - } - } - - @Override - public CipherState fork(byte[] key, int offset) { - CipherState cipher; - try { - cipher = new AESGCMCipherState(); - } catch (NoSuchAlgorithmException e) { - // Shouldn't happen. - return null; - } - cipher.initializeKey(key, offset); - return cipher; - } - - @Override - public void setNonce(long nonce) { - if (nonce < n) - throw new IllegalArgumentException("Nonce values cannot go backwards"); - n = nonce; - } -} diff --git a/NoiseJava/src/com/southernstorm/noise/protocol/Noise.java b/NoiseJava/src/com/southernstorm/noise/protocol/Noise.java index a950a3f..f5dbb78 100644 --- a/NoiseJava/src/com/southernstorm/noise/protocol/Noise.java +++ b/NoiseJava/src/com/southernstorm/noise/protocol/Noise.java @@ -109,20 +109,16 @@ public final class Noise { if (name.equals("AESGCM")) { if (forceFallbacks) return new AESGCMFallbackCipherState(); - // AESGCMCipherState doesn't seem to work yet - FIXME. - //try { - // return new AESGCMCipherState(); - //} catch (NoSuchAlgorithmException e) { - // The JCA/JCE does not have "AES/GCM/NoPadding" so try - // emulating it on top of "AES/CTR/NoPadding" instead. - try { - return new AESGCMOnCtrCipherState(); - } catch (NoSuchAlgorithmException e1) { - // Could not find anything useful in the JCA/JCE so - // use the pure Java fallback implementation instead. - return new AESGCMFallbackCipherState(); - } - //} + // "AES/GCM/NoPadding" exists in some recent JDK's but it is flaky + // to use and not easily back-portable to older Android versions. + // We instead emulate AESGCM on top of "AES/CTR/NoPadding". + try { + return new AESGCMOnCtrCipherState(); + } catch (NoSuchAlgorithmException e1) { + // Could not find anything useful in the JCA/JCE so + // use the pure Java fallback implementation instead. + return new AESGCMFallbackCipherState(); + } } else if (name.equals("ChaChaPoly")) { return new ChaChaPolyCipherState(); } diff --git a/README.md b/README.md index 292863b..82f34f3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Noise-Java will try to use them if present: * SHA-256 * SHA-512 - * AES/GCM/NoPadding * AES/CTR/NoPadding Some JDK installations restrict the use of 256-bit AES keys. You may need to