Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Commit

Permalink
Merge branch 'migration' FREEBIE
Browse files Browse the repository at this point in the history
  • Loading branch information
rhodey committed Aug 23, 2014
2 parents 8bf8b8f + 128a120 commit e3c61e1
Show file tree
Hide file tree
Showing 39 changed files with 2,015 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.anhonesteffort.flock.test.crypto;

import android.content.Context;
import android.content.SharedPreferences;
import android.test.AndroidTestCase;

import com.google.common.base.Optional;

import org.anhonesteffort.flock.crypto.KeyHelper;
import org.anhonesteffort.flock.crypto.KeyStore;
import org.anhonesteffort.flock.crypto.MasterCipher;
import org.anhonesteffort.flock.util.Base64;
import org.anhonesteffort.flock.util.Util;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
* rhodey.
*/
public class KeyHelperTest extends AndroidTestCase {

private static final String HACK_PREFERENCES_NAME = "org.anhonesteffort.flock.crypto.KeyStore";

private Context context;

@Override
protected void setUp() throws Exception {
context = this.getContext();
}

private static void saveBytes(Context context, String key, byte[] value) {
SharedPreferences settings = context.getSharedPreferences(HACK_PREFERENCES_NAME, Context.MODE_MULTI_PROCESS);
SharedPreferences.Editor editor = settings.edit();

editor.putString(key, Base64.encodeBytes(value));
editor.commit();
}

private static void saveString(Context context, String key, String value) {
SharedPreferences settings = context.getSharedPreferences(HACK_PREFERENCES_NAME, Context.MODE_MULTI_PROCESS);
SharedPreferences.Editor editor = settings.edit();

editor.putString(key, value);
editor.commit();
}

private static Optional<byte[]> retrieveBytes(Context context, String key) throws IOException {
SharedPreferences settings = context.getSharedPreferences(HACK_PREFERENCES_NAME, Context.MODE_MULTI_PROCESS);
String encodedValue = settings.getString(key, null);

if (encodedValue == null)
return Optional.absent();

return Optional.of(Base64.decode(encodedValue));
}

private byte[] encryptAndEncode(byte[] iv, SecretKey cipherKey, SecretKey macKey, byte[] data)
throws IOException, GeneralSecurityException
{
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
Mac hmac = Mac.getInstance("HmacSHA256");

encryptingCipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);
hmac.init(macKey);

byte[] ciphertext = encryptingCipher.doFinal(data);
byte[] mac = hmac.doFinal(Util.combine(new byte[]{MasterCipher.CURRENT_CIPHER_VERSION}, iv, ciphertext));

return Base64.encodeBytesToBytes(Util.combine(new byte[]{MasterCipher.CURRENT_CIPHER_VERSION}, iv, ciphertext, mac));
}

public void testKeyHelperGenerateKeyMaterial() throws Exception {
KeyHelper.generateAndSaveSaltAndKeyMaterial(context);

Optional<byte[]> resultCipherKeyBytes = retrieveBytes(context, "KEY_CIPHER_KEY");
Optional<byte[]> resultMacKeyBytes = retrieveBytes(context, "KEY_MAC_KEY");
Optional<byte[]> resultSaltBytes = retrieveBytes(context, "KEY_KEY_MATERIAL_SALT");

assertTrue("KeyHelper can generate key material.",
resultCipherKeyBytes.get().length > 0 &&
resultMacKeyBytes.get().length > 0 &&
resultSaltBytes.get().length > 0 &&
!Arrays.equals(resultCipherKeyBytes.get(), resultMacKeyBytes.get()) &&
!Arrays.equals(resultCipherKeyBytes.get(), resultSaltBytes.get()) &&
!Arrays.equals(resultMacKeyBytes.get(), resultSaltBytes.get()));
}

public void testKeyHelperMasterCipher() throws Exception {
final byte[] cipherKeyBytes = 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};
final byte[] macKeyBytes = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
final byte[] plaintext = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};

final SecretKey testCipherKey = new SecretKeySpec(cipherKeyBytes, "AES");
final SecretKey testMacKey = new SecretKeySpec(macKeyBytes, "SHA256");

saveBytes(context, "KEY_CIPHER_KEY", cipherKeyBytes);
saveBytes(context, "KEY_MAC_KEY", macKeyBytes);

