diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 8d76101ae..fa0286a90 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -454,6 +454,14 @@
+
+
+
+
+
+
+
+
{
}
CountingOutputStream outStream = new CountingOutputStream(new BufferedOutputStream(plainOut));
- boolean backupSuccess = exportKeysToStream(
- log, backupInput.getMasterKeyIds(), backupInput.getExportSecret(), outStream);
+ boolean backupSuccess = exportKeysToStream(log, backupInput.getMasterKeyIds(),
+ backupInput.getExportSecret(), backupInput.getExportPublic(), outStream);
if (!backupSuccess) {
// if there was an error, it will be in the log so we just have to return
@@ -169,9 +171,15 @@ public class BackupOperation extends BaseOperation {
PgpSignEncryptOperation signEncryptOperation = new PgpSignEncryptOperation(mContext, mKeyRepository, mProgressable, mCancelled);
PgpSignEncryptData.Builder builder = PgpSignEncryptData.builder();
- builder.setSymmetricPassphrase(cryptoInput.getPassphrase());
+ Passphrase passphrase = cryptoInput.getPassphrase();
+ builder.setSymmetricPassphrase(passphrase);
builder.setEnableAsciiArmorOutput(backupInput.getEnableAsciiArmorOutput());
- builder.setAddBackupHeader(true);
+ boolean isNumeric9x4Passphrase = passphrase != null && Numeric9x4PassphraseUtil.isNumeric9x4Passphrase(passphrase);
+ if (isNumeric9x4Passphrase) {
+ builder.setPassphraseFormat("numeric9x4");
+ char[] passphraseChars = passphrase.getCharArray();
+ builder.setPassphraseBegin("" + passphraseChars[0] + passphraseChars[1]);
+ }
PgpSignEncryptData pgpSignEncryptData = builder.build();
InputStream inStream = mContext.getContentResolver().openInputStream(plainUri);
@@ -206,7 +214,8 @@ public class BackupOperation extends BaseOperation {
pgpSignEncryptData, CryptoInputParcel.createCryptoInputParcel(), inputData, outStream);
}
- boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) {
+ boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, boolean exportPublic,
+ OutputStream outStream) {
// noinspection unused TODO use these in a log entry
int okSecret = 0, okPublic = 0;
@@ -232,9 +241,15 @@ public class BackupOperation extends BaseOperation {
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
- if (writePublicKeyToStream(masterKeyId, log, outStream)) {
- okPublic += 1;
+ boolean publicKeyWriteOk = false;
+ if (exportPublic) {
+ publicKeyWriteOk = writePublicKeyToStream(masterKeyId, log, outStream);
+ if (publicKeyWriteOk) {
+ okPublic += 1;
+ }
+ }
+ if (publicKeyWriteOk || !exportPublic) {
boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0;
if (exportSecret && hasSecret) {
log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(masterKeyId));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index 6982a10e4..966ae84d4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -627,6 +627,8 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym),
MSG_DC_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset),
MSG_DC_BACKUP_VERSION (LogLevel.DEBUG, R.string.msg_dc_backup_version),
+ MSG_DC_PASSPHRASE_FORMAT (LogLevel.DEBUG, R.string.msg_dc_passphrase_format),
+ MSG_DC_PASSPHRASE_BEGIN (LogLevel.DEBUG, R.string.msg_dc_passphrase_begin),
MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data),
MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress),
MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file),
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
index 6da29c9b8..d21f7b1c4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -41,6 +41,7 @@ public abstract class PgpDecryptVerifyInputParcel implements Parcelable {
abstract boolean isAllowSymmetricDecryption();
abstract boolean isDecryptMetadataOnly();
+ abstract boolean isAutocryptSetup();
@Nullable
abstract List getAllowedKeyIds();
@@ -55,7 +56,8 @@ public abstract class PgpDecryptVerifyInputParcel implements Parcelable {
public static Builder builder() {
return new AutoValue_PgpDecryptVerifyInputParcel.Builder()
.setAllowSymmetricDecryption(false)
- .setDecryptMetadataOnly(false);
+ .setDecryptMetadataOnly(false)
+ .setAutocryptSetup(false);
}
@AutoValue.Builder
@@ -68,6 +70,7 @@ public abstract class PgpDecryptVerifyInputParcel implements Parcelable {
public abstract Builder setDecryptMetadataOnly(boolean decryptMetadataOnly);
public abstract Builder setDetachedSignature(byte[] detachedSignature);
public abstract Builder setSenderAddress(String senderAddress);
+ public abstract Builder setAutocryptSetup(boolean isAutocryptSetup);
public abstract Builder setAllowedKeyIds(List allowedKeyIds);
abstract List getAllowedKeyIds();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index ec97b2213..6451019a5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -93,6 +93,7 @@ import static java.lang.String.format;
public class PgpDecryptVerifyOperation extends BaseOperation {
public static final int PROGRESS_STRIDE_MILLISECONDS = 200;
+ public static final String PASSPHRASE_FORMAT_NUMERIC9X4 = "numeric9x4";
public PgpDecryptVerifyOperation(Context context, KeyRepository keyRepository, Progressable progressable) {
super(context, keyRepository, progressable);
@@ -230,6 +231,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation(allowedSigningKeyIds)));
return this;
}
+
+ public abstract Builder setPassphraseFormat(String passphraseFormat);
+ public abstract Builder setPassphraseBegin(String passphraseBegin);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 96c614dd6..bf24bebfb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -202,9 +202,13 @@ public class PgpSignEncryptOperation extends BaseOperation inputUris, boolean canDelete) {
+ public void displayListFragment(ArrayList inputUris, boolean canDelete, boolean isAutocryptSetup) {
- DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete);
+ DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete, isAutocryptSetup);
FragmentManager fragMan = getSupportFragmentManager();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index 17812a13a..b1aaecc20 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -125,6 +125,7 @@ public class DecryptListFragment
public static final String ARG_CANCELLED_URIS = "cancelled_uris";
public static final String ARG_RESULTS = "results";
public static final String ARG_CAN_DELETE = "can_delete";
+ public static final String ARG_IS_AUTOCRYPT_SETUP = "is_autocrypt_setup";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
@@ -136,6 +137,7 @@ public class DecryptListFragment
private Uri mCurrentInputUri;
private boolean mCanDelete;
+ private boolean mIsAutocryptSetup;
private DecryptFilesAdapter mAdapter;
private Uri mCurrentSaveFileUri;
@@ -143,12 +145,13 @@ public class DecryptListFragment
/**
* Creates new instance of this fragment
*/
- public static DecryptListFragment newInstance(@NonNull ArrayList uris, boolean canDelete) {
+ public static DecryptListFragment newInstance(@NonNull ArrayList uris, boolean canDelete, boolean isAutocryptSetup) {
DecryptListFragment frag = new DecryptListFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(ARG_INPUT_URIS, uris);
args.putBoolean(ARG_CAN_DELETE, canDelete);
+ args.putBoolean(ARG_IS_AUTOCRYPT_SETUP, isAutocryptSetup);
frag.setArguments(args);
return frag;
@@ -205,6 +208,7 @@ public class DecryptListFragment
outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults));
outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
outState.putBoolean(ARG_CAN_DELETE, mCanDelete);
+ outState.putBoolean(ARG_IS_AUTOCRYPT_SETUP, mIsAutocryptSetup);
// this does not save mCurrentInputUri - if anything is being
// processed at fragment recreation time, the operation in
@@ -222,6 +226,7 @@ public class DecryptListFragment
ParcelableHashMap results = args.getParcelable(ARG_RESULTS);
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
+ mIsAutocryptSetup = args.getBoolean(ARG_IS_AUTOCRYPT_SETUP, false);
displayInputUris(inputUris, cancelledUris,
results != null ? results.getMap() : null
@@ -638,11 +643,14 @@ public class DecryptListFragment
}
PgpDecryptVerifyInputParcel.Builder decryptInput = PgpDecryptVerifyInputParcel.builder()
- .setAllowSymmetricDecryption(true);
+ .setAllowSymmetricDecryption(true)
+ .setAutocryptSetup(mIsAutocryptSetup);
return InputDataParcel.createInputDataParcel(mCurrentInputUri, decryptInput.build());
}
+
+
/**
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
*
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
index fb7147dea..bd0f84764 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -31,6 +31,7 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
+import android.text.InputFilter;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
@@ -68,6 +69,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.Require
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.ui.widget.CacheTTLSpinner;
+import org.sufficientlysecure.keychain.ui.widget.PrefixedEditText;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@@ -193,7 +195,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
mBackupCodeEditText[4] = view.findViewById(R.id.backup_code_5);
mBackupCodeEditText[5] = view.findViewById(R.id.backup_code_6);
- setupEditTextFocusNext(mBackupCodeEditText);
+ setupEditTextFocusNext(mBackupCodeEditText, false);
AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
@@ -201,6 +203,49 @@ public class PassphraseDialogActivity extends FragmentActivity {
return dialog;
}
+ if (mRequiredInput.mType == RequiredInputType.NUMERIC_9X4 ||
+ mRequiredInput.mType == RequiredInputType.NUMERIC_9X4_AUTOCRYPT) {
+ LayoutInflater inflater = LayoutInflater.from(theme);
+ View view = inflater.inflate(R.layout.passphrase_dialog_numeric_9x4, null);
+ alert.setView(view);
+
+ mBackupCodeEditText = new EditText[9];
+ mBackupCodeEditText[0] = view.findViewById(R.id.transfer_code_block_1);
+ mBackupCodeEditText[1] = view.findViewById(R.id.transfer_code_block_2);
+ mBackupCodeEditText[2] = view.findViewById(R.id.transfer_code_block_3);
+ mBackupCodeEditText[3] = view.findViewById(R.id.transfer_code_block_4);
+ mBackupCodeEditText[4] = view.findViewById(R.id.transfer_code_block_5);
+ mBackupCodeEditText[5] = view.findViewById(R.id.transfer_code_block_6);
+ mBackupCodeEditText[6] = view.findViewById(R.id.transfer_code_block_7);
+ mBackupCodeEditText[7] = view.findViewById(R.id.transfer_code_block_8);
+ mBackupCodeEditText[8] = view.findViewById(R.id.transfer_code_block_9);
+
+ if (mRequiredInput.hasPassphraseBegin()) {
+ String beginChars = mRequiredInput.getPassphraseBegin();
+ int inputLength = 4 - beginChars.length();
+ setupEditTextFocusNext(mBackupCodeEditText, true);
+
+ PrefixedEditText prefixEditText = (PrefixedEditText) mBackupCodeEditText[0];
+ if (beginChars.matches("\\d\\d")) {
+ prefixEditText.setPrefix(beginChars);
+ prefixEditText.setHint("1234".substring(inputLength));
+ prefixEditText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(inputLength) });
+ }
+ } else {
+ setupEditTextFocusNext(mBackupCodeEditText, false);
+ }
+
+ if (mRequiredInput.mType == RequiredInputType.NUMERIC_9X4_AUTOCRYPT) {
+ TextView promptText = view.findViewById(R.id.passphrase_text);
+ promptText.setText(R.string.passphrase_transfer_autocrypt);
+ }
+
+ AlertDialog dialog = alert.create();
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ activity.getString(R.string.btn_proceed), (DialogInterface.OnClickListener) null);
+ return dialog;
+ }
+
LayoutInflater inflater = LayoutInflater.from(theme);
mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null);
alert.setView(mLayout);
@@ -377,10 +422,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
textView.requestFocus();
}
- private static void setupEditTextFocusNext(final EditText[] backupCodes) {
+ private static void setupEditTextFocusNext(EditText[] backupCodes, boolean hasPrefix) {
for (int i = 0; i < backupCodes.length - 1; i++) {
-
- final int next = i + 1;
+ int idx = i;
backupCodes[i].addTextChangedListener(new TextWatcher() {
@Override
@@ -390,10 +434,11 @@ public class PassphraseDialogActivity extends FragmentActivity {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean inserting = before < count;
- boolean cursorAtEnd = (start + count) == 4;
+ int maxLen = hasPrefix && idx == 0 ? 2 : 4;
+ boolean cursorAtEnd = (start + count) == maxLen;
if (inserting && cursorAtEnd) {
- backupCodes[next].requestFocus();
+ backupCodes[idx + 1].requestFocus();
}
}
@@ -401,7 +446,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
public void afterTextChanged(Editable s) {
}
});
-
}
}
@@ -432,6 +476,27 @@ public class PassphraseDialogActivity extends FragmentActivity {
return;
}
+ if (mRequiredInput.mType == RequiredInputType.NUMERIC_9X4 ||
+ mRequiredInput.mType == RequiredInputType.NUMERIC_9X4_AUTOCRYPT) {
+ StringBuilder backupCodeInput = new StringBuilder(36);
+ if (mRequiredInput.hasPassphraseBegin()) {
+ backupCodeInput.append(mRequiredInput.getPassphraseBegin());
+ }
+ for (EditText editText : mBackupCodeEditText) {
+ if (editText.getText().length() != 2 && editText.getText().length() != 4) {
+ return;
+ }
+ backupCodeInput.append(editText.getText());
+ backupCodeInput.append('-');
+ }
+ backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
+
+ Passphrase passphrase = new Passphrase(backupCodeInput.toString());
+ finishCaching(passphrase, null);
+
+ return;
+ }
+
final Passphrase passphrase = new Passphrase(mPassphraseEditText);
final int timeToLiveSeconds = mTimeToLiveSpinner.getSelectedTimeToLive();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
index 248352453..06dc54345 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -162,7 +162,9 @@ public class CryptoOperationHelper.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+
+import java.nio.CharBuffer;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.regex.Pattern;
+
+import android.support.annotation.VisibleForTesting;
+
+// see https://autocrypt.org/level1.html#setup-code
+public class Numeric9x4PassphraseUtil {
+ public static Passphrase generateNumeric9x4Passphrase() {
+ return generateNumeric9x4Passphrase(new SecureRandom());
+ }
+
+ @VisibleForTesting
+ static Passphrase generateNumeric9x4Passphrase(Random r) {
+ StringBuilder code = new StringBuilder(36);
+ for (int i = 0; i < 36; i++) {
+ boolean isBeginningOfBlock = i > 0 && (i % 4) == 0;
+ if (isBeginningOfBlock) {
+ code.append('-');
+ }
+
+ String digit = Integer.toString(r.nextInt(10));
+ code.append(digit);
+ }
+
+ return new Passphrase(code.toString());
+ }
+
+ private static final Pattern AUTOCRYPT_TRANSFER_CODE = Pattern.compile("(\\d{4}-){8}\\d{4}");
+
+ public static boolean isNumeric9x4Passphrase(Passphrase transferCodeChars) {
+ return isNumeric9x4Passphrase(CharBuffer.wrap(transferCodeChars.getCharArray()));
+ }
+
+ public static boolean isNumeric9x4Passphrase(CharSequence code) {
+ return AUTOCRYPT_TRANSFER_CODE.matcher(code).matches();
+ }
+}
diff --git a/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml
deleted file mode 100644
index dfeed3f2d..000000000
--- a/OpenKeychain/src/main/res/layout-w400dp/backup_code_fragment.xml
+++ /dev/null
@@ -1,459 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OpenKeychain/src/main/res/layout-w600dp/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout-w600dp/backup_code_fragment.xml
deleted file mode 100644
index d8033a9ea..000000000
--- a/OpenKeychain/src/main/res/layout-w600dp/backup_code_fragment.xml
+++ /dev/null
@@ -1,459 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
index a85f0273f..3a4456b51 100644
--- a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml
@@ -56,330 +56,9 @@
android:outAnimation="@anim/fade_out"
custom:initialView="1">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/OpenKeychain/src/main/res/layout/passphrase_dialog_numeric_9x4.xml b/OpenKeychain/src/main/res/layout/passphrase_dialog_numeric_9x4.xml
new file mode 100644
index 000000000..b6764e912
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/passphrase_dialog_numeric_9x4.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/transfer_code_display.xml b/OpenKeychain/src/main/res/layout/transfer_code_display.xml
new file mode 100644
index 000000000..652c82491
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/transfer_code_display.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/transfer_code_input.xml b/OpenKeychain/src/main/res/layout/transfer_code_input.xml
new file mode 100644
index 000000000..8b74a388f
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/transfer_code_input.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/attr.xml b/OpenKeychain/src/main/res/values/attr.xml
index 13ff2afe8..f763774e0 100644
--- a/OpenKeychain/src/main/res/values/attr.xml
+++ b/OpenKeychain/src/main/res/values/attr.xml
@@ -13,6 +13,7 @@
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 1f2946c36..045b3f572 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -102,6 +102,7 @@
"Encrypt text"
"Add additional email address"
"Unlock"
+ "Proceed"
"Add"
"Save as default"
"Saved!"
@@ -333,6 +334,8 @@
"Enter password"
"Switch to alphabetic keyboard"
"Switch to numeric keyboard"
+ "Enter transfer code"
+ "To import this Autocrypt Setup Message, enter transfer code:"
"Enter PIN for '%s'"
"Enter PIN to access Security Token for '%s'"
"Hold Security Token against the NFC marker at the back of your device."
@@ -633,6 +636,7 @@
"Import Key with OpenKeychain"
"Encrypt with OpenKeychain"
"Decrypt with OpenKeychain"
+ "Import Autocrypt Setup Message"
"Show advanced information"
@@ -1176,6 +1180,8 @@
"Found block of asymmetrically encrypted data for key %s"
"Found charset header: '%s'"
"Found backupVersion header: '%s'"
+ "Found Passphrase-Format header: '%s'"
+ "Found Passphrase-Begin header: '%s'"
"Processing literal data"
"Unpacking compressed data"
"Filename: %s"
diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml
index 39b8cd8d7..b70093b7a 100644
--- a/OpenKeychain/src/main/res/values/styles.xml
+++ b/OpenKeychain/src/main/res/values/styles.xml
@@ -65,4 +65,23 @@
- 14dp
+
+
+
+
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java
index eb8470fec..190072597 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java
@@ -157,7 +157,7 @@ public class BackupOperationTest {
assertTrue("second keyring has local certification", checkForLocal(mStaticRing2));
ByteArrayOutputStream out = new ByteArrayOutputStream();
- boolean result = op.exportKeysToStream(new OperationLog(), null, false, out);
+ boolean result = op.exportKeysToStream(new OperationLog(), null, false, true, out);
assertTrue("export must be a success", result);
@@ -194,7 +194,7 @@ public class BackupOperationTest {
}
out = new ByteArrayOutputStream();
- result = op.exportKeysToStream(new OperationLog(), null, true, out);
+ result = op.exportKeysToStream(new OperationLog(), null, true, true, out);
assertTrue("export must be a success", result);
@@ -252,7 +252,7 @@ public class BackupOperationTest {
BackupOperation op = new BackupOperation(spyApplication,
KeyWritableRepository.create(RuntimeEnvironment.application), null);
- BackupKeyringParcel parcel = BackupKeyringParcel.createBackupKeyringParcel(
+ BackupKeyringParcel parcel = BackupKeyringParcel.create(
new long[] { mStaticRing1.getMasterKeyId() }, false, false, true, fakeOutputUri);
ExportResult result = op.execute(parcel, null);
@@ -309,7 +309,7 @@ public class BackupOperationTest {
BackupOperation op = new BackupOperation(spyApplication,
KeyWritableRepository.create(RuntimeEnvironment.application), null);
- BackupKeyringParcel parcel = BackupKeyringParcel.createBackupKeyringParcel(
+ BackupKeyringParcel parcel = BackupKeyringParcel.create(
new long[] { mStaticRing1.getMasterKeyId() }, false, true, true, fakeOutputUri);
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(passphrase);
ExportResult result = op.execute(parcel, inputParcel);
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/Numeric9x4PassphraseUtilTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/Numeric9x4PassphraseUtilTest.java
new file mode 100644
index 000000000..49c6edcfd
--- /dev/null
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/Numeric9x4PassphraseUtilTest.java
@@ -0,0 +1,40 @@
+package org.sufficientlysecure.keychain.util;
+
+
+import java.util.Random;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+public class Numeric9x4PassphraseUtilTest {
+ private static final int RANDOM_SEED = 12345;
+ private static final String TRANSFER_CODE_3x3x4 = "1018-5452-1972-0624-7325-1126-8153-1997-1535";
+
+ @Test
+ public void generateNumeric9x4Passphrase() {
+ Random r = new Random(RANDOM_SEED);
+
+ Passphrase passphrase = Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase(r);
+
+ assertEquals(TRANSFER_CODE_3x3x4, passphrase.toStringUnsafe());
+ }
+
+ @Test
+ public void isNumeric9x4Passphrase() {
+ boolean isValidCodeLayout = Numeric9x4PassphraseUtil.isNumeric9x4Passphrase(TRANSFER_CODE_3x3x4);
+
+ assertTrue(isValidCodeLayout);
+ }
+
+
+ @Test
+ public void isNumeric9x4Passphrase_withBadSuffix() {
+ boolean isValidCodeLayout = Numeric9x4PassphraseUtil.isNumeric9x4Passphrase(TRANSFER_CODE_3x3x4 + "x");
+
+ assertFalse(isValidCodeLayout);
+ }
+}
\ No newline at end of file