diff --git a/briar-android/src/net/sf/briar/android/AndroidModule.java b/briar-android/src/net/sf/briar/android/AndroidModule.java index 29a745489a7104f5c9f8485a67912c6dfe4525fb..83ec34737014019708dcf2a0e0dcbeea57c9a106 100644 --- a/briar-android/src/net/sf/briar/android/AndroidModule.java +++ b/briar-android/src/net/sf/briar/android/AndroidModule.java @@ -1,6 +1,7 @@ package net.sf.briar.android; import net.sf.briar.api.android.AndroidExecutor; +import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.ReferenceManager; import com.google.inject.AbstractModule; @@ -11,6 +12,8 @@ public class AndroidModule extends AbstractModule { @Override protected void configure() { bind(AndroidExecutor.class).to(AndroidExecutorImpl.class); + bind(BundleEncrypter.class).to(BundleEncrypterImpl.class).in( + Singleton.class); bind(ReferenceManager.class).to(ReferenceManagerImpl.class).in( Singleton.class); } diff --git a/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java b/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ce8f9912d75cdb9dd70330b4c6fbbebb9d719b2e --- /dev/null +++ b/briar-android/src/net/sf/briar/android/BundleEncrypterImpl.java @@ -0,0 +1,89 @@ +package net.sf.briar.android; + +import static javax.crypto.Cipher.DECRYPT_MODE; +import static javax.crypto.Cipher.ENCRYPT_MODE; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +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 final AuthenticatedCipher cipher; + private final SecureRandom random; + private final ErasableKey key; + private final int blockSize, macLength; + + @Inject + BundleEncrypterImpl(CryptoComponent crypto) { + cipher = crypto.getBundleCipher(); + random = crypto.getSecureRandom(); + key = crypto.generateSecretKey(); + blockSize = cipher.getBlockSize(); + macLength = cipher.getMacLength(); + } + + @Override + public void encrypt(Bundle b) { + // Marshall the plaintext contents into a byte array + Parcel p = Parcel.obtain(); + b.writeToParcel(p, 0); + byte[] plaintext = p.marshall(); + p.recycle(); + // Encrypt the byte array using the storage key and 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); + } + ByteUtils.erase(plaintext); + // Replace the plaintext contents with the IV and 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(); + 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 storage key and 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 + Parcel p = Parcel.obtain(); + p.unmarshall(plaintext, 0, plaintext.length); + ByteUtils.erase(plaintext); + // Replace the IV and the ciphertext with the plaintext contents + b.remove("net.sf.briar.IV"); + b.remove("net.sf.briar.CIPHERTEXT"); + p.setDataPosition(0); + b.readFromParcel(p); + p.recycle(); + return true; + } +} diff --git a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldActivity.java b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldActivity.java index afa9c32e8d348496a153215b2a0fe2a3c050db0c..e253024c6064978ea7d1cc29fc57576c2c5d3fc2 100644 --- a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldActivity.java +++ b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldActivity.java @@ -28,8 +28,8 @@ implements OnClickListener { Logger.getLogger(HelloWorldActivity.class.getName()); @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onCreate(Bundle state) { + super.onCreate(state); if(LOG.isLoggable(INFO)) LOG.info("Created"); LinearLayout layout = new LinearLayout(this); layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT)); diff --git a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java index 1b1172c10523ba1dda044140d8ff741acf56238b..5de5df339ecced46fadd2d55addad3bd1c380e71 100644 --- a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java +++ b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java @@ -5,6 +5,7 @@ import static java.util.logging.Level.WARNING; import java.util.concurrent.Executor; import java.util.logging.Logger; +import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.ReferenceManager; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.db.DatabaseComponent; @@ -30,6 +31,7 @@ implements InvitationListener { @Inject @DatabaseExecutor private Executor dbExecutor; @Inject private InvitationTaskFactory invitationTaskFactory; @Inject private ReferenceManager referenceManager; + @Inject private BundleEncrypter bundleEncrypter; // All of the following must be accessed on the UI thread private AddContactView view = null; @@ -46,6 +48,7 @@ implements InvitationListener { @Override public void onCreate(Bundle state) { super.onCreate(state); + if(state != null && !bundleEncrypter.decrypt(state)) state = null; if(state == null) { // This is a new activity setView(new NetworkSetupView(this)); @@ -120,14 +123,16 @@ implements InvitationListener { @Override public void onSaveInstanceState(Bundle state) { super.onSaveInstanceState(state); - state.putString("net.sf.briar.NETWORK_NAME", networkName); - state.putBoolean("net.sf.briar.USE_BLUETOOTH", useBluetooth); - state.putInt("net.sf.briar.LOCAL_CODE", localInvitationCode); - state.putInt("net.sf.briar.REMOTE_CODE", remoteInvitationCode); - state.putBoolean("net.sf.briar.FAILED", connectionFailed); - state.putBoolean("net.sf.briar.MATCHED", localMatched && remoteMatched); - if(task != null) - state.putLong("net.sf.briar.TASK_HANDLE", taskHandle); + Bundle b = new Bundle(); + b.putString("net.sf.briar.NETWORK_NAME", networkName); + b.putBoolean("net.sf.briar.USE_BLUETOOTH", useBluetooth); + b.putInt("net.sf.briar.LOCAL_CODE", localInvitationCode); + b.putInt("net.sf.briar.REMOTE_CODE", remoteInvitationCode); + b.putBoolean("net.sf.briar.FAILED", connectionFailed); + b.putBoolean("net.sf.briar.MATCHED", localMatched && remoteMatched); + if(task != null) b.putLong("net.sf.briar.TASK_HANDLE", taskHandle); + bundleEncrypter.encrypt(b); + state.putAll(b); } @Override diff --git a/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java b/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java index aef34f05fb9bd361cc7d21bcb93c082ef4c3fc4f..50d652f070fd1121958422499690acae7b432ef2 100644 --- a/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java +++ b/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java @@ -17,6 +17,8 @@ import android.widget.TextView.OnEditorActionListener; public class ContactAddedView extends AddContactView implements OnClickListener, OnEditorActionListener { + EditText nicknameEntry = null; + ContactAddedView(Context ctx) { super(ctx); } @@ -50,7 +52,7 @@ OnEditorActionListener { innerLayout.setGravity(CENTER); final Button done = new Button(ctx); - EditText nicknameEntry = new EditText(ctx) { + nicknameEntry = new EditText(ctx) { @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { @@ -71,11 +73,13 @@ OnEditorActionListener { } public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) { - if(textView.getText().length() > 0) container.finish(); + String nickname = textView.getText().toString(); + if(nickname.length() > 0) container.addContactAndFinish(nickname); return true; } public void onClick(View view) { - container.finish(); // Done + String nickname = nicknameEntry.getText().toString(); + container.addContactAndFinish(nickname); } } diff --git a/briar-api/src/net/sf/briar/api/android/BundleEncrypter.java b/briar-api/src/net/sf/briar/api/android/BundleEncrypter.java new file mode 100644 index 0000000000000000000000000000000000000000..e2af1a8324e564645675212c4560d395bc2b2718 --- /dev/null +++ b/briar-api/src/net/sf/briar/api/android/BundleEncrypter.java @@ -0,0 +1,27 @@ +package net.sf.briar.api.android; + +import android.os.Bundle; + +/** + * Encrypts and decrypts the contents of bundles in case the operating system + * writes them to unencrypted storage. + * <p> + * This interface is designed to be accessed from the UI thread, so + * implementations may not be thread-safe. + */ +public interface BundleEncrypter { + + /** + * Encrypts the given bundle, replacing its contents with the encrypted + * data. + */ + void encrypt(Bundle b); + + /** + * Decrypts the given bundle, replacing its contents with the decrypted + * data, or returns false if the bundle contains invalid data, which may + * occur if the process that created the encrypted bundle was terminated + * and replaced by the current process. + */ + boolean decrypt(Bundle b); +} diff --git a/briar-api/src/net/sf/briar/api/crypto/AuthenticatedCipher.java b/briar-api/src/net/sf/briar/api/crypto/AuthenticatedCipher.java index e6a8cd8adf20b5eebd12610acb373acf417548fa..0dfa322af5e79bd872598e3408b0b511d12769fe 100644 --- a/briar-api/src/net/sf/briar/api/crypto/AuthenticatedCipher.java +++ b/briar-api/src/net/sf/briar/api/crypto/AuthenticatedCipher.java @@ -26,4 +26,7 @@ public interface AuthenticatedCipher { /** Returns the length of the message authenticated code (MAC) in bytes. */ int getMacLength(); + + /** Returns the block size of the cipher in bytes. */ + int getBlockSize(); } 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 0c792b068d3723da04da7590095321a73739723a..e59df9c5ef04b667f6646161594147628cdce619 100644 --- a/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java +++ b/briar-api/src/net/sf/briar/api/crypto/CryptoComponent.java @@ -65,7 +65,7 @@ public interface CryptoComponent { KeyParser getSignatureKeyParser(); - ErasableKey generateTestKey(); + ErasableKey generateSecretKey(); MessageDigest getMessageDigest(); @@ -77,5 +77,7 @@ public interface CryptoComponent { AuthenticatedCipher getFrameCipher(); + AuthenticatedCipher getBundleCipher(); + Signature getSignature(); } diff --git a/briar-core/src/net/sf/briar/crypto/AuthenticatedCipherImpl.java b/briar-core/src/net/sf/briar/crypto/AuthenticatedCipherImpl.java index 0514e1e69b50a0f2c7f1097bd9a85956612b38ab..57d39f792df5672a6a380020b0f5a8c90c76b786 100644 --- a/briar-core/src/net/sf/briar/crypto/AuthenticatedCipherImpl.java +++ b/briar-core/src/net/sf/briar/crypto/AuthenticatedCipherImpl.java @@ -67,4 +67,8 @@ class AuthenticatedCipherImpl implements AuthenticatedCipher { public int getMacLength() { return macLength; } + + public int getBlockSize() { + return cipher.getUnderlyingCipher().getBlockSize(); + } } diff --git a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java index a59ee4924b40684dfc361fa43b3bd0c0cbe08e8b..344a863bc3eb6f08da465a201d8fc9f3901a3b7d 100644 --- a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java +++ b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java @@ -332,7 +332,7 @@ class CryptoComponentImpl implements CryptoComponent { return signatureKeyParser; } - public ErasableKey generateTestKey() { + public ErasableKey generateSecretKey() { byte[] b = new byte[SECRET_KEY_BYTES]; secureRandom.nextBytes(b); return new ErasableKeyImpl(b, SECRET_KEY_ALGO); @@ -377,4 +377,11 @@ class CryptoComponentImpl implements CryptoComponent { AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH); } + + public AuthenticatedCipher getBundleCipher() { + // 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); + } } diff --git a/briar-tests/src/net/sf/briar/transport/IncomingEncryptionLayerTest.java b/briar-tests/src/net/sf/briar/transport/IncomingEncryptionLayerTest.java index 65ed6844e0287a1549a3193809e4c1445bb9f17f..84234fa3ae8ab1ac1ca3e32ab0d24955104de805 100644 --- a/briar-tests/src/net/sf/briar/transport/IncomingEncryptionLayerTest.java +++ b/briar-tests/src/net/sf/briar/transport/IncomingEncryptionLayerTest.java @@ -37,7 +37,7 @@ public class IncomingEncryptionLayerTest extends BriarTestCase { Injector i = Guice.createInjector(new CryptoModule()); crypto = i.getInstance(CryptoComponent.class); frameCipher = crypto.getFrameCipher(); - frameKey = crypto.generateTestKey(); + frameKey = crypto.generateSecretKey(); } @Test diff --git a/briar-tests/src/net/sf/briar/transport/OutgoingEncryptionLayerTest.java b/briar-tests/src/net/sf/briar/transport/OutgoingEncryptionLayerTest.java index 475e5d9adb4d26237e533194994056c6ac86c504..5c9e129fee84a40910fe11529a326a833f46594d 100644 --- a/briar-tests/src/net/sf/briar/transport/OutgoingEncryptionLayerTest.java +++ b/briar-tests/src/net/sf/briar/transport/OutgoingEncryptionLayerTest.java @@ -46,7 +46,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH]; byte[] plaintext = new byte[FRAME_LENGTH - MAC_LENGTH]; byte[] ciphertext = new byte[FRAME_LENGTH]; - ErasableKey frameKey = crypto.generateTestKey(); + ErasableKey frameKey = crypto.generateSecretKey(); // Calculate the expected ciphertext FrameEncoder.encodeIv(iv, 0); FrameEncoder.encodeAad(aad, 0, plaintext.length); @@ -71,7 +71,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Initiator's constructor OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), + 10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(), FRAME_LENGTH, tag); // Write an empty final frame without having written any other frames o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true); @@ -84,7 +84,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Responder's constructor OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), + 10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(), FRAME_LENGTH); // Write an empty final frame without having written any other frames o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true); @@ -98,7 +98,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Initiator's constructor OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), + 10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(), FRAME_LENGTH, tag); // There should be space for nine full frames and one partial frame byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH]; @@ -122,7 +122,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Responder's constructor OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - 10 * FRAME_LENGTH, frameCipher, crypto.generateTestKey(), + 10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(), FRAME_LENGTH); // There should be space for ten full frames assertEquals(10 * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity()); @@ -145,7 +145,7 @@ public class OutgoingEncryptionLayerTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); // The connection has plenty of space so we're limited by frame numbers OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out, - Long.MAX_VALUE, frameCipher, crypto.generateTestKey(), + Long.MAX_VALUE, frameCipher, crypto.generateSecretKey(), FRAME_LENGTH); // There should be enough frame numbers for 2^32 frames assertEquals((1L << 32) * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());