byte[] encodedKeyHelperResult = KeyHelper.getMasterCipher(context).get().encryptAndEncode(plaintext);
byte[] keyHelperResult = Base64.decode(encodedKeyHelperResult);
byte[] keyHelperIv = Arrays.copyOfRange(keyHelperResult, 1, 1 + 16);

byte[] encodedTestResult = encryptAndEncode(keyHelperIv, testCipherKey, testMacKey, plaintext);
byte[] testResult = Base64.decode(encodedTestResult);

assertTrue("KeyHelper's MasterCipher works.",
new String(testResult).equals(new String(keyHelperResult)));
}

public void testBuildAndImportEncryptedKeyMaterial() throws Exception {
final String masterPassphrase = "oioioi";
final byte[] cipherKeyBytes = 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};
final byte[] macKeyBytes = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
final byte[] saltBytes = new byte[] {2, 2, 2, 2, 2, 2, 2, 2};

saveBytes(context, "KEY_CIPHER_KEY", cipherKeyBytes);
saveBytes(context, "KEY_MAC_KEY", macKeyBytes);
saveBytes(context, "KEY_KEY_MATERIAL_SALT", saltBytes);
saveString(context, "KEY_OLD_MASTER_PASSPHRASE", masterPassphrase);

Optional<String> encodedSalt = KeyHelper.buildEncodedSalt(context);
Optional<String> encryptedKeyMaterial = KeyHelper.buildEncryptedKeyMaterial(context);
String[] saltAndEncryptedKeyMaterial = new String[] {
encodedSalt.get(),
encryptedKeyMaterial.get()
};

KeyStore.invalidateKeyMaterial(context);
saveString(context, "KEY_OLD_MASTER_PASSPHRASE", masterPassphrase);

KeyHelper.importSaltAndEncryptedKeyMaterial(context, saltAndEncryptedKeyMaterial);

Optional<byte[]> resultCipherKeyBytes = retrieveBytes(context, "KEY_CIPHER_KEY");
Optional<byte[]> resultMacKeyBytes = retrieveBytes(context, "KEY_MAC_KEY");
Optional<byte[]> resultSaltBytes = retrieveBytes(context, "KEY_KEY_MATERIAL_SALT");

