diff --git a/components/net/sf/briar/crypto/ErasableKeyImpl.java b/components/net/sf/briar/crypto/ErasableKeyImpl.java index 5267e394d5b65feff23bf353e89189a1599695c8..b50c424797e1938d2bf4320670f2ec4cdc888cfd 100644 --- a/components/net/sf/briar/crypto/ErasableKeyImpl.java +++ b/components/net/sf/briar/crypto/ErasableKeyImpl.java @@ -1,6 +1,7 @@ package net.sf.briar.crypto; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.util.ByteUtils; @@ -11,6 +12,8 @@ class ErasableKeyImpl implements ErasableKey { private final byte[] key; private final String algorithm; + + private Collection<byte[]> copies = null; private boolean erased = false; ErasableKeyImpl(byte[] key, String algorithm) { @@ -26,6 +29,8 @@ class ErasableKeyImpl implements ErasableKey { if(erased) throw new IllegalStateException(); byte[] b = new byte[key.length]; System.arraycopy(key, 0, b, 0, key.length); + if(copies == null) copies = new ArrayList<byte[]>(); + copies.add(b); return b; } @@ -40,21 +45,7 @@ class ErasableKeyImpl implements ErasableKey { public void erase() { if(erased) throw new IllegalStateException(); ByteUtils.erase(key); + if(copies != null) for(byte[] b : copies) ByteUtils.erase(b); erased = true; } - - @Override - public int hashCode() { - // Not good, but the array can't be used because it's mutable - return algorithm.hashCode(); - } - - @Override - public boolean equals(Object o) { - if(o instanceof ErasableKeyImpl) { - ErasableKeyImpl e = (ErasableKeyImpl) o; - return algorithm.equals(e.algorithm) && Arrays.equals(key, e.key); - } - return false; - } } diff --git a/test/build.xml b/test/build.xml index 6b5083f53ba8ed8207ea2d93dc9656ba1e31b7ae..2644843b78feab0845136cee55befdd64c8bc34b 100644 --- a/test/build.xml +++ b/test/build.xml @@ -17,6 +17,7 @@ <test name='net.sf.briar.LockFairnessTest'/> <test name='net.sf.briar.ProtocolIntegrationTest'/> <test name='net.sf.briar.crypto.CounterModeTest'/> + <test name='net.sf.briar.crypto.ErasableKeyTest'/> <test name='net.sf.briar.crypto.KeyDerivationTest'/> <test name='net.sf.briar.db.BasicH2Test'/> <test name='net.sf.briar.db.DatabaseCleanerImplTest'/> diff --git a/test/net/sf/briar/crypto/ErasableKeyTest.java b/test/net/sf/briar/crypto/ErasableKeyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..72f78b19534f9783397d8dd41bc1c38abdeeff22 --- /dev/null +++ b/test/net/sf/briar/crypto/ErasableKeyTest.java @@ -0,0 +1,79 @@ +package net.sf.briar.crypto; + +import static org.junit.Assert.assertArrayEquals; + +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; + +import junit.framework.TestCase; +import net.sf.briar.api.crypto.ErasableKey; + +import org.junit.Test; + +public class ErasableKeyTest extends TestCase { + + private static final String CIPHER = "AES"; + private static final String CIPHER_MODE = "AES/CTR/NoPadding"; + private static final int IV_BYTES = 16; // 128 bits + private static final int KEY_BYTES = 32; // 256 bits + private static final String MAC = "HMacSHA256"; + + private final Random random = new Random(); + + @Test + public void testCopiesAreErased() { + byte[] master = new byte[KEY_BYTES]; + random.nextBytes(master); + ErasableKey k = new ErasableKeyImpl(master, CIPHER); + byte[] copy = k.getEncoded(); + assertArrayEquals(master, copy); + k.erase(); + byte[] blank = new byte[KEY_BYTES]; + assertArrayEquals(blank, master); + assertArrayEquals(blank, copy); + } + + @Test + public void testErasureDoesNotAffectCipher() throws Exception { + byte[] key = new byte[KEY_BYTES]; + random.nextBytes(key); + ErasableKey k = new ErasableKeyImpl(key, CIPHER); + Cipher c = Cipher.getInstance(CIPHER_MODE); + IvParameterSpec iv = new IvParameterSpec(new byte[IV_BYTES]); + c.init(Cipher.ENCRYPT_MODE, k, iv); + // Encrypt a blank plaintext + byte[] plaintext = new byte[123]; + byte[] ciphertext = c.doFinal(plaintext); + // Erase the key and encrypt again - erase() was called after doFinal() + k.erase(); + byte[] ciphertext1 = c.doFinal(plaintext); + // Encrypt again - this time erase() was called before doFinal() + byte[] ciphertext2 = c.doFinal(plaintext); + // The ciphertexts should match + assertArrayEquals(ciphertext, ciphertext1); + assertArrayEquals(ciphertext, ciphertext2); + } + + @Test + public void testErasureDoesNotAffectMac() throws Exception { + byte[] key = new byte[KEY_BYTES]; + random.nextBytes(key); + ErasableKey k = new ErasableKeyImpl(key, CIPHER); + Mac m = Mac.getInstance(MAC); + m.init(k); + // Authenticate a blank plaintext + byte[] plaintext = new byte[123]; + byte[] mac = m.doFinal(plaintext); + // Erase the key and authenticate again + k.erase(); + byte[] mac1 = m.doFinal(plaintext); + // Authenticate again + byte[] mac2 = m.doFinal(plaintext); + // The MACs should match + assertArrayEquals(mac, mac1); + assertArrayEquals(mac, mac2); + } +}