From 9215537d8042b3525d5c0fe2aaf5d07d1816bdf5 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 19 Feb 2013 00:41:50 +0000
Subject: [PATCH] Refactored bundle encryption code.

---
 .../sf/briar/android/BundleEncrypterImpl.java | 52 ++++--------------
 .../sf/briar/api/crypto/CryptoComponent.java  | 15 +++++-
 .../sf/briar/crypto/CryptoComponentImpl.java  | 53 ++++++++++++++++++-
 3 files changed, 75 insertions(+), 45 deletions(-)

diff --git a/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java b/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java
index 68305ffe6c..108c3d78a9 100644
--- a/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java
+++ b/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java
@@ -1,41 +1,27 @@
 package net.sf.briar.android;
 
 import static java.util.logging.Level.INFO;
-import static javax.crypto.Cipher.DECRYPT_MODE;
-import static javax.crypto.Cipher.ENCRYPT_MODE;
 
-import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
 import java.util.logging.Logger;
 
 import net.sf.briar.api.android.BundleEncrypter;
-import net.sf.briar.api.crypto.AuthenticatedCipher;
 import net.sf.briar.api.crypto.CryptoComponent;
-import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.util.ByteUtils;
 import android.os.Bundle;
 import android.os.Parcel;
 
 import com.google.inject.Inject;
 