assertTrue("KeyHelper can export and import encrypted key material.",
Arrays.equals(resultCipherKeyBytes.get(), cipherKeyBytes) &&
Arrays.equals(resultMacKeyBytes.get(), macKeyBytes) &&
Arrays.equals(resultSaltBytes.get(), saltBytes));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class MockMasterCipher extends MasterCipher {

public MockMasterCipher() {
super(null, null);
super(false, null, null);
}

@Override
Expand All @@ -28,8 +28,8 @@ public String encryptAndEncode(String data) {
}

@Override
public byte[] decodeAndDecrypt(byte[] encodedIvCiphertextAndMac) {
return Base64.decode(encodedIvCiphertextAndMac);
public byte[] decodeAndDecrypt(byte[] encodedVersionIvCiphertextAndMac) {
return Base64.decode(encodedVersionIvCiphertextAndMac);
}

public String decodeAndDecrypt(String data) throws IOException {
Expand Down
19 changes: 17 additions & 2 deletions flock/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.anhonesteffort.flock"
android:versionCode="7"
android:versionName="0.7.7" >
android:versionCode="8"
android:versionName="0.8" >

<uses-sdk
android:minSdkVersion="16"
Expand Down Expand Up @@ -131,6 +131,8 @@

<activity android:name="org.anhonesteffort.flock.SendBitcoinActivity" />

<activity android:name="org.anhonesteffort.flock.MigrationReleaseNotesActivity" />

<service android:name="org.anhonesteffort.flock.auth.AccountAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
Expand Down Expand Up @@ -197,12 +199,25 @@
<service android:name="org.anhonesteffort.flock.ChangeEncryptionPasswordService"
android:exported="false"/>

<service android:name="org.anhonesteffort.flock.MigrationService"
android:exported="false"/>

<receiver android:name="org.anhonesteffort.flock.sync.SyncBooter">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>

<receiver android:name="org.anhonesteffort.flock.MigrationHelperBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="org.anhonesteffort.flock.MigrationService.ACTION_MIGRATION_STARTED"/>
<action android:name="org.anhonesteffort.flock.MigrationService.ACTION_MIGRATION_COMPLETE"/>
<action android:name="org.anhonesteffort.flock.sync.key.ACTION_KEY_MATERIAL_IMPORTED"/>
<action android:name="org.anhonesteffort.flock.sync.addressbook.AddressbookSyncWorker.ACTION_PUSH_CREATED_CONTACTS"/>
</intent-filter>
</receiver>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public View onCreateView(LayoutInflater inflater,
activity = getActivity();
View fragmentView = inflater.inflate(R.layout.fragment_list_sync_collections, container, false);

if (accountAndKeyAvailable())
if (accountAndKeyAvailableAndMigrationComplete())
initButtons();

return fragmentView;
Expand All @@ -105,6 +105,9 @@ public void onClick(View view) {
public void onResume() {
super.onResume();

if (!accountAndKeyAvailableAndMigrationComplete())
return;

activity = getActivity();
initializeList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,16 @@ protected void onCreate(Bundle savedInstanceState) {
masterCipher = handleGetMasterCipherOrFail(this);
}

protected boolean accountAndKeyAvailable() {
protected boolean accountAndKeyAvailableAndMigrationComplete() {
if (MigrationHelperBroadcastReceiver.getUiDisabledForMigration(getBaseContext())) {
Toast.makeText(getBaseContext(),
R.string.migration_in_progress_please_wait,
Toast.LENGTH_LONG).show();

finish();
return false;
}

return account != null && masterCipher != null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.widget.Toast;

import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.crypto.MasterCipher;
Expand All @@ -41,7 +42,16 @@ public void onAttach(Activity activity) {
masterCipher = AccountAndKeyRequiredActivity.handleGetMasterCipherOrFail(getActivity());
}

protected boolean accountAndKeyAvailable() {
protected boolean accountAndKeyAvailableAndMigrationComplete() {
if (MigrationHelperBroadcastReceiver.getUiDisabledForMigration(getActivity())) {
Toast.makeText(getActivity(),
R.string.migration_in_progress_please_wait,
Toast.LENGTH_LONG).show();

getActivity().finish();
return false;
}

return account != null && masterCipher != null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.MenuItem;
Expand Down Expand Up @@ -55,7 +54,7 @@ public class ChangeEncryptionPasswordActivity extends AccountAndKeyRequiredActiv
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (!accountAndKeyAvailable())
if (!accountAndKeyAvailableAndMigrationComplete())
return;

requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Expand Down
13 changes: 11 additions & 2 deletions flock/src/main/java/org/anhonesteffort/flock/DavAccountHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.anhonesteffort.flock;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.SharedPreferences;
Expand Down Expand Up @@ -92,8 +93,8 @@ public static Optional<String> getAccountDavHREF(Context context) {
return Optional.fromNullable(getSharedPreferences(context).getString(KEY_DAV_HOST, null));
}

public static void setAccountDavHREF(Context context, String username) {
getSharedPreferences(context).edit().putString(KEY_DAV_HOST, username).commit();
public static void setAccountDavHREF(Context context, String href) {
getSharedPreferences(context).edit().putString(KEY_DAV_HOST, href).commit();
}

public static void invalidateAccount(Context context) {
Expand All @@ -111,6 +112,14 @@ public static boolean isUsingOurServers(Context context) {
getAccountDavHREF(context).get().equals(OwsWebDav.HREF_WEBDAV_HOST);
}

public static Optional<Account> getOsAccount(Context context) {
Optional<String> accountUsername = getAccountUsername(context);
if (!accountUsername.isPresent())
return Optional.absent();

return Optional.of(new Account(accountUsername.get(), DavAccount.SYNC_ACCOUNT_TYPE));
}

public static Optional<DavAccount> getAccount(Context context) {
Optional<String> davHREF = getAccountDavHREF(context);
Optional<String> accountUsername = getAccountUsername(context);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/

package org.anhonesteffort.flock;

import android.app.AlertDialog;
Expand Down Expand Up @@ -34,7 +53,7 @@ public class DeleteAllContactsActivity extends AccountAndKeyRequiredActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (!accountAndKeyAvailable())
if (!accountAndKeyAvailableAndMigrationComplete())
return;

requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Expand Down
Loading

0 comments on commit e3c61e1

Please sign in to comment.