Merge pull request #2304 from open-keychain/autocrypt-setup-message
Autocrypt setup message format support
This commit is contained in:
@@ -58,8 +58,10 @@ import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
|
||||
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
|
||||
import org.sufficientlysecure.keychain.util.CountingOutputStream;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -127,8 +129,8 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
|
||||
}
|
||||
|
||||
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<BackupKeyringParcel> {
|
||||
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<BackupKeyringParcel> {
|
||||
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<BackupKeyringParcel> {
|
||||
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));
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -41,6 +41,7 @@ public abstract class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||
|
||||
abstract boolean isAllowSymmetricDecryption();
|
||||
abstract boolean isDecryptMetadataOnly();
|
||||
abstract boolean isAutocryptSetup();
|
||||
|
||||
@Nullable
|
||||
abstract List<Long> 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<Long> allowedKeyIds);
|
||||
abstract List<Long> getAllowedKeyIds();
|
||||
|
||||
@@ -93,6 +93,7 @@ import static java.lang.String.format;
|
||||
public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
|
||||
|
||||
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<PgpDecryptVerifyInp
|
||||
private static class ArmorHeaders {
|
||||
String charset = null;
|
||||
Integer backupVersion = null;
|
||||
String passphraseFormat;
|
||||
String passphraseBegin;
|
||||
}
|
||||
|
||||
private ArmorHeaders parseArmorHeaders(InputStream in, OperationLog log, int indent) {
|
||||
@@ -261,6 +264,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "passphrase-format": {
|
||||
armorHeaders.passphraseFormat = pieces[1].trim();
|
||||
break;
|
||||
}
|
||||
case "passphrase-begin": {
|
||||
armorHeaders.passphraseBegin = pieces[1].trim();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// continue;
|
||||
}
|
||||
@@ -272,6 +283,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
if (armorHeaders.backupVersion != null) {
|
||||
log.add(LogType.MSG_DC_BACKUP_VERSION, indent, Integer.toString(armorHeaders.backupVersion));
|
||||
}
|
||||
if (armorHeaders.passphraseFormat != null) {
|
||||
log.add(LogType.MSG_DC_PASSPHRASE_FORMAT, indent, armorHeaders.passphraseFormat);
|
||||
}
|
||||
if (armorHeaders.passphraseBegin != null) {
|
||||
log.add(LogType.MSG_DC_PASSPHRASE_BEGIN, indent, armorHeaders.passphraseBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,9 +311,16 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
// parse ASCII Armor headers
|
||||
ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent);
|
||||
String charset = armorHeaders.charset;
|
||||
boolean useBackupCode = false;
|
||||
RequiredInputParcel customRequiredInputParcel = null;
|
||||
if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 2) {
|
||||
useBackupCode = true;
|
||||
customRequiredInputParcel = RequiredInputParcel.createRequiredBackupCode();
|
||||
} else if (PASSPHRASE_FORMAT_NUMERIC9X4.equalsIgnoreCase(armorHeaders.passphraseFormat)) {
|
||||
if (input.isAutocryptSetup()) {
|
||||
customRequiredInputParcel =
|
||||
RequiredInputParcel.createRequiredNumeric9x4Autocrypt(armorHeaders.passphraseBegin);
|
||||
} else {
|
||||
customRequiredInputParcel = RequiredInputParcel.createRequiredNumeric9x4(armorHeaders.passphraseBegin);
|
||||
}
|
||||
}
|
||||
|
||||
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
|
||||
@@ -311,7 +335,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
if (obj instanceof PGPEncryptedDataList) {
|
||||
esResult = handleEncryptedPacket(
|
||||
input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, useBackupCode);
|
||||
input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, customRequiredInputParcel);
|
||||
|
||||
// if there is an error, nothing left to do here
|
||||
if (esResult.errorResult != null) {
|
||||
@@ -565,7 +589,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
|
||||
private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||
PGPEncryptedDataList enc, OperationLog log, int indent, boolean useBackupCode) throws PGPException {
|
||||
PGPEncryptedDataList enc, OperationLog log, int indent, RequiredInputParcel customRequiredInputParcel)
|
||||
throws PGPException {
|
||||
|
||||
EncryptStreamResult result = new EncryptStreamResult();
|
||||
|
||||
@@ -723,9 +748,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
if (passphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
RequiredInputParcel requiredInputParcel = useBackupCode ?
|
||||
RequiredInputParcel.createRequiredBackupCode() :
|
||||
RequiredInputParcel.createRequiredSymmetricPassphrase();
|
||||
RequiredInputParcel requiredInputParcel = customRequiredInputParcel != null ?
|
||||
customRequiredInputParcel : RequiredInputParcel.createRequiredSymmetricPassphrase();
|
||||
return result.with(new DecryptVerifyResult(log,
|
||||
requiredInputParcel,
|
||||
cryptoInput));
|
||||
@@ -771,9 +795,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||
} catch (PGPDataValidationException e) {
|
||||
log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1);
|
||||
RequiredInputParcel requiredInputParcel = useBackupCode ?
|
||||
RequiredInputParcel.createRequiredBackupCode() :
|
||||
RequiredInputParcel.createRequiredSymmetricPassphrase();
|
||||
RequiredInputParcel requiredInputParcel = customRequiredInputParcel != null ?
|
||||
customRequiredInputParcel : RequiredInputParcel.createRequiredSymmetricPassphrase();
|
||||
return result.with(new DecryptVerifyResult(log, requiredInputParcel, cryptoInput));
|
||||
}
|
||||
|
||||
|
||||
@@ -58,9 +58,13 @@ public abstract class PgpSignEncryptData implements Parcelable {
|
||||
public abstract boolean isEnableAsciiArmorOutput();
|
||||
public abstract boolean isCleartextSignature();
|
||||
public abstract boolean isDetachedSignature();
|
||||
public abstract boolean isAddBackupHeader();
|
||||
public abstract boolean isHiddenRecipients();
|
||||
|
||||
@Nullable
|
||||
public abstract String getPassphraseFormat();
|
||||
@Nullable
|
||||
public abstract String getPassphraseBegin();
|
||||
|
||||
public static Builder builder() {
|
||||
return new AutoValue_PgpSignEncryptData.Builder()
|
||||
.setSignatureMasterKeyId(Constants.key.none)
|
||||
@@ -68,7 +72,6 @@ public abstract class PgpSignEncryptData implements Parcelable {
|
||||
.setEnableAsciiArmorOutput(false)
|
||||
.setCleartextSignature(false)
|
||||
.setDetachedSignature(false)
|
||||
.setAddBackupHeader(false)
|
||||
.setHiddenRecipients(false)
|
||||
.setCompressionAlgorithm(OpenKeychainCompressionAlgorithmTags.USE_DEFAULT)
|
||||
.setSignatureHashAlgorithm(OpenKeychainHashAlgorithmTags.USE_DEFAULT)
|
||||
@@ -91,7 +94,6 @@ public abstract class PgpSignEncryptData implements Parcelable {
|
||||
public abstract Builder setSignatureHashAlgorithm(int signatureHashAlgorithm);
|
||||
public abstract Builder setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm);
|
||||
|
||||
public abstract Builder setAddBackupHeader(boolean isAddBackupHeader);
|
||||
public abstract Builder setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput);
|
||||
public abstract Builder setCleartextSignature(boolean isCleartextSignature);
|
||||
public abstract Builder setDetachedSignature(boolean isDetachedSignature);
|
||||
@@ -102,6 +104,9 @@ public abstract class PgpSignEncryptData implements Parcelable {
|
||||
setAllowedSigningKeyIds(Collections.unmodifiableList(new ArrayList<>(allowedSigningKeyIds)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract Builder setPassphraseFormat(String passphraseFormat);
|
||||
public abstract Builder setPassphraseBegin(String passphraseBegin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,9 +202,13 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
|
||||
if (data.getCharset() != null) {
|
||||
armorOut.setHeader("Charset", data.getCharset());
|
||||
}
|
||||
// add proprietary header to indicate that this is a key backup
|
||||
if (data.isAddBackupHeader()) {
|
||||
armorOut.setHeader("BackupVersion", "2");
|
||||
String passphraseFormat = data.getPassphraseFormat();
|
||||
if (passphraseFormat != null) {
|
||||
armorOut.setHeader("Passphrase-Format", passphraseFormat);
|
||||
}
|
||||
String passphraseBegin = data.getPassphraseBegin();
|
||||
if (passphraseBegin != null) {
|
||||
armorOut.setHeader("Passphrase-Begin", passphraseBegin);
|
||||
}
|
||||
out = armorOut;
|
||||
} else {
|
||||
|
||||
@@ -731,7 +731,7 @@ public class OpenPgpService extends Service {
|
||||
// the backup code is cached in CryptoInputParcelCacheService, now we can proceed
|
||||
|
||||
BackupKeyringParcel input = BackupKeyringParcel
|
||||
.createBackupKeyringParcel(masterKeyIds, backupSecret, true, enableAsciiArmorOutput, null);
|
||||
.create(masterKeyIds, backupSecret, true, enableAsciiArmorOutput, null);
|
||||
BackupOperation op = new BackupOperation(this, mKeyRepository, null);
|
||||
ExportResult pgpResult = op.execute(input, inputParcel, outputStream);
|
||||
|
||||
|
||||
@@ -31,14 +31,20 @@ public abstract class BackupKeyringParcel implements Parcelable {
|
||||
@SuppressWarnings("mutable")
|
||||
public abstract long[] getMasterKeyIds();
|
||||
public abstract boolean getExportSecret();
|
||||
public abstract boolean getExportPublic();
|
||||
public abstract boolean getIsEncrypted();
|
||||
public abstract boolean getEnableAsciiArmorOutput();
|
||||
@Nullable
|
||||
public abstract Uri getOutputUri();
|
||||
|
||||
public static BackupKeyringParcel createBackupKeyringParcel(long[] masterKeyIds, boolean exportSecret,
|
||||
public static BackupKeyringParcel create(long[] masterKeyIds, boolean exportSecret,
|
||||
boolean isEncrypted, boolean enableAsciiArmorOutput, Uri outputUri) {
|
||||
return new AutoValue_BackupKeyringParcel(
|
||||
masterKeyIds, exportSecret, isEncrypted, enableAsciiArmorOutput, outputUri);
|
||||
masterKeyIds, exportSecret, true, isEncrypted, enableAsciiArmorOutput, outputUri);
|
||||
}
|
||||
|
||||
public static BackupKeyringParcel createExportAutocryptSetupMessage(long[] masterKeyIds) {
|
||||
return new AutoValue_BackupKeyringParcel(
|
||||
masterKeyIds, true, false, true, true, null);
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,17 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class RequiredInputParcel implements Parcelable {
|
||||
|
||||
public boolean hasPassphraseBegin() {
|
||||
return mInputData != null && mInputData.length == 1 && mInputData[0].length == 2;
|
||||
}
|
||||
|
||||
public String getPassphraseBegin() {
|
||||
return new String(mInputData[0]);
|
||||
}
|
||||
|
||||
public enum RequiredInputType {
|
||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, PASSPHRASE_AUTH,
|
||||
BACKUP_CODE,
|
||||
BACKUP_CODE, NUMERIC_9X4, NUMERIC_9X4_AUTOCRYPT,
|
||||
SECURITY_TOKEN_SIGN, SECURITY_TOKEN_AUTH, SECURITY_TOKEN_DECRYPT,
|
||||
SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD,
|
||||
ENABLE_ORBOT, UPLOAD_FAIL_RETRY
|
||||
@@ -179,6 +187,18 @@ public class RequiredInputParcel implements Parcelable {
|
||||
null, null, null, (long[]) null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredNumeric9x4(String beginChars) {
|
||||
byte[][] inputData = beginChars != null ? new byte[][] { beginChars.getBytes() } : null;
|
||||
return new RequiredInputParcel(RequiredInputType.NUMERIC_9X4,
|
||||
inputData, null, null, (long[]) null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredNumeric9x4Autocrypt(String beginChars) {
|
||||
byte[][] inputData = beginChars != null ? new byte[][] { beginChars.getBytes() } : null;
|
||||
return new RequiredInputParcel(RequiredInputType.NUMERIC_9X4_AUTOCRYPT,
|
||||
inputData, null, null, (long[]) null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredPassphrase(
|
||||
RequiredInputParcel req) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
|
||||
@@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -52,7 +50,6 @@ import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
@@ -66,6 +63,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
|
||||
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
@@ -83,20 +81,13 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
public static final int REQUEST_SAVE = 1;
|
||||
public static final String ARG_BACK_STACK = "back_stack";
|
||||
|
||||
// https://github.com/open-keychain/open-keychain/wiki/Backups
|
||||
// excludes 0 and O
|
||||
private static final char[] mBackupCodeAlphabet =
|
||||
new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
|
||||
|
||||
// argument variables
|
||||
private boolean mExportSecret;
|
||||
private long[] mMasterKeyIds;
|
||||
String mBackupCode;
|
||||
Passphrase mBackupCode;
|
||||
private boolean mExecuteBackupOperation;
|
||||
|
||||
private EditText[] mCodeEditText;
|
||||
private TextView[] mCodeEditText;
|
||||
|
||||
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
|
||||
private Integer mBackStackLevel;
|
||||
@@ -110,7 +101,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
BackupCodeFragment frag = new BackupCodeFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_BACKUP_CODE, generateRandomBackupCode());
|
||||
args.putParcelable(ARG_BACKUP_CODE, Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase());
|
||||
args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
|
||||
args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
|
||||
args.putBoolean(ARG_EXECUTE_BACKUP_OPERATION, executeBackupOperation);
|
||||
@@ -149,13 +140,16 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
item.setChecked(newCheckedState);
|
||||
mDebugModeAcceptAnyCode = newCheckedState;
|
||||
if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) {
|
||||
mCodeEditText[0].setText("ABCD");
|
||||
mCodeEditText[1].setText("EFGH");
|
||||
mCodeEditText[2].setText("IJKL");
|
||||
mCodeEditText[3].setText("MNOP");
|
||||
mCodeEditText[4].setText("QRST");
|
||||
mCodeEditText[5].setText("UVWX");
|
||||
Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show();
|
||||
mCodeEditText[0].setText("1234");
|
||||
mCodeEditText[1].setText("5678");
|
||||
mCodeEditText[2].setText("9012");
|
||||
mCodeEditText[3].setText("3456");
|
||||
mCodeEditText[4].setText("7890");
|
||||
mCodeEditText[5].setText("1234");
|
||||
mCodeEditText[6].setText("5678");
|
||||
mCodeEditText[7].setText("9012");
|
||||
mCodeEditText[8].setText("3456");
|
||||
Notify.create(getActivity(), "Actual backup code is all '1's", Style.WARN).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -178,7 +172,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
mTitleAnimator.setDisplayedChild(1, animate);
|
||||
mStatusAnimator.setDisplayedChild(1, animate);
|
||||
mCodeFieldsAnimator.setDisplayedChild(1, animate);
|
||||
for (EditText editText : mCodeEditText) {
|
||||
for (TextView editText : mCodeEditText) {
|
||||
editText.setText("");
|
||||
}
|
||||
|
||||
@@ -213,7 +207,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
|
||||
hideKeyboard();
|
||||
|
||||
for (EditText editText : mCodeEditText) {
|
||||
for (TextView editText : mCodeEditText) {
|
||||
editText.setEnabled(false);
|
||||
}
|
||||
|
||||
@@ -255,30 +249,18 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
View view = inflater.inflate(R.layout.backup_code_fragment, container, false);
|
||||
|
||||
Bundle args = getArguments();
|
||||
mBackupCode = args.getString(ARG_BACKUP_CODE);
|
||||
mBackupCode = args.getParcelable(ARG_BACKUP_CODE);
|
||||
mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
|
||||
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
|
||||
mExecuteBackupOperation = args.getBoolean(ARG_EXECUTE_BACKUP_OPERATION, true);
|
||||
|
||||
mCodeEditText = new EditText[6];
|
||||
mCodeEditText[0] = view.findViewById(R.id.backup_code_1);
|
||||
mCodeEditText[1] = view.findViewById(R.id.backup_code_2);
|
||||
mCodeEditText[2] = view.findViewById(R.id.backup_code_3);
|
||||
mCodeEditText[3] = view.findViewById(R.id.backup_code_4);
|
||||
mCodeEditText[4] = view.findViewById(R.id.backup_code_5);
|
||||
mCodeEditText[5] = view.findViewById(R.id.backup_code_6);
|
||||
mCodeEditText = getTransferCodeTextViews(view, R.id.transfer_code_input);
|
||||
|
||||
{
|
||||
TextView[] codeDisplayText = new TextView[6];
|
||||
codeDisplayText[0] = view.findViewById(R.id.backup_code_display_1);
|
||||
codeDisplayText[1] = view.findViewById(R.id.backup_code_display_2);
|
||||
codeDisplayText[2] = view.findViewById(R.id.backup_code_display_3);
|
||||
codeDisplayText[3] = view.findViewById(R.id.backup_code_display_4);
|
||||
codeDisplayText[4] = view.findViewById(R.id.backup_code_display_5);
|
||||
codeDisplayText[5] = view.findViewById(R.id.backup_code_display_6);
|
||||
TextView[] codeDisplayText = getTransferCodeTextViews(view, R.id.transfer_code_display);
|
||||
|
||||
// set backup code in code TextViews
|
||||
char[] backupCode = mBackupCode.toCharArray();
|
||||
char[] backupCode = mBackupCode.getCharArray();
|
||||
for (int i = 0; i < codeDisplayText.length; i++) {
|
||||
codeDisplayText[i].setText(backupCode, i * 5, 4);
|
||||
}
|
||||
@@ -340,6 +322,22 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
return view;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private TextView[] getTransferCodeTextViews(View view, int transferCodeViewGroupId) {
|
||||
ViewGroup transferCodeGroup = view.findViewById(transferCodeViewGroupId);
|
||||
TextView[] codeDisplayText = new TextView[9];
|
||||
codeDisplayText[0] = transferCodeGroup.findViewById(R.id.transfer_code_block_1);
|
||||
codeDisplayText[1] = transferCodeGroup.findViewById(R.id.transfer_code_block_2);
|
||||
codeDisplayText[2] = transferCodeGroup.findViewById(R.id.transfer_code_block_3);
|
||||
codeDisplayText[3] = transferCodeGroup.findViewById(R.id.transfer_code_block_4);
|
||||
codeDisplayText[4] = transferCodeGroup.findViewById(R.id.transfer_code_block_5);
|
||||
codeDisplayText[5] = transferCodeGroup.findViewById(R.id.transfer_code_block_6);
|
||||
codeDisplayText[6] = transferCodeGroup.findViewById(R.id.transfer_code_block_7);
|
||||
codeDisplayText[7] = transferCodeGroup.findViewById(R.id.transfer_code_block_8);
|
||||
codeDisplayText[8] = transferCodeGroup.findViewById(R.id.transfer_code_block_9);
|
||||
return codeDisplayText;
|
||||
}
|
||||
|
||||
private void showFaq() {
|
||||
HelpActivity.startHelpActivity(getActivity(), HelpActivity.TAB_FAQ);
|
||||
}
|
||||
@@ -369,8 +367,8 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
|
||||
}
|
||||
|
||||
private void setupEditTextSuccessListener(final EditText[] backupCodes) {
|
||||
for (EditText backupCode : backupCodes) {
|
||||
private void setupEditTextSuccessListener(final TextView[] backupCodes) {
|
||||
for (TextView backupCode : backupCodes) {
|
||||
|
||||
backupCode.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
@@ -410,7 +408,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
}
|
||||
|
||||
StringBuilder backupCodeInput = new StringBuilder(26);
|
||||
for (EditText editText : mCodeEditText) {
|
||||
for (TextView editText : mCodeEditText) {
|
||||
if (editText.getText().length() < 4) {
|
||||
return;
|
||||
}
|
||||
@@ -449,7 +447,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
|
||||
}
|
||||
|
||||
private static void setupEditTextFocusNext(final EditText[] backupCodes) {
|
||||
private static void setupEditTextFocusNext(final TextView[] backupCodes) {
|
||||
for (int i = 0; i < backupCodes.length - 1; i++) {
|
||||
|
||||
final int next = i + 1;
|
||||
@@ -516,9 +514,9 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
+ (mExportSecret ? Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_SECRET
|
||||
: Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_PUBLIC);
|
||||
|
||||
Passphrase passphrase = new Passphrase(mBackupCode);
|
||||
Passphrase passphrase = new Passphrase(mBackupCode.getCharArray());
|
||||
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
|
||||
passphrase = new Passphrase("AAAA-AAAA-AAAA-AAAA-AAAA-AAAA");
|
||||
passphrase = new Passphrase("1111-1111-1111-1111-1111-1111-1111-1111-1111");
|
||||
}
|
||||
|
||||
// if we don't want to execute the actual operation outside of this activity, drop out here
|
||||
@@ -612,7 +610,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
@Override
|
||||
public BackupKeyringParcel createOperationInput() {
|
||||
return BackupKeyringParcel
|
||||
.createBackupKeyringParcel(mMasterKeyIds, mExportSecret, true, true, mCachedBackupUri);
|
||||
.create(mMasterKeyIds, mExportSecret, true, true, mCachedBackupUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -631,27 +629,4 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
||||
mCachedBackupUri = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate backup code using format defined in
|
||||
* https://github.com/open-keychain/open-keychain/wiki/Backups
|
||||
*/
|
||||
@NonNull
|
||||
private static String generateRandomBackupCode() {
|
||||
|
||||
Random r = new SecureRandom();
|
||||
|
||||
// simple generation of a 24 character backup code
|
||||
StringBuilder code = new StringBuilder(28);
|
||||
for (int i = 0; i < 24; i++) {
|
||||
if (i == 4 || i == 8 || i == 12 || i == 16 || i == 20) {
|
||||
code.append('-');
|
||||
}
|
||||
|
||||
code.append(mBackupCodeAlphabet[r.nextInt(mBackupCodeAlphabet.length)]);
|
||||
}
|
||||
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
public class DecryptActivity extends BaseActivity {
|
||||
public static final String APPLICATION_AUTOCRYPT_SETUP = "application/autocrypt-setup";
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_DECRYPT_FROM_CLIPBOARD = "DECRYPT_DATA_CLIPBOARD";
|
||||
@@ -84,6 +85,7 @@ public class DecryptActivity extends BaseActivity {
|
||||
|
||||
// depending on the data source, we may or may not be able to delete the original file
|
||||
boolean canDelete = false;
|
||||
boolean isAutocryptSetup = false;
|
||||
|
||||
try {
|
||||
|
||||
@@ -160,6 +162,9 @@ public class DecryptActivity extends BaseActivity {
|
||||
case Constants.DECRYPT_DATA:
|
||||
default:
|
||||
Uri uri = intent.getData();
|
||||
|
||||
isAutocryptSetup = APPLICATION_AUTOCRYPT_SETUP.equalsIgnoreCase(intent.getType());
|
||||
|
||||
if (uri != null) {
|
||||
|
||||
if ("com.android.email.attachmentprovider".equals(uri.getHost())) {
|
||||
@@ -187,7 +192,7 @@ public class DecryptActivity extends BaseActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
displayListFragment(uris, canDelete);
|
||||
displayListFragment(uris, canDelete, isAutocryptSetup);
|
||||
|
||||
}
|
||||
|
||||
@@ -211,9 +216,9 @@ public class DecryptActivity extends BaseActivity {
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public void displayListFragment(ArrayList<Uri> inputUris, boolean canDelete) {
|
||||
public void displayListFragment(ArrayList<Uri> inputUris, boolean canDelete, boolean isAutocryptSetup) {
|
||||
|
||||
DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete);
|
||||
DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete, isAutocryptSetup);
|
||||
|
||||
FragmentManager fragMan = getSupportFragmentManager();
|
||||
|
||||
|
||||
@@ -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<Uri> uris, boolean canDelete) {
|
||||
public static DecryptListFragment newInstance(@NonNull ArrayList<Uri> 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<Uri, InputDataResult> 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.
|
||||
* <p/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -162,7 +162,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
|
||||
case PASSPHRASE:
|
||||
case PASSPHRASE_SYMMETRIC:
|
||||
case BACKUP_CODE: {
|
||||
case BACKUP_CODE:
|
||||
case NUMERIC_9X4:
|
||||
case NUMERIC_9X4_AUTOCRYPT: {
|
||||
Intent intent = new Intent(activity, PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
||||
|
||||
@@ -21,21 +21,25 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.*;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.text.Layout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class PrefixedEditText extends EditText {
|
||||
public class PrefixedEditText extends AppCompatEditText {
|
||||
|
||||
private String mPrefix;
|
||||
private Rect mPrefixRect = new Rect();
|
||||
private CharSequence mPrefix;
|
||||
private int mPrefixColor;
|
||||
private int desiredWidth;
|
||||
|
||||
public PrefixedEditText(Context context, AttributeSet attrs) {
|
||||
public PrefixedEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
TypedArray style = context.getTheme().obtainStyledAttributes(
|
||||
attrs, R.styleable.PrefixedEditText, 0, 0);
|
||||
mPrefix = style.getString(R.styleable.PrefixedEditText_prefix);
|
||||
mPrefixColor = style.getColor(R.styleable.PrefixedEditText_prefixColor, getCurrentTextColor());
|
||||
if (mPrefix == null) {
|
||||
mPrefix = "";
|
||||
}
|
||||
@@ -43,20 +47,29 @@ public class PrefixedEditText extends EditText {
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
getPaint().getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixRect);
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
desiredWidth = (int) Math.ceil(Layout.getDesiredWidth(mPrefix, getPaint()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawText(mPrefix, super.getCompoundPaddingLeft(), getBaseline(), getPaint());
|
||||
TextPaint paint = getPaint();
|
||||
// reset to the actual text color - it might be the hint color currently
|
||||
paint.setColor(mPrefixColor);
|
||||
canvas.drawText(mPrefix, 0, mPrefix.length(), super.getCompoundPaddingLeft(), getBaseline(), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingLeft() {
|
||||
return super.getCompoundPaddingLeft() + mPrefixRect.width();
|
||||
return super.getCompoundPaddingLeft() + desiredWidth;
|
||||
}
|
||||
|
||||
public void setPrefix(CharSequence prefix) {
|
||||
mPrefix = prefix;
|
||||
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user