-// This class is not thread-safe
 class BundleEncrypterImpl implements BundleEncrypter {
 
 	private static final Logger LOG =
 			Logger.getLogger(BundleEncrypterImpl.class.getName());
 
-	private final AuthenticatedCipher cipher;
-	private final SecureRandom random;
-	private final ErasableKey key;
-	private final int blockSize, macLength;
+	private final CryptoComponent crypto;
 
 	@Inject
 	BundleEncrypterImpl(CryptoComponent crypto) {
-		cipher = crypto.getBundleCipher();
-		random = crypto.getSecureRandom();
-		key = crypto.generateSecretKey();
-		blockSize = cipher.getBlockSize();
-		macLength = cipher.getMacLength();
+		this.crypto = crypto;
 	}
 
 	@Override
@@ -49,41 +35,23 @@ class BundleEncrypterImpl implements BundleEncrypter {
 			LOG.info("Marshalled " + b.size() + " mappings, "
 					+ plaintext.length + " plaintext bytes");
 		}
-		// Encrypt the byte array using a random IV
-		byte[] iv = new byte[blockSize];
-		random.nextBytes(iv);
-		byte[] ciphertext = new byte[plaintext.length + macLength];
-		try {
-			cipher.init(ENCRYPT_MODE, key, iv, null);
-			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
-		} catch(GeneralSecurityException e) {
-			throw new RuntimeException(e);
-		}
+		// Encrypt the plaintext
+		byte[] ciphertext = crypto.encryptTemporaryStorage(plaintext);
 		ByteUtils.erase(plaintext);
-		// Replace the plaintext contents with the IV and the ciphertext
+		// Replace the plaintext contents with the ciphertext
 		b.clear();
-		b.putByteArray("net.sf.briar.IV", iv);
 		b.putByteArray("net.sf.briar.CIPHERTEXT", ciphertext);
 	}
 
 	@Override
 	public boolean decrypt(Bundle b) {
-		// Retrieve the IV and the ciphertext
-		byte[] iv = b.getByteArray("net.sf.briar.IV");
-		if(iv == null) throw new IllegalArgumentException();
-		if(iv.length != blockSize) throw new IllegalArgumentException();
+		// Retrieve the ciphertext
 		byte[] ciphertext = b.getByteArray("net.sf.briar.CIPHERTEXT");
 		if(ciphertext == null) throw new IllegalArgumentException();
-		if(ciphertext.length < macLength) throw new IllegalArgumentException();
-		// Decrypt the ciphertext using the IV
-		byte[] plaintext = new byte[ciphertext.length - macLength];
-		try {
-			cipher.init(DECRYPT_MODE, key, iv, null);
-			cipher.doFinal(ciphertext, 0, ciphertext.length, plaintext, 0);
-		} catch(GeneralSecurityException e) {
-			return false; // Invalid ciphertext
-		}
-		// Unmarshall the byte array
+		// Decrypt the ciphertext
+		byte[] plaintext = crypto.decryptTemporaryStorage(ciphertext);
+		if(plaintext == null) return false;
+		// Unmarshall the plaintext
 		Parcel p = Parcel.obtain();
 		p.unmarshall(plaintext, 0, plaintext.length);
 		ByteUtils.erase(plaintext);
diff --git a/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java b/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java
index e59df9c5ef..aa88207c16 100644
--- a/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java
+++ b/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java
@@ -77,7 +77,18 @@ public interface CryptoComponent {
 
 	AuthenticatedCipher getFrameCipher();
 
-	AuthenticatedCipher getBundleCipher();
-
 	Signature getSignature();
+
+	/**
+	 * Encrypts the given plaintext so it can be written to temporary storage.
+	 * The ciphertext will not be decryptable after the app restarts.
+	 */
+	byte[] encryptTemporaryStorage(byte[] plaintext);
+
+	/**
+	 * Decrypts the given ciphertext that has been read from temporary storage.
+	 * Returns null if the ciphertext is not decryptable (for example, if it
+	 * was written before the app restarted).
+	 */
+	byte[] decryptTemporaryStorage(byte[] ciphertext);
 }
diff --git a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
index 344a863bc3..0ee33c10cb 100644
--- a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -1,5 +1,6 @@
 package net.sf.briar.crypto;
 
+import static javax.crypto.Cipher.DECRYPT_MODE;
 import static javax.crypto.Cipher.ENCRYPT_MODE;
 import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
@@ -7,6 +8,7 @@ import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -56,6 +58,7 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final String SIGNATURE_ALGO = "ECDSA";
 	private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
 	private static final int GCM_MAC_LENGTH = 16; // 128 bits
+	private static final int STORAGE_IV_LENGTH = 32; // 256 bits
 
 	// Labels for key derivation
 	private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
@@ -116,6 +119,7 @@ class CryptoComponentImpl implements CryptoComponent {
 	private final KeyPairGenerator agreementKeyPairGenerator;
 	private final KeyPairGenerator signatureKeyPairGenerator;
 	private final SecureRandom secureRandom;
+	private final ErasableKey temporaryStorageKey;
 
 	@Inject
 	CryptoComponentImpl() {
@@ -139,6 +143,7 @@ class CryptoComponentImpl implements CryptoComponent {
 			throw new RuntimeException(e);
 		}
 		secureRandom = new SecureRandom();
+		temporaryStorageKey = generateSecretKey();
 	}
 
 	public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
@@ -378,10 +383,56 @@ class CryptoComponentImpl implements CryptoComponent {
 		return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
 	}
 
-	public AuthenticatedCipher getBundleCipher() {
+	private AuthenticatedCipher getTemporaryStorageCipher() {
 		// This code is specific to BouncyCastle because javax.crypto.Cipher
 		// doesn't support additional authenticated data until Java 7
 		AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
 		return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
 	}
+
+	public byte[] encryptTemporaryStorage(byte[] plaintext) {
+		AuthenticatedCipher cipher = getTemporaryStorageCipher();
+		// Generate a random IV
+		byte[] iv = new byte[STORAGE_IV_LENGTH];
+		secureRandom.nextBytes(iv);
+		// The output contains the IV, ciphertext and MAC
+		int ciphertextLength = iv.length + plaintext.length + GCM_MAC_LENGTH;
+		byte[] ciphertext = new byte[ciphertextLength];
+		System.arraycopy(iv, 0, ciphertext, 0, iv.length);
+		// Initialise the cipher and encrypt the plaintext
+		try {
+			cipher.init(ENCRYPT_MODE, temporaryStorageKey, iv, null);
+			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
+					iv.length);
+		} catch(GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		}
+		return ciphertext;
+	}
+
+	public byte[] decryptTemporaryStorage(byte[] ciphertext) {
+		// The input contains the IV, ciphertext and MAC
+		if(ciphertext.length < STORAGE_IV_LENGTH + GCM_MAC_LENGTH)
+			throw new IllegalArgumentException();
+		AuthenticatedCipher cipher = getTemporaryStorageCipher();
+		// Copy the IV
+		byte[] iv = new byte[STORAGE_IV_LENGTH];
+		System.arraycopy(ciphertext, 0, iv, 0, iv.length);
+		// Initialise the cipher
+		try {
+			cipher.init(DECRYPT_MODE, temporaryStorageKey, iv, null);
+		} catch(InvalidKeyException e) {
+			throw new RuntimeException(e);
+		}
+		// Try to decrypt the ciphertext (may be invalid)
+		int plaintextLength = ciphertext.length - iv.length - GCM_MAC_LENGTH;
+		byte[] plaintext = new byte[plaintextLength];
+		try {
+			cipher.doFinal(ciphertext, iv.length, ciphertext.length - iv.length,
+					plaintext, 0);
+		} catch(GeneralSecurityException e) {
+			return null; // Invalid ciphertext
+		}
+		return plaintext;
+	}
 }
-- 
GitLab