Merge pull request #2157 from open-keychain/token-import
new Security Token import flow
This commit is contained in:
@@ -806,6 +806,13 @@
|
||||
android:taskAffinity=":Nfc"
|
||||
android:theme="@style/Theme.Keychain.Light.Dialog" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.SecurityTokenChangePinOperationActivity"
|
||||
android:allowTaskReparenting="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=":Nfc"
|
||||
android:theme="@style/Theme.Keychain.Light.Dialog" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.HelpActivity"
|
||||
android:label="@string/title_help" />
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.operations;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
@@ -91,7 +92,7 @@ public class DeleteOperation extends BaseReadWriteOperation<DeleteKeyringParcel>
|
||||
}
|
||||
}
|
||||
|
||||
if (isSecret && success > 0) {
|
||||
if (!BuildConfig.DEBUG && isSecret && success > 0) {
|
||||
log.add(LogType.MSG_DEL_CONSOLIDATE, 1);
|
||||
ConsolidateResult sub = mKeyWritableRepository.consolidateDatabaseStep1(mProgressable);
|
||||
log.add(sub, 2);
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -70,25 +72,24 @@ public class PromoteKeyOperation extends BaseReadWriteOperation<PromoteKeyringPa
|
||||
CanonicalizedPublicKeyRing pubRing =
|
||||
mKeyRepository.getCanonicalizedPublicKeyRing(promoteKeyringParcel.getMasterKeyId());
|
||||
|
||||
long[] subKeyIds = promoteKeyringParcel.getSubKeyIds();
|
||||
if (subKeyIds == null) {
|
||||
List<byte[]> fingerprints = promoteKeyringParcel.getFingerprints();
|
||||
if (fingerprints == null) {
|
||||
log.add(LogType.MSG_PR_ALL, 1);
|
||||
} else {
|
||||
// sort for binary search
|
||||
for (CanonicalizedPublicKey key : pubRing.publicKeyIterator()) {
|
||||
long subKeyId = key.getKeyId();
|
||||
if (naiveIndexOf(subKeyIds, subKeyId) != null) {
|
||||
log.add(LogType.MSG_PR_SUBKEY_MATCH, 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||
|
||||
if (naiveArraySearch(fingerprints, key.getFingerprint())) {
|
||||
log.add(LogType.MSG_PR_SUBKEY_MATCH, 1, KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||
} else {
|
||||
log.add(LogType.MSG_PR_SUBKEY_NOMATCH, 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||
log.add(LogType.MSG_PR_SUBKEY_NOMATCH, 1, KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create divert-to-card secret key from public key
|
||||
promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.getCardAid(), subKeyIds);
|
||||
promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.getCardAid(), fingerprints);
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
@@ -128,13 +129,13 @@ public class PromoteKeyOperation extends BaseReadWriteOperation<PromoteKeyringPa
|
||||
|
||||
}
|
||||
|
||||
static private Integer naiveIndexOf(long[] haystack, long needle) {
|
||||
for (int i = 0; i < haystack.length; i++) {
|
||||
if (needle == haystack[i]) {
|
||||
return i;
|
||||
static private boolean naiveArraySearch(List<byte[]> searchElements, byte[] needle) {
|
||||
for (byte[] searchElement : searchElements) {
|
||||
if (Arrays.equals(needle, searchElement)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
|
||||
public class GenericOperationResult extends OperationResult {
|
||||
public GenericOperationResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public GenericOperationResult(Parcel source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public static final Creator<GenericOperationResult> CREATOR = new Creator<GenericOperationResult>() {
|
||||
public GenericOperationResult createFromParcel(final Parcel source) {
|
||||
return new GenericOperationResult(source);
|
||||
}
|
||||
|
||||
public GenericOperationResult[] newArray(final int size) {
|
||||
return new GenericOperationResult[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -905,6 +905,37 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_BENCH_S2K_100MS_ITS (LogLevel.INFO, R.string.msg_bench_s2k_100ms_its),
|
||||
MSG_BENCH_SUCCESS (LogLevel.OK, R.string.msg_bench_success),
|
||||
|
||||
MSG_RET_CURI_ERROR_IO (LogLevel.ERROR, R.string.msg_ret_curi_error_io),
|
||||
MSG_RET_CURI_ERROR_NO_MATCH (LogLevel.ERROR, R.string.msg_ret_curi_error_no_match),
|
||||
MSG_RET_CURI_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ret_curi_error_not_found),
|
||||
MSG_RET_CURI_FOUND (LogLevel.DEBUG, R.string.msg_ret_curi_found),
|
||||
MSG_RET_CURI_MISMATCH (LogLevel.ERROR, R.string.msg_ret_curi_mismatch),
|
||||
MSG_RET_CURI_OK (LogLevel.OK, R.string.msg_ret_curi_ok),
|
||||
MSG_RET_CURI_OPEN (LogLevel.DEBUG, R.string.msg_ret_curi_open),
|
||||
MSG_RET_CURI_START (LogLevel.START, R.string.msg_ret_curi_start),
|
||||
MSG_RET_KS_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ret_ks_error_not_found),
|
||||
MSG_RET_KS_ERROR (LogLevel.ERROR, R.string.msg_ret_ks_error),
|
||||
MSG_RET_KS_FP_MATCH (LogLevel.DEBUG, R.string.msg_ret_ks_fp_match),
|
||||
MSG_RET_KS_FP_MISMATCH (LogLevel.ERROR, R.string.msg_ret_ks_fp_mismatch),
|
||||
MSG_RET_KS_OK (LogLevel.OK, R.string.msg_ret_ks_ok),
|
||||
MSG_RET_KS_START (LogLevel.START, R.string.msg_ret_ks_start),
|
||||
MSG_RET_LOCAL_SEARCH(LogLevel.DEBUG, R.string.msg_ret_local_search),
|
||||
MSG_RET_LOCAL_FP_MATCH (LogLevel.DEBUG, R.string.msg_ret_local_fp_match),
|
||||
MSG_RET_LOCAL_FP_MISMATCH (LogLevel.ERROR, R.string.msg_ret_local_fp_mismatch),
|
||||
MSG_RET_LOCAL_NOT_FOUND (LogLevel.DEBUG, R.string.msg_ret_local_not_found),
|
||||
MSG_RET_LOCAL_NONE_FOUND (LogLevel.ERROR, R.string.msg_ret_local_none_found),
|
||||
MSG_RET_LOCAL_OK (LogLevel.OK, R.string.msg_ret_local_ok),
|
||||
MSG_RET_LOCAL_SECRET (LogLevel.INFO, R.string.msg_ret_local_secret),
|
||||
MSG_RET_LOCAL_START (LogLevel.START, R.string.msg_ret_local_start),
|
||||
MSG_RET_URI_ERROR_NO_MATCH(LogLevel.ERROR, R.string.msg_ret_uri_error_no_match),
|
||||
MSG_RET_URI_ERROR_FETCH (LogLevel.ERROR, R.string.msg_ret_uri_error_fetch),
|
||||
MSG_RET_URI_ERROR_PARSE (LogLevel.ERROR, R.string.msg_ret_uri_error_parse),
|
||||
MSG_RET_URI_FETCHING (LogLevel.DEBUG, R.string.msg_ret_uri_fetching),
|
||||
MSG_RET_URI_OK (LogLevel.OK, R.string.msg_ret_uri_ok),
|
||||
MSG_RET_URI_START (LogLevel.START, R.string.msg_ret_uri_start),
|
||||
MSG_RET_URI_NULL (LogLevel.ERROR, R.string.msg_ret_uri_null),
|
||||
MSG_RET_URI_TEST (LogLevel.DEBUG, R.string.msg_ret_uri_test),
|
||||
|
||||
;
|
||||
|
||||
public final int mMsgId;
|
||||
|
||||
@@ -20,7 +20,9 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -32,6 +34,7 @@ import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
|
||||
|
||||
@@ -153,19 +156,19 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
}
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
|
||||
public UncachedKeyRing createDivertSecretRing(byte[] cardAid, List<byte[]> subKeyFingerprints) {
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
|
||||
|
||||
if (subKeyIds == null) {
|
||||
if (subKeyFingerprints == null) {
|
||||
return new UncachedKeyRing(secRing);
|
||||
}
|
||||
|
||||
// if only specific subkeys should be promoted, construct a
|
||||
// stripped dummy, then move divert-to-card keys over
|
||||
PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
|
||||
for (long subKeyId : subKeyIds) {
|
||||
PGPSecretKey key = secRing.getSecretKey(subKeyId);
|
||||
if (key != null) {
|
||||
for (byte[] subKeyFingerprint : subKeyFingerprints) {
|
||||
PGPSecretKey key = secRing.getSecretKey(KeyFormattingUtils.convertFingerprintToKeyId(subKeyFingerprint));
|
||||
if (key != null && Arrays.equals(subKeyFingerprint, key.getPublicKey().getFingerprint())) {
|
||||
newRing = PGPSecretKeyRing.insertSecretKey(newRing, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -31,6 +32,7 @@ import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeSet;
|
||||
@@ -170,7 +172,24 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
public static IteratorWithIOThrow<UncachedKeyRing> fromStream(final InputStream stream) {
|
||||
public boolean containsKeyWithAnyFingerprint(List<byte[]> expectedFingerprints) {
|
||||
Iterator<UncachedPublicKey> publicKeys = getPublicKeys();
|
||||
|
||||
while (publicKeys.hasNext()) {
|
||||
UncachedPublicKey publicKey = publicKeys.next();
|
||||
|
||||
for (byte[] expectedFingerprint : expectedFingerprints) {
|
||||
if (Arrays.equals(expectedFingerprint, publicKey.getFingerprint())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IteratorWithIOThrow<UncachedKeyRing> fromStream(InputStream rawStream) {
|
||||
final InputStream stream = rawStream.markSupported() ? rawStream: new BufferedInputStream(rawStream);
|
||||
|
||||
return new IteratorWithIOThrow<UncachedKeyRing>() {
|
||||
|
||||
@@ -183,9 +202,15 @@ public class UncachedKeyRing {
|
||||
}
|
||||
|
||||
try {
|
||||
while (stream.available() > 0) {
|
||||
while (true) {
|
||||
// if there are no objects left from the last factory, create a new one
|
||||
if (mObjectFactory == null) {
|
||||
stream.mark(1);
|
||||
if (stream.read() == -1) {
|
||||
break;
|
||||
}
|
||||
stream.reset();
|
||||
|
||||
InputStream in = PGPUtil.getDecoderStream(stream);
|
||||
mObjectFactory = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public class CryptoInputParcelCacheService extends Service {
|
||||
data.setExtrasClassLoader(CryptoInputParcelCacheService.class.getClassLoader());
|
||||
|
||||
// And write out the UUID most and least significant bits.
|
||||
data.setExtrasClassLoader(CryptoInputParcelCacheService.class.getClassLoader());
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID1, mTicket.getMostSignificantBits());
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID2, mTicket.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
@@ -217,6 +217,28 @@ public class SecurityTokenHelper {
|
||||
|
||||
}
|
||||
|
||||
public void resetPin(String newPinStr) throws IOException {
|
||||
if (!mPw3Validated) {
|
||||
verifyPin(0x83); // (Verify PW1 with mode 82 for decryption)
|
||||
}
|
||||
|
||||
byte[] newPin = newPinStr.getBytes();
|
||||
|
||||
final int MAX_PW1_LENGTH_INDEX = 1;
|
||||
byte[] pwStatusBytes = getPwStatusBytes();
|
||||
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
|
||||
throw new IOException("Invalid PIN length");
|
||||
}
|
||||
|
||||
// Command APDU for RESET RETRY COUNTER command (page 33)
|
||||
CommandAPDU changePin = new CommandAPDU(0x00, 0x2C, 0x02, 0x81, newPin);
|
||||
ResponseAPDU response = communicate(changePin);
|
||||
|
||||
if (response.getSW() != APDU_SW_SUCCESS) {
|
||||
throw new CardException("Failed to change PIN", response.getSW());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
|
||||
* conformance to the token's requirements for key length.
|
||||
@@ -587,6 +609,11 @@ public class SecurityTokenHelper {
|
||||
return getData(0x00, 0x4F);
|
||||
}
|
||||
|
||||
public String getUrl() throws IOException {
|
||||
byte[] data = getData(0x5F, 0x50);
|
||||
return new String(data).trim();
|
||||
}
|
||||
|
||||
public String getUserId() throws IOException {
|
||||
return getHolderName(getData(0x00, 0x65));
|
||||
}
|
||||
@@ -951,6 +978,24 @@ public class SecurityTokenHelper {
|
||||
return mOpenPgpCapabilities;
|
||||
}
|
||||
|
||||
public SecurityTokenInfo getTokenInfo() throws IOException {
|
||||
byte[] rawFingerprints = getFingerprints();
|
||||
|
||||
byte[][] fingerprints = new byte[rawFingerprints.length / 20][];
|
||||
ByteBuffer buf = ByteBuffer.wrap(rawFingerprints);
|
||||
for (int i = 0; i < rawFingerprints.length / 20; i++) {
|
||||
fingerprints[i] = new byte[20];
|
||||
buf.get(fingerprints[i]);
|
||||
}
|
||||
|
||||
byte[] aid = getAid();
|
||||
String userId = getUserId();
|
||||
String url = getUrl();
|
||||
byte[] pwInfo = getPwStatusBytes();
|
||||
|
||||
return SecurityTokenInfo.create(fingerprints, aid, userId, url, pwInfo[4], pwInfo[6]);
|
||||
}
|
||||
|
||||
private static class LazyHolder {
|
||||
private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
|
||||
@AutoValue
|
||||
public abstract class SecurityTokenInfo implements Parcelable {
|
||||
private static final byte[] EMPTY_ARRAY = new byte[20];
|
||||
|
||||
public abstract List<byte[]> getFingerprints();
|
||||
@Nullable
|
||||
public abstract byte[] getAid();
|
||||
@Nullable
|
||||
public abstract String getUserId();
|
||||
@Nullable
|
||||
public abstract String getUrl();
|
||||
public abstract int getVerifyRetries();
|
||||
public abstract int getVerifyAdminRetries();
|
||||
|
||||
public boolean isEmpty() {
|
||||
return getFingerprints().isEmpty();
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo create(byte[][] fingerprints, byte[] aid, String userId, String url,
|
||||
int verifyRetries, int verifyAdminRetries) {
|
||||
ArrayList<byte[]> fingerprintList = new ArrayList<>(fingerprints.length);
|
||||
for (byte[] fingerprint : fingerprints) {
|
||||
if (!Arrays.equals(EMPTY_ARRAY, fingerprint)) {
|
||||
fingerprintList.add(fingerprint);
|
||||
}
|
||||
}
|
||||
return new AutoValue_SecurityTokenInfo(fingerprintList, aid, userId, url, verifyRetries, verifyAdminRetries);
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo newInstanceDebugKeyserver() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("1efdb4845ca242ca6977fddb1f788094fd3b430a") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", null, 3, 3);
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo newInstanceDebugUri() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 3, 3);
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo newInstanceDebugLocked() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 3);
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo newInstanceDebugLockedHard() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@@ -34,10 +36,10 @@ public abstract class PromoteKeyringParcel implements Parcelable {
|
||||
public abstract byte[] getCardAid();
|
||||
@Nullable
|
||||
@SuppressWarnings("mutable")
|
||||
public abstract long[] getSubKeyIds();
|
||||
public abstract List<byte[]> getFingerprints();
|
||||
|
||||
public static PromoteKeyringParcel createPromoteKeyringParcel(long keyRingId, byte[] cardAid,
|
||||
@Nullable long[] subKeyIds) {
|
||||
return new AutoValue_PromoteKeyringParcel(keyRingId, cardAid, subKeyIds);
|
||||
@Nullable List<byte[]> fingerprints) {
|
||||
return new AutoValue_PromoteKeyringParcel(keyRingId, cardAid, fingerprints);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
package org.sufficientlysecure.keychain.service.input;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
|
||||
public class RequiredInputParcel implements Parcelable {
|
||||
|
||||
public enum RequiredInputType {
|
||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT,
|
||||
SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
|
||||
SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY
|
||||
}
|
||||
|
||||
public Date mSignatureTime;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.sufficientlysecure.keychain.service.input;
|
||||
|
||||
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
|
||||
@AutoValue
|
||||
public abstract class SecurityTokenChangePinParcel implements Parcelable {
|
||||
public abstract String getAdminPin();
|
||||
public abstract String getNewPin();
|
||||
|
||||
public static SecurityTokenChangePinParcel createSecurityTokenUnlock(String adminPin, String newPin) {
|
||||
return new AutoValue_SecurityTokenChangePinParcel(adminPin, newPin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,14 +31,10 @@ import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.securitytoken.KeyFormat;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
@@ -53,9 +49,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin";
|
||||
public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin";
|
||||
|
||||
public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id";
|
||||
public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid";
|
||||
public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints";
|
||||
public static final String EXTRA_SECURITY_TOKEN_INFO = "token_info";
|
||||
|
||||
public static final String FRAGMENT_TAG = "currentFragment";
|
||||
|
||||
@@ -73,10 +67,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
|
||||
Fragment mCurrentFragment;
|
||||
|
||||
|
||||
byte[] mScannedFingerprints;
|
||||
byte[] mSecurityTokenAid;
|
||||
String mSecurityTokenUserId;
|
||||
SecurityTokenInfo tokenInfo;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -103,7 +94,6 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
|
||||
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
|
||||
mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN);
|
||||
mSecurityTokenAid = savedInstanceState.getByteArray(EXTRA_SECURITY_TOKEN_AID);
|
||||
mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN);
|
||||
mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN);
|
||||
|
||||
@@ -117,22 +107,12 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||
mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false);
|
||||
|
||||
if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) {
|
||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS);
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
|
||||
if (intent.hasExtra(EXTRA_SECURITY_TOKEN_INFO)) {
|
||||
SecurityTokenInfo tokenInfo = intent.getParcelableExtra(EXTRA_SECURITY_TOKEN_INFO);
|
||||
|
||||
if (containsKeys(nfcFingerprints)) {
|
||||
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
||||
nfcFingerprints, nfcAid, nfcUserId);
|
||||
loadFragment(frag, FragAction.START);
|
||||
|
||||
setTitle(R.string.title_import_keys);
|
||||
} else {
|
||||
Fragment frag = CreateSecurityTokenBlankFragment.newInstance(nfcAid);
|
||||
loadFragment(frag, FragAction.START);
|
||||
setTitle(R.string.title_manage_my_keys);
|
||||
}
|
||||
Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo);
|
||||
loadFragment(frag, FragAction.START);
|
||||
setTitle(R.string.title_manage_my_keys);
|
||||
|
||||
// done
|
||||
return;
|
||||
@@ -159,9 +139,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
mScannedFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||
tokenInfo = mSecurityTokenHelper.getTokenInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,45 +157,14 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false;
|
||||
}
|
||||
|
||||
if (containsKeys(mScannedFingerprints)) {
|
||||
try {
|
||||
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints);
|
||||
CachedPublicKeyRing ring = KeyRepository.createDatabaseInteractor(this).getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
||||
mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId);
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo);
|
||||
if (mCurrentFragment instanceof ManageSecurityTokenFragment) {
|
||||
loadFragment(frag, FragAction.REPLACE);
|
||||
} else {
|
||||
Fragment frag = CreateSecurityTokenBlankFragment.newInstance(mSecurityTokenAid);
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsKeys(byte[] scannedFingerprints) {
|
||||
if (scannedFingerprints == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If all fingerprint bytes are 0, the card contains no keys.
|
||||
for (byte b : scannedFingerprints) {
|
||||
if (b != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -228,7 +175,6 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
|
||||
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
|
||||
outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken);
|
||||
outState.putByteArray(EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin);
|
||||
outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin);
|
||||
}
|
||||
@@ -238,10 +184,19 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
setContentView(R.layout.create_key_activity);
|
||||
}
|
||||
|
||||
public void startCreateKeyForSecurityToken(SecurityTokenInfo tokenInfo) {
|
||||
mCreateSecurityToken = true;
|
||||
this.tokenInfo = tokenInfo;
|
||||
|
||||
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
|
||||
public enum FragAction {
|
||||
START,
|
||||
TO_RIGHT,
|
||||
TO_LEFT
|
||||
TO_LEFT,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
public void loadFragment(Fragment fragment, FragAction action) {
|
||||
@@ -259,6 +214,10 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||
case TO_LEFT:
|
||||
getSupportFragmentManager().popBackStackImmediate();
|
||||
break;
|
||||
case REPLACE:
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment, FRAGMENT_TAG)
|
||||
.commit();
|
||||
break;
|
||||
case TO_RIGHT:
|
||||
transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left,
|
||||
R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right);
|
||||
|
||||
@@ -100,7 +100,7 @@ public class CreateSecurityTokenAlgorithmFragment extends Fragment {
|
||||
choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString(
|
||||
R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html)));
|
||||
|
||||
final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.mSecurityTokenAid);
|
||||
final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.tokenInfo.getAid());
|
||||
|
||||
if (version >= 3.0) {
|
||||
choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString(
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
|
||||
public class CreateSecurityTokenBlankFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
View mBackButton;
|
||||
View mNextButton;
|
||||
|
||||
private byte[] mAid;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateSecurityTokenBlankFragment newInstance(byte[] aid) {
|
||||
CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
frag.mAid = aid;
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false);
|
||||
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mNextButton = view.findViewById(R.id.create_key_next_button);
|
||||
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
}
|
||||
});
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
nextClicked();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
private void nextClicked() {
|
||||
mCreateKeyActivity.mCreateSecurityToken = true;
|
||||
mCreateKeyActivity.mSecurityTokenAid = mAid;
|
||||
|
||||
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
|
||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment;
|
||||
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public class CreateSecurityTokenImportResetFragment
|
||||
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
|
||||
implements SecurityTokenListenerFragment {
|
||||
|
||||
private static final int REQUEST_CODE_RESET = 0x00005001;
|
||||
|
||||
private static final String ARG_FINGERPRINTS = "fingerprint";
|
||||
public static final String ARG_AID = "aid";
|
||||
public static final String ARG_USER_ID = "user_ids";
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
private byte[] mTokenFingerprints;
|
||||
private byte[] mTokenAid;
|
||||
private double mTokenVersion;
|
||||
private String mTokenUserId;
|
||||
private byte[] mTokenFingerprint;
|
||||
private TextView vSerNo;
|
||||
private TextView vUserId;
|
||||
private TextView mNextButton;
|
||||
private RadioButton mRadioImport;
|
||||
private RadioButton mRadioFile;
|
||||
private RadioButton mRadioReset;
|
||||
private View mResetWarning;
|
||||
|
||||
// for CryptoOperationFragment key import
|
||||
private HkpKeyserverAddress mKeyserver;
|
||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
||||
|
||||
public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
|
||||
|
||||
CreateSecurityTokenImportResetFragment frag = new CreateSecurityTokenImportResetFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints);
|
||||
args.putByteArray(ARG_AID, nfcAid);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
|
||||
mTokenFingerprints = args.getByteArray(ARG_FINGERPRINTS);
|
||||
mTokenAid = args.getByteArray(ARG_AID);
|
||||
mTokenUserId = args.getString(ARG_USER_ID);
|
||||
|
||||
byte[] fp = new byte[20];
|
||||
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);
|
||||
mTokenFingerprint = fp;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_security_token_import_reset_fragment, container, false);
|
||||
|
||||
vSerNo = (TextView) view.findViewById(R.id.token_serno);
|
||||
vUserId = (TextView) view.findViewById(R.id.token_userid);
|
||||
mNextButton = (TextView) view.findViewById(R.id.create_key_next_button);
|
||||
mRadioImport = (RadioButton) view.findViewById(R.id.token_decision_import);
|
||||
mRadioFile = (RadioButton) view.findViewById(R.id.token_decision_file);
|
||||
mRadioReset = (RadioButton) view.findViewById(R.id.token_decision_reset);
|
||||
mResetWarning = view.findViewById(R.id.token_import_reset_warning);
|
||||
|
||||
View mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mRadioReset.isChecked()) {
|
||||
resetCard();
|
||||
} else if (mRadioImport.isChecked()){
|
||||
importKey();
|
||||
} else {
|
||||
importFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mRadioImport.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
mNextButton.setText(R.string.btn_import);
|
||||
mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_key_plus_grey600_24dp, 0);
|
||||
mNextButton.setVisibility(View.VISIBLE);
|
||||
mResetWarning.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
mRadioFile.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
mNextButton.setText(R.string.key_list_fab_import);
|
||||
mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_folder_grey_24dp, 0);
|
||||
mNextButton.setVisibility(View.VISIBLE);
|
||||
mResetWarning.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
mNextButton.setText(R.string.btn_reset);
|
||||
mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_grey_24dp, 0);
|
||||
mNextButton.setVisibility(View.VISIBLE);
|
||||
mResetWarning.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setData();
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle args) {
|
||||
super.onSaveInstanceState(args);
|
||||
|
||||
args.putByteArray(ARG_FINGERPRINTS, mTokenFingerprints);
|
||||
args.putByteArray(ARG_AID, mTokenAid);
|
||||
args.putString(ARG_USER_ID, mTokenUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
public void setData() {
|
||||
String serno = Hex.toHexString(mTokenAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.security_token_serial_no, serno));
|
||||
|
||||
if (!mTokenUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.security_token_key_holder, mTokenUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.security_token_key_holder_not_set));
|
||||
}
|
||||
}
|
||||
|
||||
public void importKey() {
|
||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||
keyList.add(ParcelableKeyRing.createFromReference(mTokenFingerprint, null, null, null));
|
||||
mKeyList = keyList;
|
||||
|
||||
mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
|
||||
super.setProgressMessageResource(R.string.progress_importing);
|
||||
|
||||
super.cryptoOperation();
|
||||
}
|
||||
|
||||
public void importFile() {
|
||||
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void resetCard() {
|
||||
Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class);
|
||||
RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset();
|
||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
|
||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, CryptoInputParcel.createCryptoInputParcel());
|
||||
startActivityForResult(intent, REQUEST_CODE_RESET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_CODE_RESET && resultCode == Activity.RESULT_OK) {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSecurityTokenInBackground() throws IOException {
|
||||
|
||||
mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints();
|
||||
mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid();
|
||||
mTokenVersion = SecurityTokenHelper.parseOpenPgpVersion(mTokenAid);
|
||||
mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId();
|
||||
|
||||
byte[] fp = new byte[20];
|
||||
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);
|
||||
mTokenFingerprint = fp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenPostExecute() {
|
||||
|
||||
setData();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportKeyringParcel createOperationInput() {
|
||||
return ImportKeyringParcel.createImportKeyringParcel(mKeyList, mKeyserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueuedOperationSuccess(ImportKeyResult result) {
|
||||
long[] masterKeyIds = result.getImportedMasterKeyIds();
|
||||
if (masterKeyIds.length == 0) {
|
||||
super.onCryptoOperationError(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// null-protected from Queueing*Fragment
|
||||
Activity activity = getActivity();
|
||||
|
||||
Intent viewKeyIntent = new Intent(activity, ViewKeyActivity.class);
|
||||
// use the imported masterKeyId, not the one from the token, because
|
||||
// that one might* just have been a subkey of the imported key
|
||||
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0]));
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mTokenVersion);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints);
|
||||
|
||||
if (activity instanceof CreateKeyActivity) {
|
||||
((CreateKeyActivity) activity).finishWithFirstTimeHandling(viewKeyIntent);
|
||||
} else {
|
||||
activity.startActivity(viewKeyIntent);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class CreateSecurityTokenPinFragment extends Fragment {
|
||||
|
||||
mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString());
|
||||
|
||||
final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.mSecurityTokenAid);
|
||||
final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.tokenInfo.getAid());
|
||||
|
||||
Fragment frag;
|
||||
if (version >= 3.0) {
|
||||
|
||||
@@ -17,18 +17,26 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment;
|
||||
|
||||
|
||||
public class CreateSecurityTokenWaitFragment extends Fragment {
|
||||
|
||||
@@ -44,6 +52,36 @@ public class CreateSecurityTokenWaitFragment extends Fragment {
|
||||
if (this.getActivity() instanceof BaseSecurityTokenActivity) {
|
||||
((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection();
|
||||
}
|
||||
|
||||
setHasOptionsMenu(BuildConfig.DEBUG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.token_debug, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_token_debug_uri:
|
||||
mCreateKeyActivity.loadFragment(ManageSecurityTokenFragment.newInstance(
|
||||
SecurityTokenInfo.newInstanceDebugUri()), FragAction.TO_RIGHT);
|
||||
break;
|
||||
case R.id.menu_token_debug_keyserver:
|
||||
mCreateKeyActivity.loadFragment(ManageSecurityTokenFragment.newInstance(
|
||||
SecurityTokenInfo.newInstanceDebugKeyserver()), FragAction.TO_RIGHT);
|
||||
break;
|
||||
case R.id.menu_token_debug_locked:
|
||||
mCreateKeyActivity.loadFragment(ManageSecurityTokenFragment.newInstance(
|
||||
SecurityTokenInfo.newInstanceDebugLocked()), FragAction.TO_RIGHT);
|
||||
break;
|
||||
case R.id.menu_token_debug_locked_hard:
|
||||
mCreateKeyActivity.loadFragment(ManageSecurityTokenFragment.newInstance(
|
||||
SecurityTokenInfo.newInstanceDebugLockedHard()), FragAction.TO_RIGHT);
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import nordpol.android.NfcGuideView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.service.input.SecurityTokenChangePinParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.OrientationUtils;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
public class SecurityTokenChangePinOperationActivity extends BaseSecurityTokenActivity {
|
||||
public static final String EXTRA_CHANGE_PIN_PARCEL = "change_pin_parcel";
|
||||
|
||||
public static final String RESULT_TOKEN_INFO = "token_info";
|
||||
|
||||
public ViewAnimator vAnimator;
|
||||
public TextView vErrorText;
|
||||
private TextView vErrorTextPin;
|
||||
public Button vErrorTryAgainButton;
|
||||
public NfcGuideView nfcGuideView;
|
||||
|
||||
private SecurityTokenChangePinParcel changePinInput;
|
||||
|
||||
private SecurityTokenInfo resultTokenInfo;
|
||||
|
||||
@Override
|
||||
protected void initTheme() {
|
||||
mThemeChanger = new ThemeChanger(this);
|
||||
mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog,
|
||||
R.style.Theme_Keychain_Dark_Dialog);
|
||||
mThemeChanger.changeTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
|
||||
|
||||
nfcGuideView = (NfcGuideView) findViewById(R.id.nfc_guide_view);
|
||||
|
||||
// prevent annoying orientation changes while fumbling with the device
|
||||
OrientationUtils.lockOrientation(this);
|
||||
// prevent close when touching outside of the dialog (happens easily when fumbling with the device)
|
||||
setFinishOnTouchOutside(false);
|
||||
// keep screen on
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
setTitle(R.string.security_token_nfc_text);
|
||||
|
||||
vAnimator = (ViewAnimator) findViewById(R.id.view_animator);
|
||||
vAnimator.setDisplayedChild(0);
|
||||
|
||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION);
|
||||
|
||||
vErrorText = (TextView) findViewById(R.id.security_token_activity_3_error_text);
|
||||
vErrorTextPin = (TextView) findViewById(R.id.security_token_activity_4_error_text);
|
||||
vErrorTryAgainButton = (Button) findViewById(R.id.security_token_activity_3_error_try_again);
|
||||
vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
resumeTagHandling();
|
||||
|
||||
vAnimator.setDisplayedChild(0);
|
||||
|
||||
nfcGuideView.setVisibility(View.VISIBLE);
|
||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION);
|
||||
}
|
||||
});
|
||||
Button vCancel = (Button) findViewById(R.id.security_token_activity_0_cancel);
|
||||
vCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.security_token_activity_4_back).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_TOKEN_INFO, resultTokenInfo);
|
||||
setResult(RESULT_CANCELED, result);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
changePinInput = getIntent().getParcelableExtra(EXTRA_CHANGE_PIN_PARCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.security_token_operation_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenPreExecute() {
|
||||
// start with indeterminate progress
|
||||
vAnimator.setDisplayedChild(1);
|
||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSecurityTokenInBackground() throws IOException {
|
||||
mSecurityTokenHelper.setAdminPin(new Passphrase(changePinInput.getAdminPin()));
|
||||
mSecurityTokenHelper.resetPin(changePinInput.getNewPin());
|
||||
|
||||
resultTokenInfo = mSecurityTokenHelper.getTokenInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onSecurityTokenPostExecute() {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_TOKEN_INFO, resultTokenInfo);
|
||||
setResult(RESULT_OK, result);
|
||||
|
||||
// show finish
|
||||
vAnimator.setDisplayedChild(2);
|
||||
|
||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE);
|
||||
|
||||
if (mSecurityTokenHelper.isPersistentConnectionAllowed()) {
|
||||
// Just close
|
||||
finish();
|
||||
} else {
|
||||
mSecurityTokenHelper.clearSecureMessaging();
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
// check all 200ms if Security Token has been taken away
|
||||
while (true) {
|
||||
if (isSecurityTokenConnected()) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
finish();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSecurityTokenError(String error) {
|
||||
pauseTagHandling();
|
||||
|
||||
vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text));
|
||||
vAnimator.setDisplayedChild(3);
|
||||
|
||||
nfcGuideView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) {
|
||||
resultTokenInfo = tokeninfo;
|
||||
|
||||
pauseTagHandling();
|
||||
|
||||
vErrorTextPin.setText(error);
|
||||
vAnimator.setDisplayedChild(4);
|
||||
|
||||
nfcGuideView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.securitytoken.KeyType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
@@ -65,6 +66,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||
|
||||
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
||||
public static final String RESULT_TOKEN_INFO = "token_info";
|
||||
|
||||
public ViewAnimator vAnimator;
|
||||
public TextView vErrorText;
|
||||
@@ -74,6 +76,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||
private RequiredInputParcel mRequiredInput;
|
||||
|
||||
private CryptoInputParcel mInputParcel;
|
||||
private SecurityTokenInfo mResultTokenInfo;
|
||||
|
||||
@Override
|
||||
protected void initTheme() {
|
||||
@@ -277,6 +280,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||
}
|
||||
case SECURITY_TOKEN_RESET_CARD: {
|
||||
mSecurityTokenHelper.resetAndWipeToken();
|
||||
mResultTokenInfo = mSecurityTokenHelper.getTokenInfo();
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -334,6 +338,9 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||
Intent result = new Intent();
|
||||
// send back the CryptoInputParcel we received
|
||||
result.putExtra(RESULT_CRYPTO_INPUT, inputParcel);
|
||||
if (mResultTokenInfo != null) {
|
||||
result.putExtra(RESULT_TOKEN_INFO, mResultTokenInfo);
|
||||
}
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
@@ -348,7 +355,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenPinError(String error) {
|
||||
public void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) {
|
||||
onSecurityTokenError(error);
|
||||
|
||||
// clear (invalid) passphrase
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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.ui;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
public class ViewKeySecurityTokenFragment
|
||||
extends QueueingCryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult>
|
||||
implements LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_USER_ID = "user_id";
|
||||
public static final String ARG_CARD_AID = "aid";
|
||||
public static final String ARG_CARD_VERSION = "version";
|
||||
|
||||
private byte[][] mFingerprints;
|
||||
private String mUserId;
|
||||
private byte[] mCardAid;
|
||||
private double mCardVersion;
|
||||
private long mMasterKeyId;
|
||||
private long[] mSubKeyIds;
|
||||
|
||||
private Button vButton;
|
||||
private TextView vStatus;
|
||||
|
||||
public static ViewKeySecurityTokenFragment newInstance(long masterKeyId,
|
||||
byte[] fingerprints, String userId, byte[] aid, double version) {
|
||||
ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_MASTER_KEY_ID, masterKeyId);
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprints);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
args.putByteArray(ARG_CARD_AID, aid);
|
||||
args.putDouble(ARG_CARD_VERSION, version);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public ViewKeySecurityTokenFragment() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT));
|
||||
mFingerprints = new byte[buf.remaining()/20][];
|
||||
for (int i = 0; i < mFingerprints.length; i++) {
|
||||
mFingerprints[i] = new byte[20];
|
||||
buf.get(mFingerprints[i]);
|
||||
}
|
||||
mUserId = args.getString(ARG_USER_ID);
|
||||
mCardAid = args.getByteArray(ARG_CARD_AID);
|
||||
mCardVersion = args.getDouble(ARG_CARD_VERSION);
|
||||
|
||||
mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.view_key_security_token, null);
|
||||
|
||||
TextView vSerNo = (TextView) view.findViewById(R.id.token_serno);
|
||||
TextView vUserId = (TextView) view.findViewById(R.id.token_userid);
|
||||
|
||||
String serno = Hex.toHexString(mCardAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.security_token_serial_no, serno));
|
||||
|
||||
if (!mUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.security_token_key_holder, mUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.security_token_key_holder_not_set));
|
||||
}
|
||||
|
||||
vButton = (Button) view.findViewById(R.id.button_bind);
|
||||
vButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
promoteToSecretKey();
|
||||
}
|
||||
});
|
||||
|
||||
vStatus = (TextView) view.findViewById(R.id.token_status);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void promoteToSecretKey() {
|
||||
long[] subKeyIds = new long[mFingerprints.length];
|
||||
for (int i = 0; i < subKeyIds.length; i++) {
|
||||
subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]);
|
||||
}
|
||||
|
||||
// mMasterKeyId and mCardAid are already set
|
||||
mSubKeyIds = subKeyIds;
|
||||
|
||||
cryptoOperation();
|
||||
}
|
||||
|
||||
public static final String[] PROJECTION = new String[]{
|
||||
Keys._ID,
|
||||
Keys.KEY_ID,
|
||||
Keys.RANK,
|
||||
Keys.HAS_SECRET,
|
||||
Keys.FINGERPRINT
|
||||
};
|
||||
// private static final int INDEX_KEY_ID = 1;
|
||||
// private static final int INDEX_RANK = 2;
|
||||
private static final int INDEX_HAS_SECRET = 3;
|
||||
private static final int INDEX_FINGERPRINT = 4;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId),
|
||||
PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (!data.moveToFirst()) {
|
||||
// wut?
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allBound = true;
|
||||
boolean noneBound = true;
|
||||
|
||||
do {
|
||||
SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET));
|
||||
byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
Integer index = naiveIndexOf(mFingerprints, fingerprint);
|
||||
if (index == null) {
|
||||
continue;
|
||||
}
|
||||
if (keyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||
noneBound = false;
|
||||
} else {
|
||||
allBound = false;
|
||||
}
|
||||
} while (data.moveToNext());
|
||||
|
||||
if (allBound) {
|
||||
vButton.setVisibility(View.GONE);
|
||||
vStatus.setText(R.string.security_token_status_bound);
|
||||
} else {
|
||||
vButton.setVisibility(View.VISIBLE);
|
||||
vStatus.setText(noneBound
|
||||
? R.string.security_token_status_unbound
|
||||
: R.string.security_token_status_partly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
|
||||
for (int i = 0; i < haystack.length; i++) {
|
||||
if (Arrays.equals(needle, haystack[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PromoteKeyringParcel createOperationInput() {
|
||||
return PromoteKeyringParcel.createPromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueuedOperationSuccess(PromoteKeyResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,13 +41,10 @@ import nordpol.android.TagDispatcher;
|
||||
import nordpol.android.TagDispatcherBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.securitytoken.CardException;
|
||||
import org.sufficientlysecure.keychain.securitytoken.NfcTransport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.UsbConnectionDispatcher;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport;
|
||||
@@ -56,10 +53,8 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -78,9 +73,7 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
protected UsbConnectionDispatcher mUsbDispatcher;
|
||||
private boolean mTagHandlingEnabled;
|
||||
|
||||
private byte[] mSecurityTokenFingerprints;
|
||||
private String mSecurityTokenUserId;
|
||||
private byte[] mSecurityTokenAid;
|
||||
private SecurityTokenInfo tokenInfo;
|
||||
|
||||
/**
|
||||
* Override to change UI before SecurityToken handling (UI thread)
|
||||
@@ -92,36 +85,17 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
* Override to implement SecurityToken operations (background thread)
|
||||
*/
|
||||
protected void doSecurityTokenInBackground() throws IOException {
|
||||
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||
mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||
tokenInfo = mSecurityTokenHelper.getTokenInfo();
|
||||
Log.d(Constants.TAG, "Security Token: " + tokenInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to handle result of SecurityToken operations (UI thread)
|
||||
*/
|
||||
protected void onSecurityTokenPostExecute() {
|
||||
|
||||
final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
|
||||
|
||||
try {
|
||||
CachedPublicKeyRing ring = KeyRepository.createDatabaseInteractor(this).getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
|
||||
long masterKeyId = ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
}
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_INFO, tokenInfo);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +108,7 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
/**
|
||||
* Override to do something when PIN is wrong, e.g., clear passphrases (UI thread)
|
||||
*/
|
||||
protected void onSecurityTokenPinError(String error) {
|
||||
protected void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) {
|
||||
onSecurityTokenError(error);
|
||||
}
|
||||
|
||||
@@ -268,8 +242,16 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410
|
||||
if ((status & (short) 0xFFF0) == 0x63C0) {
|
||||
int tries = status & 0x000F;
|
||||
|
||||
SecurityTokenInfo tokeninfo = null;
|
||||
try {
|
||||
tokeninfo = mSecurityTokenHelper.getTokenInfo();
|
||||
} catch (IOException e2) {
|
||||
// don't care
|
||||
}
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries));
|
||||
onSecurityTokenPinError(
|
||||
getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries), tokeninfo);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,8 +264,15 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN
|
||||
case 0x6982: {
|
||||
SecurityTokenInfo tokeninfo = null;
|
||||
try {
|
||||
tokeninfo = mSecurityTokenHelper.getTokenInfo();
|
||||
} catch (IOException e2) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied));
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied), tokeninfo);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Selected file in termination state */
|
||||
@@ -296,14 +285,14 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f
|
||||
case 0x6700: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length));
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Incorrect parameters in the data field */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN
|
||||
case 0x6A80: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data));
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */
|
||||
|
||||
@@ -25,9 +25,11 @@ import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
@@ -55,6 +57,8 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
*/
|
||||
public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> {
|
||||
|
||||
private long operationStartTime;
|
||||
|
||||
public interface Callback<T extends Parcelable, S extends OperationResult> {
|
||||
T createOperationInput();
|
||||
|
||||
@@ -67,6 +71,19 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
boolean onCryptoSetProgress(String msg, int progress, int max);
|
||||
}
|
||||
|
||||
public static abstract class AbstractCallback<T extends Parcelable, S extends OperationResult>
|
||||
implements Callback<T,S> {
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
throw new UnsupportedOperationException("Unexpectedly cancelled operation!!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// request codes from CryptoOperationHelper are created essentially
|
||||
// a static property, used to identify requestCodes meant for this
|
||||
// particular helper. a request code looks as follows:
|
||||
@@ -85,6 +102,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
|
||||
private Integer mProgressMessageResource;
|
||||
private boolean mCancellable = false;
|
||||
private Long minimumOperationDelay;
|
||||
|
||||
private FragmentActivity mActivity;
|
||||
private Fragment mFragment;
|
||||
@@ -119,6 +137,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
mProgressMessageResource = id;
|
||||
}
|
||||
|
||||
public void setOperationMinimumDelay(Long delay) {
|
||||
this.minimumOperationDelay = delay;
|
||||
}
|
||||
|
||||
public void setProgressCancellable(boolean cancellable) {
|
||||
mCancellable = cancellable;
|
||||
}
|
||||
@@ -323,10 +345,11 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
}
|
||||
|
||||
public void cryptoOperation() {
|
||||
operationStartTime = SystemClock.elapsedRealtime();
|
||||
cryptoOperation(CryptoInputParcel.createCryptoInputParcel(new Date()));
|
||||
}
|
||||
|
||||
public void onHandleResult(OperationResult result) {
|
||||
private void onHandleResult(final OperationResult result) {
|
||||
Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success());
|
||||
|
||||
if (result instanceof InputPendingResult) {
|
||||
@@ -340,6 +363,21 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
|
||||
dismissProgress();
|
||||
|
||||
long elapsedTime = SystemClock.elapsedRealtime() - operationStartTime;
|
||||
if (minimumOperationDelay == null || elapsedTime > minimumOperationDelay) {
|
||||
returnResultToCallback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
returnResultToCallback(result);
|
||||
}
|
||||
}, minimumOperationDelay - elapsedTime);
|
||||
}
|
||||
|
||||
private void returnResultToCallback(OperationResult result) {
|
||||
try {
|
||||
if (result.success()) {
|
||||
// noinspection unchecked, because type erasure :(
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
package org.sufficientlysecure.keychain.ui.keyview;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
@@ -76,7 +75,6 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
@@ -87,7 +85,6 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.BackupActivity;
|
||||
import org.sufficientlysecure.keychain.ui.CertifyFingerprintActivity;
|
||||
import org.sufficientlysecure.keychain.ui.CertifyKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.DeleteKeyDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.EncryptFilesActivity;
|
||||
import org.sufficientlysecure.keychain.ui.EncryptTextActivity;
|
||||
@@ -98,7 +95,6 @@ import org.sufficientlysecure.keychain.ui.QrCodeViewActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SafeSlingerActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyKeybaseFragment;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeySecurityTokenFragment;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
@@ -107,7 +103,6 @@ import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
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.util.QrCodeUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
@@ -119,12 +114,6 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
||||
|
||||
public static final String EXTRA_SECURITY_TOKEN_USER_ID = "security_token_user_id";
|
||||
public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid";
|
||||
public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version";
|
||||
public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints";
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE})
|
||||
private @interface RequestType {
|
||||
@@ -173,8 +162,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
private boolean mIsSecure = true;
|
||||
private boolean mIsExpired = false;
|
||||
|
||||
private boolean mShowSecurityTokenAfterCreation = false;
|
||||
|
||||
private MenuItem mRefreshItem;
|
||||
private boolean mIsRefreshing;
|
||||
private Animation mRotate, mRotateSpin;
|
||||
@@ -183,11 +170,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
private long mMasterKeyId;
|
||||
private byte[] mFingerprint;
|
||||
|
||||
private byte[] mSecurityTokenFingerprints;
|
||||
private String mSecurityTokenUserId;
|
||||
private byte[] mSecurityTokenAid;
|
||||
private double mSecurityTokenVersion;
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -334,11 +316,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
.replace(R.id.view_key_keybase_fragment, keybaseFrag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
// need to postpone loading of the security token fragment until after mMasterKeyId
|
||||
// is available, but we mark here that this should be done
|
||||
mShowSecurityTokenAfterCreation = true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -641,89 +618,10 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSecurityTokenInBackground() throws IOException {
|
||||
|
||||
mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSecurityTokenPostExecute() {
|
||||
|
||||
long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
|
||||
|
||||
try {
|
||||
|
||||
// if the security token matches a subkey in any key
|
||||
CachedPublicKeyRing ring = mKeyRepository.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(tokenId));
|
||||
byte[] candidateFp = ring.getFingerprint();
|
||||
|
||||
// if the master key of that key matches this one, just show the token dialog
|
||||
if (Arrays.equals(candidateFp, mFingerprint)) {
|
||||
showSecurityTokenFragment(
|
||||
mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid, mSecurityTokenVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, offer to go to that key
|
||||
final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(candidateFp);
|
||||
Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_security_token_view).show();
|
||||
// and if it's not found, offer import
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_security_token_import).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void showSecurityTokenFragment(
|
||||
final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid, final double tokenVersion) {
|
||||
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance(
|
||||
mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
|
||||
manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
manager.beginTransaction()
|
||||
.addToBackStack("security_token")
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
// if this is called while the activity wasn't resumed, just forget it happened
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
});
|
||||
|
||||
super.onSecurityTokenPostExecute();
|
||||
finish();
|
||||
}
|
||||
|
||||
public void showMainFragment() {
|
||||
@@ -927,17 +825,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
// queue showing of the main fragment
|
||||
showMainFragment();
|
||||
|
||||
// if it wasn't shown yet, display token fragment
|
||||
if (mShowSecurityTokenAfterCreation && getIntent().hasExtra(EXTRA_SECURITY_TOKEN_AID)) {
|
||||
mShowSecurityTokenAfterCreation = false;
|
||||
Intent intent = getIntent();
|
||||
byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS);
|
||||
String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
|
||||
byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
|
||||
double tokenVersion = intent.getDoubleExtra(EXTRA_SECURITY_TOKEN_VERSION, 2.0);
|
||||
showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
||||
}
|
||||
|
||||
// if the refresh animation isn't playing
|
||||
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
|
||||
// re-create options menu based on mIsSecret, mIsVerified
|
||||
|
||||
@@ -151,7 +151,8 @@ public class IdentityLoader extends AsyncTaskLoader<List<IdentityInfo>> {
|
||||
|
||||
private Intent getTrustIdActivityIntentIfResolvable(String packageName, String autocryptPeer) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(packageName + ".AUTOCRYPT_PEER_ACTION");
|
||||
intent.setAction("org.autocrypt.PEER_ACTION");
|
||||
intent.setPackage(packageName);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, autocryptPeer);
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.sufficientlysecure.keychain.ui.token;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnShowListener;
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
|
||||
|
||||
class ChangePinDialogHelper {
|
||||
@CheckResult
|
||||
static AlertDialog createAdminPinDialog(Context context, final ManageSecurityTokenMvpPresenter presenter) {
|
||||
ContextThemeWrapper themedContext = ThemeChanger.getDialogThemeWrapper(context);
|
||||
|
||||
@SuppressLint("InflateParams") // it's a dialog, no root element
|
||||
View view = LayoutInflater.from(themedContext).inflate(R.layout.admin_pin_dialog, null, false);
|
||||
final EditText adminPin = (EditText) view.findViewById(R.id.admin_pin_current);
|
||||
final EditText newPin = (EditText) view.findViewById(R.id.pin_new);
|
||||
final EditText newPinRepeat = (EditText) view.findViewById(R.id.pin_new_repeat);
|
||||
|
||||
AlertDialog dialog = new Builder(themedContext)
|
||||
.setView(view)
|
||||
.setNegativeButton(R.string.button_cancel, null)
|
||||
.setPositiveButton(R.string.token_unlock_ok, null).create();
|
||||
dialog.setOnShowListener(new OnShowListener() {
|
||||
@Override
|
||||
public void onShow(final DialogInterface dialog) {
|
||||
((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
checkAndHandleInput(adminPin, newPin, newPinRepeat, dialog, presenter);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private static void checkAndHandleInput(EditText adminPinView, EditText newPinView, EditText newPinRepeatView,
|
||||
DialogInterface dialog, ManageSecurityTokenMvpPresenter presenter) {
|
||||
String adminPin = adminPinView.getText().toString();
|
||||
String newPin = newPinView.getText().toString();
|
||||
String newPinRepeat = newPinRepeatView.getText().toString();
|
||||
|
||||
if (adminPin.length() < 8) {
|
||||
adminPinView.setError(adminPinView.getContext().getString(R.string.token_error_admin_min8));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPin.length() < 6) {
|
||||
newPinView.setError(newPinView.getContext().getString(R.string.token_error_pin_min6));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newPin.equals(newPinRepeat)) {
|
||||
newPinRepeatView.setError(newPinRepeatView.getContext().getString(R.string.token_error_pin_repeat));
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
presenter.onInputAdminPin(adminPin, newPin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.ui.token;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment.StatusLine;
|
||||
|
||||
|
||||
class ManageSecurityTokenContract {
|
||||
interface ManageSecurityTokenMvpPresenter {
|
||||
void setView(ManageSecurityTokenMvpView createSecurityTokenImportFragment);
|
||||
void onActivityCreated();
|
||||
|
||||
void onClickRetry();
|
||||
void onClickViewKey();
|
||||
void onMenuClickViewLog();
|
||||
|
||||
void onClickImport();
|
||||
void onImportSuccess(OperationResult result);
|
||||
void onImportError(OperationResult result);
|
||||
|
||||
void onPromoteSuccess(OperationResult result);
|
||||
void onPromoteError(OperationResult result);
|
||||
|
||||
|
||||
void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo);
|
||||
|
||||
void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo);
|
||||
|
||||
void onClickLoadFile();
|
||||
void onFileSelected(Uri fileUri);
|
||||
void onStoragePermissionGranted();
|
||||
void onStoragePermissionDenied();
|
||||
|
||||
void onClickResetToken();
|
||||
void onClickConfirmReset();
|
||||
void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo);
|
||||
void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo);
|
||||
|
||||
void onClickSetupToken();
|
||||
|
||||
void onClickUnlockToken();
|
||||
void onMenuClickChangePin();
|
||||
void onInputAdminPin(String adminPin, String newPin);
|
||||
|
||||
void onClickUnlockTokenImpossible();
|
||||
}
|
||||
|
||||
interface ManageSecurityTokenMvpView {
|
||||
void statusLineAdd(StatusLine statusLine);
|
||||
void statusLineOk();
|
||||
void statusLineError();
|
||||
void resetStatusLines();
|
||||
|
||||
void showActionImport();
|
||||
void showActionViewKey();
|
||||
void showActionRetryOrFromFile();
|
||||
void showActionLocked(int unlockAttempts);
|
||||
void showActionEmptyToken();
|
||||
void hideAction();
|
||||
|
||||
void operationImportKey(byte[] importKeyData);
|
||||
void operationPromote(long masterKeyId, byte[] cardAid, List<byte[]> fingerprints);
|
||||
void operationResetSecurityToken();
|
||||
void operationChangePinSecurityToken(String adminPin, String newPin);
|
||||
|
||||
void finishAndShowKey(long masterKeyId);
|
||||
|
||||
void showFileSelectDialog();
|
||||
void showConfirmResetDialog();
|
||||
void showAdminPinDialog();
|
||||
void startCreateKeyForToken(SecurityTokenInfo tokenInfo);
|
||||
|
||||
void showDisplayLogActivity(OperationResult result);
|
||||
|
||||
void requestStoragePermission();
|
||||
|
||||
void showErrorCannotUnlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.ui.token;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.SecurityTokenChangePinParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SecurityTokenChangePinOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpView;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
|
||||
import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
|
||||
|
||||
public class ManageSecurityTokenFragment extends Fragment implements ManageSecurityTokenMvpView, OnClickListener {
|
||||
private static final String ARG_TOKEN_INFO = "token_info";
|
||||
public static final int REQUEST_CODE_OPEN_FILE = 0;
|
||||
public static final int REQUEST_CODE_RESET = 1;
|
||||
public static final int REQUEST_CODE_CHANGE_PIN = 2;
|
||||
public static final int PERMISSION_READ_STORAGE = 0;
|
||||
|
||||
ManageSecurityTokenMvpPresenter presenter;
|
||||
private ViewGroup statusLayoutGroup;
|
||||
private ToolableViewAnimator actionAnimator;
|
||||
private TextView unlockSubtitle;
|
||||
|
||||
ImportKeyringParcel currentImportKeyringParcel;
|
||||
PromoteKeyringParcel currentPromoteKeyringParcel;
|
||||
private LayoutInflater layoutInflater;
|
||||
private StatusIndicator latestStatusIndicator;
|
||||
|
||||
public static Fragment newInstance(SecurityTokenInfo tokenInfo) {
|
||||
ManageSecurityTokenFragment frag = new ManageSecurityTokenFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_TOKEN_INFO, tokenInfo);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
SecurityTokenInfo tokenInfo = args.getParcelable(ARG_TOKEN_INFO);
|
||||
|
||||
presenter = new ManageSecurityTokenPresenter(getContext(), getLoaderManager(), tokenInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
this.layoutInflater = inflater;
|
||||
View view = inflater.inflate(R.layout.create_security_token_import_fragment, container, false);
|
||||
|
||||
statusLayoutGroup = (ViewGroup) view.findViewById(R.id.status_indicator_layout);
|
||||
actionAnimator = (ToolableViewAnimator) view.findViewById(R.id.action_animator);
|
||||
unlockSubtitle = (TextView) view.findViewById(R.id.button_unlock_subtitle);
|
||||
|
||||
view.findViewById(R.id.button_import).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_view_key).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_retry).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_reset_token_1).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_reset_token_2).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_reset_token_3).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_reset_token_4).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_reset_token_5).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_unlock).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_unlock_impossible).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_load_file).setOnClickListener(this);
|
||||
view.findViewById(R.id.button_setup).setOnClickListener(this);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
presenter.setView(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
presenter.setView(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.token_setup, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.view_log: {
|
||||
presenter.onMenuClickViewLog();
|
||||
return true;
|
||||
}
|
||||
case R.id.change_pin: {
|
||||
presenter.onMenuClickChangePin();
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
presenter.onActivityCreated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishAndShowKey(long masterKeyId) {
|
||||
Activity activity = getActivity();
|
||||
|
||||
Intent viewKeyIntent = new Intent(activity, ViewKeyActivity.class);
|
||||
// use the imported masterKeyId, not the one from the token, because
|
||||
// that one might* just have been a subkey of the imported key
|
||||
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
|
||||
if (activity instanceof CreateKeyActivity) {
|
||||
((CreateKeyActivity) activity).finishWithFirstTimeHandling(viewKeyIntent);
|
||||
} else {
|
||||
activity.startActivity(viewKeyIntent);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusLineAdd(StatusLine statusLine) {
|
||||
if (latestStatusIndicator != null) {
|
||||
throw new IllegalStateException("Cannot set next status line before completing previous!");
|
||||
}
|
||||
|
||||
View line = layoutInflater.inflate(R.layout.status_indicator_line, statusLayoutGroup, false);
|
||||
|
||||
latestStatusIndicator = (StatusIndicator) line.findViewById(R.id.status_indicator);
|
||||
latestStatusIndicator.setDisplayedChild(Status.PROGRESS);
|
||||
TextView latestStatusText = (TextView) line.findViewById(R.id.status_text);
|
||||
latestStatusText.setText(statusLine.stringRes);
|
||||
|
||||
statusLayoutGroup.addView(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusLineOk() {
|
||||
latestStatusIndicator.setDisplayedChild(Status.OK);
|
||||
latestStatusIndicator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusLineError() {
|
||||
latestStatusIndicator.setDisplayedChild(Status.ERROR);
|
||||
latestStatusIndicator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetStatusLines() {
|
||||
latestStatusIndicator = null;
|
||||
statusLayoutGroup.removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionImport() {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_import);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionViewKey() {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_ok);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionRetryOrFromFile() {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_not_found);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionLocked(int attemptsLeft) {
|
||||
if (attemptsLeft > 0) {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_locked);
|
||||
|
||||
String unlockAttemptsText = getResources().getQuantityString(
|
||||
R.plurals.token_unlock_attempts, attemptsLeft, attemptsLeft);
|
||||
unlockSubtitle.setText(unlockAttemptsText);
|
||||
} else {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_locked_hard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionEmptyToken() {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_empty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideAction() {
|
||||
actionAnimator.setDisplayedChild(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationImportKey(byte[] importKeyData) {
|
||||
if (currentImportKeyringParcel != null) {
|
||||
throw new IllegalStateException("Cannot trigger import operation twice!");
|
||||
}
|
||||
|
||||
currentImportKeyringParcel =
|
||||
ImportKeyringParcel.createImportKeyringParcel(ParcelableKeyRing.createFromEncodedBytes(importKeyData));
|
||||
cryptoImportOperationHelper.setOperationMinimumDelay(1000L);
|
||||
cryptoImportOperationHelper.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationPromote(long masterKeyId, byte[] cardAid, List<byte[]> fingerprints) {
|
||||
if (currentImportKeyringParcel != null) {
|
||||
throw new IllegalStateException("Cannot trigger import operation twice!");
|
||||
}
|
||||
|
||||
currentPromoteKeyringParcel = PromoteKeyringParcel.createPromoteKeyringParcel(
|
||||
masterKeyId, cardAid, fingerprints);
|
||||
cryptoPromoteOperationHelper.setOperationMinimumDelay(1000L);
|
||||
cryptoPromoteOperationHelper.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationResetSecurityToken() {
|
||||
Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class);
|
||||
RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset();
|
||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
|
||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, CryptoInputParcel.createCryptoInputParcel());
|
||||
startActivityForResult(intent, REQUEST_CODE_RESET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operationChangePinSecurityToken(String adminPin, String newPin) {
|
||||
Intent intent = new Intent(getActivity(), SecurityTokenChangePinOperationActivity.class);
|
||||
SecurityTokenChangePinParcel changePinParcel =
|
||||
SecurityTokenChangePinParcel.createSecurityTokenUnlock(adminPin, newPin);
|
||||
intent.putExtra(SecurityTokenChangePinOperationActivity.EXTRA_CHANGE_PIN_PARCEL, changePinParcel);
|
||||
startActivityForResult(intent, REQUEST_CODE_CHANGE_PIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFileSelectDialog() {
|
||||
FileHelper.openDocument(this, null, "*/*", false, REQUEST_CODE_OPEN_FILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showConfirmResetDialog() {
|
||||
new Builder(ThemeChanger.getDialogThemeWrapper(getContext()))
|
||||
.setTitle(R.string.token_reset_confirm_title)
|
||||
.setMessage(R.string.token_reset_confirm_message)
|
||||
.setNegativeButton(R.string.button_cancel, null)
|
||||
.setPositiveButton(R.string.token_reset_confirm_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
presenter.onClickConfirmReset();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showAdminPinDialog() {
|
||||
AlertDialog adminPinDialog = ChangePinDialogHelper.createAdminPinDialog(getContext(), presenter);
|
||||
|
||||
adminPinDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCreateKeyForToken(SecurityTokenInfo tokenInfo) {
|
||||
CreateKeyActivity activity = (CreateKeyActivity) getActivity();
|
||||
activity.startCreateKeyForSecurityToken(tokenInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showErrorCannotUnlock() {
|
||||
Notify.create(getActivity(), R.string.token_error_locked_indefinitely, Style.ERROR).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDisplayLogActivity(OperationResult result) {
|
||||
Intent intent = new Intent(getActivity(), LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public void requestStoragePermission() {
|
||||
requestPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, PERMISSION_READ_STORAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
if (requestCode != PERMISSION_READ_STORAGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
if (permissionWasGranted) {
|
||||
presenter.onStoragePermissionGranted();
|
||||
} else {
|
||||
presenter.onStoragePermissionDenied();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_OPEN_FILE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
|
||||
Uri fileUri = data.getData();
|
||||
presenter.onFileSelected(fileUri);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQUEST_CODE_RESET: {
|
||||
SecurityTokenInfo tokenInfo = data == null ? null :
|
||||
data.<SecurityTokenInfo>getParcelableExtra(SecurityTokenOperationActivity.RESULT_TOKEN_INFO);
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
presenter.onSecurityTokenResetSuccess(tokenInfo);
|
||||
} else {
|
||||
presenter.onSecurityTokenResetCanceled(tokenInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQUEST_CODE_CHANGE_PIN: {
|
||||
SecurityTokenInfo tokenInfo = data == null ? null :
|
||||
data.<SecurityTokenInfo>getParcelableExtra(SecurityTokenOperationActivity.RESULT_TOKEN_INFO);
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
presenter.onSecurityTokenChangePinSuccess(tokenInfo);
|
||||
} else {
|
||||
presenter.onSecurityTokenChangePinCanceled(tokenInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.button_import: {
|
||||
presenter.onClickImport();
|
||||
break;
|
||||
}
|
||||
case R.id.button_retry: {
|
||||
presenter.onClickRetry();
|
||||
break;
|
||||
}
|
||||
case R.id.button_view_key: {
|
||||
presenter.onClickViewKey();
|
||||
break;
|
||||
}
|
||||
case R.id.button_load_file: {
|
||||
presenter.onClickLoadFile();
|
||||
break;
|
||||
}
|
||||
case R.id.button_reset_token_1:
|
||||
case R.id.button_reset_token_2:
|
||||
case R.id.button_reset_token_3:
|
||||
case R.id.button_reset_token_4:
|
||||
case R.id.button_reset_token_5: {
|
||||
presenter.onClickResetToken();
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.button_unlock: {
|
||||
presenter.onClickUnlockToken();
|
||||
break;
|
||||
}
|
||||
case R.id.button_unlock_impossible: {
|
||||
presenter.onClickUnlockTokenImpossible();
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.button_setup: {
|
||||
presenter.onClickSetupToken();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> cryptoImportOperationHelper =
|
||||
new CryptoOperationHelper<>(0, this, new AbstractCallback<ImportKeyringParcel, ImportKeyResult>() {
|
||||
@Override
|
||||
public ImportKeyringParcel createOperationInput() {
|
||||
return currentImportKeyringParcel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(ImportKeyResult result) {
|
||||
currentImportKeyringParcel = null;
|
||||
presenter.onImportSuccess(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(ImportKeyResult result) {
|
||||
currentImportKeyringParcel = null;
|
||||
presenter.onImportError(result);
|
||||
}
|
||||
}, null);
|
||||
|
||||
CryptoOperationHelper<PromoteKeyringParcel, PromoteKeyResult> cryptoPromoteOperationHelper =
|
||||
new CryptoOperationHelper<>(1, this, new AbstractCallback<PromoteKeyringParcel, PromoteKeyResult>() {
|
||||
@Override
|
||||
public PromoteKeyringParcel createOperationInput() {
|
||||
return currentPromoteKeyringParcel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(PromoteKeyResult result) {
|
||||
currentPromoteKeyringParcel = null;
|
||||
presenter.onPromoteSuccess(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(PromoteKeyResult result) {
|
||||
currentPromoteKeyringParcel = null;
|
||||
presenter.onPromoteError(result);
|
||||
}
|
||||
}, null);
|
||||
|
||||
enum StatusLine {
|
||||
CHECK_KEY (R.string.status_check_key),
|
||||
SEARCH_LOCAL (R.string.status_search_local),
|
||||
SEARCH_URI (R.string.status_search_uri),
|
||||
SEARCH_KEYSERVER (R.string.status_search_keyserver),
|
||||
IMPORT (R.string.status_import),
|
||||
TOKEN_PROMOTE(R.string.status_token_promote),
|
||||
TOKEN_CHECK (R.string.status_token_check),
|
||||
SEARCH_CONTENT_URI (R.string.status_content_uri);
|
||||
|
||||
@StringRes
|
||||
private int stringRes;
|
||||
|
||||
StatusLine(@StringRes int stringRes) {
|
||||
this.stringRes = stringRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.ui.token;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.GenericOperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpView;
|
||||
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment.StatusLine;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.ContentUriRetrievalLoader;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyserverRetrievalLoader;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.LocalKeyLookupLoader;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.UriKeyRetrievalLoader;
|
||||
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
|
||||
|
||||
|
||||
class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
|
||||
private static final int LOADER_LOCAL = 0;
|
||||
private static final int LOADER_URI = 1;
|
||||
private static final int LOADER_KEYSERVER = 2;
|
||||
private static final int LOADER_CONTENT_URI = 3;
|
||||
private static final String ARG_CONTENT_URI = "content_uri";
|
||||
|
||||
|
||||
private final Context context;
|
||||
private final LoaderManager loaderManager;
|
||||
|
||||
private SecurityTokenInfo tokenInfo;
|
||||
|
||||
|
||||
private ManageSecurityTokenMvpView view;
|
||||
|
||||
private boolean checkedKeyStatus;
|
||||
private boolean searchedLocally;
|
||||
private boolean searchedAtUri;
|
||||
private boolean searchedKeyservers;
|
||||
|
||||
private byte[] importKeyData;
|
||||
private Long masterKeyId;
|
||||
|
||||
private OperationLog log;
|
||||
private Uri selectedContentUri;
|
||||
|
||||
ManageSecurityTokenPresenter(Context context, LoaderManager loaderManager, SecurityTokenInfo tokenInfo) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.loaderManager = loaderManager;
|
||||
this.tokenInfo = tokenInfo;
|
||||
|
||||
this.log = new OperationLog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setView(ManageSecurityTokenMvpView view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated() {
|
||||
if (!checkedKeyStatus || !searchedLocally || !searchedAtUri || !searchedKeyservers) {
|
||||
continueSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private void continueSearchAfterError() {
|
||||
view.statusLineError();
|
||||
continueSearch();
|
||||
}
|
||||
|
||||
private void resetAndContinueSearch() {
|
||||
checkedKeyStatus = false;
|
||||
searchedLocally = false;
|
||||
searchedAtUri = false;
|
||||
searchedKeyservers = false;
|
||||
|
||||
view.hideAction();
|
||||
view.resetStatusLines();
|
||||
continueSearch();
|
||||
}
|
||||
|
||||
private void continueSearch() {
|
||||
if (!checkedKeyStatus) {
|
||||
boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0;
|
||||
boolean keyIsEmpty = tokenInfo.isEmpty();
|
||||
if (keyIsLocked || keyIsEmpty) {
|
||||
// the "checking key status" is fake: we only do it if we already know the key is locked
|
||||
view.statusLineAdd(StatusLine.CHECK_KEY);
|
||||
delayPerformKeyCheck();
|
||||
return;
|
||||
} else {
|
||||
checkedKeyStatus = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchedLocally) {
|
||||
view.statusLineAdd(StatusLine.SEARCH_LOCAL);
|
||||
loaderManager.restartLoader(LOADER_LOCAL, null, loaderCallbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!searchedAtUri) {
|
||||
view.statusLineAdd(StatusLine.SEARCH_URI);
|
||||
loaderManager.restartLoader(LOADER_URI, null, loaderCallbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!searchedKeyservers) {
|
||||
view.statusLineAdd(StatusLine.SEARCH_KEYSERVER);
|
||||
loaderManager.restartLoader(LOADER_KEYSERVER, null, loaderCallbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
view.showActionRetryOrFromFile();
|
||||
}
|
||||
|
||||
private void delayPerformKeyCheck() {
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performKeyCheck();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private void performKeyCheck() {
|
||||
boolean keyIsEmpty = tokenInfo.isEmpty();
|
||||
if (keyIsEmpty) {
|
||||
view.statusLineOk();
|
||||
|
||||
view.showActionEmptyToken();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0;
|
||||
if (keyIsLocked) {
|
||||
view.statusLineError();
|
||||
|
||||
int unlockAttemptsLeft = tokenInfo.getVerifyAdminRetries();
|
||||
view.showActionLocked(unlockAttemptsLeft);
|
||||
return;
|
||||
}
|
||||
|
||||
view.statusLineOk();
|
||||
|
||||
checkedKeyStatus = true;
|
||||
continueSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickUnlockToken() {
|
||||
view.showAdminPinDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuClickChangePin() {
|
||||
if (!checkedKeyStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenInfo.getVerifyAdminRetries() == 0) {
|
||||
view.showErrorCannotUnlock();
|
||||
return;
|
||||
}
|
||||
|
||||
view.showAdminPinDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputAdminPin(String adminPin, String newPin) {
|
||||
view.operationChangePinSecurityToken(adminPin, newPin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickUnlockTokenImpossible() {
|
||||
view.showErrorCannotUnlock();
|
||||
}
|
||||
|
||||
private LoaderCallbacks<KeyRetrievalResult> loaderCallbacks = new LoaderCallbacks<KeyRetrievalResult>() {
|
||||
@Override
|
||||
public Loader<KeyRetrievalResult> onCreateLoader(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case LOADER_LOCAL:
|
||||
return new LocalKeyLookupLoader(context, tokenInfo.getFingerprints());
|
||||
case LOADER_URI:
|
||||
return new UriKeyRetrievalLoader(context, tokenInfo.getUrl(), tokenInfo.getFingerprints());
|
||||
case LOADER_KEYSERVER:
|
||||
return new KeyserverRetrievalLoader(context, tokenInfo.getFingerprints());
|
||||
case LOADER_CONTENT_URI:
|
||||
return new ContentUriRetrievalLoader(context, tokenInfo.getFingerprints(),
|
||||
args.<Uri>getParcelable(ARG_CONTENT_URI));
|
||||
}
|
||||
throw new IllegalArgumentException("called with unknown loader id!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<KeyRetrievalResult> loader, KeyRetrievalResult data) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (loader.getId()) {
|
||||
case LOADER_LOCAL: {
|
||||
searchedLocally = true;
|
||||
break;
|
||||
}
|
||||
case LOADER_URI: {
|
||||
searchedAtUri = true;
|
||||
break;
|
||||
}
|
||||
case LOADER_KEYSERVER: {
|
||||
searchedKeyservers = true;
|
||||
break;
|
||||
}
|
||||
case LOADER_CONTENT_URI: {
|
||||
// nothing to do here
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("called with unknown loader id!");
|
||||
}
|
||||
}
|
||||
|
||||
log.add(data.getOperationResult(), 0);
|
||||
|
||||
if (data.isSuccess()) {
|
||||
processResult(data);
|
||||
} else {
|
||||
continueSearchAfterError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<KeyRetrievalResult> loader) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private void processResult(KeyRetrievalResult result) {
|
||||
view.statusLineOk();
|
||||
|
||||
byte[] importKeyData = result.getKeyData();
|
||||
Long masterKeyId = result.getMasterKeyId();
|
||||
if (importKeyData != null && masterKeyId != null) {
|
||||
view.showActionImport();
|
||||
this.importKeyData = importKeyData;
|
||||
this.masterKeyId = masterKeyId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (masterKeyId != null) {
|
||||
this.masterKeyId = masterKeyId;
|
||||
view.statusLineAdd(StatusLine.TOKEN_CHECK);
|
||||
|
||||
promoteKeyWithTokenInfo(masterKeyId);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Method can only be called with successful result!");
|
||||
}
|
||||
|
||||
private void promoteKeyWithTokenInfo(Long masterKeyId) {
|
||||
view.operationPromote(masterKeyId, tokenInfo.getAid(), tokenInfo.getFingerprints());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickImport() {
|
||||
view.statusLineAdd(StatusLine.IMPORT);
|
||||
view.hideAction();
|
||||
view.operationImportKey(importKeyData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImportSuccess(OperationResult result) {
|
||||
log.add(result, 0);
|
||||
|
||||
view.statusLineOk();
|
||||
view.statusLineAdd(StatusLine.TOKEN_PROMOTE);
|
||||
promoteKeyWithTokenInfo(masterKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImportError(OperationResult result) {
|
||||
log.add(result, 0);
|
||||
|
||||
view.statusLineError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPromoteSuccess(OperationResult result) {
|
||||
log.add(result, 0);
|
||||
|
||||
view.statusLineOk();
|
||||
view.showActionViewKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPromoteError(OperationResult result) {
|
||||
log.add(result, 0);
|
||||
|
||||
view.statusLineError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickRetry() {
|
||||
resetAndContinueSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickViewKey() {
|
||||
view.finishAndShowKey(masterKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickResetToken() {
|
||||
view.showConfirmResetDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickConfirmReset() {
|
||||
view.operationResetSecurityToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo) {
|
||||
this.tokenInfo = tokenInfo;
|
||||
resetAndContinueSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo) {
|
||||
if (tokenInfo != null) {
|
||||
this.tokenInfo = tokenInfo;
|
||||
resetAndContinueSearch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickSetupToken() {
|
||||
view.startCreateKeyForToken(tokenInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) {
|
||||
this.tokenInfo = tokenInfo;
|
||||
resetAndContinueSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo) {
|
||||
if (tokenInfo != null) {
|
||||
this.tokenInfo = tokenInfo;
|
||||
resetAndContinueSearch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickLoadFile() {
|
||||
view.showFileSelectDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileSelected(Uri contentUri) {
|
||||
boolean hasReadPermission = PermissionsUtil.checkReadPermission(context, contentUri);
|
||||
if (!hasReadPermission) {
|
||||
selectedContentUri = contentUri;
|
||||
view.requestStoragePermission();
|
||||
return;
|
||||
}
|
||||
|
||||
startLoadingFile(contentUri);
|
||||
}
|
||||
|
||||
private void startLoadingFile(Uri contentUri) {
|
||||
view.resetStatusLines();
|
||||
view.statusLineAdd(StatusLine.SEARCH_CONTENT_URI);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_CONTENT_URI, contentUri);
|
||||
loaderManager.restartLoader(LOADER_CONTENT_URI, args, loaderCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoragePermissionGranted() {
|
||||
Uri contentUri = selectedContentUri;
|
||||
selectedContentUri = null;
|
||||
startLoadingFile(contentUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoragePermissionDenied() {
|
||||
selectedContentUri = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuClickViewLog() {
|
||||
OperationResult result = new GenericOperationResult(GenericOperationResult.RESULT_OK, log);
|
||||
view.showDisplayLogActivity(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.ui.token;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request.Builder;
|
||||
import okhttp3.Response;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryFailedException;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryNotFoundException;
|
||||
import org.sufficientlysecure.keychain.network.OkHttpClientFactory;
|
||||
import org.sufficientlysecure.keychain.operations.results.GenericOperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
public abstract class PublicKeyRetrievalLoader extends AsyncTaskLoader<KeyRetrievalResult> {
|
||||
private static final long MIN_OPERATION_TIME_MILLIS = 1500;
|
||||
|
||||
|
||||
private KeyRetrievalResult cachedResult;
|
||||
|
||||
protected final List<byte[]> fingerprints;
|
||||
|
||||
|
||||
private PublicKeyRetrievalLoader(Context context, List<byte[]> fingerprints) {
|
||||
super(context);
|
||||
|
||||
this.fingerprints = fingerprints;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyRetrievalResult onLoadInBackground() {
|
||||
long startTime = SystemClock.elapsedRealtime();
|
||||
|
||||
KeyRetrievalResult keyRetrievalResult = super.onLoadInBackground();
|
||||
|
||||
try {
|
||||
long elapsedTime = SystemClock.elapsedRealtime() - startTime;
|
||||
if (elapsedTime < MIN_OPERATION_TIME_MILLIS) {
|
||||
Thread.sleep(MIN_OPERATION_TIME_MILLIS - elapsedTime);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// nvm
|
||||
}
|
||||
|
||||
return keyRetrievalResult;
|
||||
}
|
||||
|
||||
static class LocalKeyLookupLoader extends PublicKeyRetrievalLoader {
|
||||
private final KeyRepository keyRepository;
|
||||
|
||||
LocalKeyLookupLoader(Context context, List<byte[]> fingerprints) {
|
||||
super(context, fingerprints);
|
||||
|
||||
this.keyRepository = KeyRepository.createDatabaseInteractor(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyRetrievalResult loadInBackground() {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_RET_LOCAL_START, 0);
|
||||
|
||||
for (byte[] fingerprint : fingerprints) {
|
||||
long keyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprint);
|
||||
if (keyId == 0L) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_RET_LOCAL_SEARCH, 1, KeyFormattingUtils.convertKeyIdToHex(keyId));
|
||||
try {
|
||||
CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId)
|
||||
);
|
||||
|
||||
long masterKeyId = cachedPublicKeyRing.getMasterKeyId();
|
||||
// TODO check fingerprint
|
||||
// if (!Arrays.equals(fingerprints, cachedPublicKeyRing.getFingerprint())) {
|
||||
// log.add(LogType.MSG_RET_LOCAL_FP_MISMATCH, 1);
|
||||
// return KeyRetrievalResult.createWithError(log);
|
||||
// } else {
|
||||
// log.add(LogType.MSG_RET_LOCAL_FP_MATCH, 1);
|
||||
// }
|
||||
|
||||
switch (cachedPublicKeyRing.getSecretKeyType(keyId)) {
|
||||
case PASSPHRASE:
|
||||
case PASSPHRASE_EMPTY: {
|
||||
log.add(LogType.MSG_RET_LOCAL_SECRET, 1);
|
||||
log.add(LogType.MSG_RET_LOCAL_OK, 1);
|
||||
return KeyRetrievalResult.createWithMasterKeyIdAndSecretAvailable(log, masterKeyId);
|
||||
}
|
||||
|
||||
case GNU_DUMMY:
|
||||
case DIVERT_TO_CARD:
|
||||
case UNAVAILABLE: {
|
||||
log.add(LogType.MSG_RET_LOCAL_OK, 1);
|
||||
return KeyRetrievalResult.createWithMasterKeyId(log, masterKeyId);
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new IllegalStateException("Unhandled SecretKeyType!");
|
||||
}
|
||||
}
|
||||
} catch (PgpKeyNotFoundException | NotFoundException e) {
|
||||
log.add(LogType.MSG_RET_LOCAL_NOT_FOUND, 2);
|
||||
}
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_RET_LOCAL_NONE_FOUND, 1);
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
}
|
||||
|
||||
static class UriKeyRetrievalLoader extends PublicKeyRetrievalLoader {
|
||||
private final String tokenUri;
|
||||
|
||||
UriKeyRetrievalLoader(Context context, String tokenUri, List<byte[]> fingerprints) {
|
||||
super(context, fingerprints);
|
||||
|
||||
this.tokenUri = tokenUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyRetrievalResult loadInBackground() {
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_RET_URI_START, 0);
|
||||
if (TextUtils.isEmpty(tokenUri)) {
|
||||
log.add(LogType.MSG_RET_URI_NULL, 1);
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_RET_URI_FETCHING, 1, tokenUri);
|
||||
|
||||
HttpUrl httpUrl = HttpUrl.parse(tokenUri);
|
||||
if (httpUrl == null) {
|
||||
log.add(LogType.MSG_RET_URI_ERROR_PARSE, 1);
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
|
||||
Call call = OkHttpClientFactory.getSimpleClient().newCall(new Builder().url(httpUrl).build());
|
||||
Response execute = call.execute();
|
||||
if (!execute.isSuccessful()) {
|
||||
log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1);
|
||||
}
|
||||
|
||||
IteratorWithIOThrow<UncachedKeyRing> uncachedKeyRingIterator = UncachedKeyRing.fromStream(
|
||||
execute.body().byteStream());
|
||||
while (uncachedKeyRingIterator.hasNext()) {
|
||||
UncachedKeyRing keyRing = uncachedKeyRingIterator.next();
|
||||
log.add(LogType.MSG_RET_URI_TEST, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId()));
|
||||
if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) {
|
||||
log.add(LogType.MSG_RET_URI_OK, 1);
|
||||
return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded());
|
||||
}
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_RET_URI_ERROR_NO_MATCH, 1);
|
||||
} catch (IOException e) {
|
||||
log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1);
|
||||
Log.e(Constants.TAG, "error retrieving key from uri", e);
|
||||
}
|
||||
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
}
|
||||
|
||||
static class KeyserverRetrievalLoader extends PublicKeyRetrievalLoader {
|
||||
KeyserverRetrievalLoader(Context context, List<byte[]> fingerprints) {
|
||||
super(context, fingerprints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyRetrievalResult loadInBackground() {
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(getContext()).getPreferredKeyserver();
|
||||
ParcelableProxy parcelableProxy = Preferences.getPreferences(getContext()).getParcelableProxy();
|
||||
|
||||
HkpKeyserverClient keyserverClient = HkpKeyserverClient.fromHkpKeyserverAddress(preferredKeyserver);
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_RET_KS_START, 0);
|
||||
|
||||
String keyString = keyserverClient.get(
|
||||
"0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprints.get(0)), parcelableProxy);
|
||||
UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(keyString.getBytes());
|
||||
|
||||
if (!keyRing.containsKeyWithAnyFingerprint(fingerprints)) {
|
||||
log.add(LogType.MSG_RET_KS_FP_MISMATCH, 1);
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
} else {
|
||||
log.add(LogType.MSG_RET_KS_FP_MATCH, 1);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_RET_KS_OK, 1);
|
||||
return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded());
|
||||
} catch (QueryNotFoundException e) {
|
||||
log.add(LogType.MSG_RET_KS_ERROR_NOT_FOUND, 1);
|
||||
} catch (QueryFailedException | IOException | PgpGeneralException e) {
|
||||
log.add(LogType.MSG_RET_KS_ERROR, 1);
|
||||
Log.e(Constants.TAG, "error retrieving key from keyserver", e);
|
||||
}
|
||||
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
}
|
||||
|
||||
static class ContentUriRetrievalLoader extends PublicKeyRetrievalLoader {
|
||||
private final ContentResolver contentResolver;
|
||||
private final Uri uri;
|
||||
|
||||
ContentUriRetrievalLoader(Context context, List<byte[]> fingerprints, Uri uri) {
|
||||
super(context, fingerprints);
|
||||
|
||||
this.uri = uri;
|
||||
this.contentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyRetrievalResult loadInBackground() {
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_RET_CURI_START, 0);
|
||||
|
||||
log.add(LogType.MSG_RET_CURI_OPEN, 1, uri.toString());
|
||||
InputStream is = contentResolver.openInputStream(uri);
|
||||
if (is == null) {
|
||||
log.add(LogType.MSG_RET_CURI_ERROR_NOT_FOUND, 1);
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
|
||||
IteratorWithIOThrow<UncachedKeyRing> uncachedKeyRingIterator = UncachedKeyRing.fromStream(is);
|
||||
while (uncachedKeyRingIterator.hasNext()) {
|
||||
UncachedKeyRing keyRing = uncachedKeyRingIterator.next();
|
||||
log.add(LogType.MSG_RET_CURI_FOUND, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId()));
|
||||
if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) {
|
||||
log.add(LogType.MSG_RET_CURI_OK, 1);
|
||||
return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded());
|
||||
} else {
|
||||
log.add(LogType.MSG_RET_CURI_MISMATCH, 1);
|
||||
}
|
||||
}
|
||||
log.add(LogType.MSG_RET_CURI_ERROR_NO_MATCH, 1);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error reading keyring from file", e);
|
||||
log.add(LogType.MSG_RET_CURI_ERROR_IO, 1);
|
||||
}
|
||||
|
||||
return KeyRetrievalResult.createWithError(log);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(KeyRetrievalResult result) {
|
||||
cachedResult = result;
|
||||
|
||||
if (isStarted()) {
|
||||
super.deliverResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (cachedResult != null) {
|
||||
deliverResult(cachedResult);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || cachedResult == null) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
static abstract class KeyRetrievalResult {
|
||||
abstract GenericOperationResult getOperationResult();
|
||||
|
||||
@Nullable
|
||||
abstract Long getMasterKeyId();
|
||||
@Nullable
|
||||
abstract byte[] getKeyData();
|
||||
abstract boolean isSecretKeyAvailable();
|
||||
|
||||
boolean isSuccess() {
|
||||
return getMasterKeyId() != null || getKeyData() != null;
|
||||
}
|
||||
|
||||
static KeyRetrievalResult createWithError(OperationLog log) {
|
||||
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(
|
||||
new GenericOperationResult(GenericOperationResult.RESULT_ERROR, log),
|
||||
null, null, false);
|
||||
}
|
||||
|
||||
static KeyRetrievalResult createWithKeyringdata(OperationLog log, long masterKeyId, byte[] keyringData) {
|
||||
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(
|
||||
new GenericOperationResult(GenericOperationResult.RESULT_OK, log),
|
||||
masterKeyId, keyringData, false);
|
||||
}
|
||||
|
||||
static KeyRetrievalResult createWithMasterKeyIdAndSecretAvailable(OperationLog log, long masterKeyId) {
|
||||
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(
|
||||
new GenericOperationResult(GenericOperationResult.RESULT_OK, log),
|
||||
masterKeyId, null, true);
|
||||
}
|
||||
|
||||
static KeyRetrievalResult createWithMasterKeyId(OperationLog log, long masterKeyId) {
|
||||
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(
|
||||
new GenericOperationResult(GenericOperationResult.RESULT_OK, log),
|
||||
masterKeyId, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public class PermissionsUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean checkReadPermission(Context context, Uri uri) {
|
||||
public static boolean checkReadPermission(Context context, Uri uri) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_bomb_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_bomb_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 616 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_bomb_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_bomb_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 412 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_bomb_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_bomb_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 751 B |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_bomb_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/ic_bomb_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxxhdpi/ic_bomb_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xxxhdpi/ic_bomb_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
31
OpenKeychain/src/main/res/layout/admin_pin_dialog.xml
Normal file
31
OpenKeychain/src/main/res/layout/admin_pin_dialog.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:showIn="@layout/fake_dialog">
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/token_hint_admin_pin"
|
||||
android:id="@+id/admin_pin_current"
|
||||
android:inputType="numberPassword|textVisiblePassword" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/token_hint_new_pin"
|
||||
android:id="@+id/pin_new"
|
||||
android:inputType="numberPassword|textVisiblePassword" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/token_hint_new_pin_repeat"
|
||||
android:id="@+id/pin_new_repeat"
|
||||
android:inputType="numberPassword|textVisiblePassword" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,377 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/yubi_icon" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/token_status_title"
|
||||
style="?android:textAppearanceMedium"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:id="@+id/status_indicator_layout">
|
||||
|
||||
<!--
|
||||
<include layout="@layout/status_indicator_line" />
|
||||
-->
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/action_animator"
|
||||
android:layout_marginRight="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:inAnimation="@anim/fade_in_delayed"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
android:clipChildren="false"
|
||||
custom:initialView="06">
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_not_found">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/token_result_not_found"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_retry"
|
||||
android:drawableLeft="@drawable/ic_repeat_grey_24dp"
|
||||
android:drawableStart="@drawable/ic_repeat_grey_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/card_view_button"
|
||||
android:text="@string/token_action_retry"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_load_file"
|
||||
android:drawableLeft="@drawable/ic_folder_grey_24dp"
|
||||
android:drawableStart="@drawable/ic_folder_grey_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/card_view_button"
|
||||
android:text="@string/token_action_load_from_file"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_reset_token_1"
|
||||
android:drawableLeft="@drawable/ic_bomb_24dp"
|
||||
android:drawableStart="@drawable/ic_bomb_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/android_red_dark"
|
||||
android:text="@string/token_action_reset"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_import">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/token_result_key_found"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_import"
|
||||
android:drawableLeft="@drawable/ic_key_plus_grey600_24dp"
|
||||
android:drawableStart="@drawable/ic_key_plus_grey600_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/card_view_button"
|
||||
android:text="@string/token_action_import"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_reset_token_2"
|
||||
android:drawableLeft="@drawable/ic_bomb_24dp"
|
||||
android:drawableStart="@drawable/ic_bomb_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/android_red_dark"
|
||||
android:text="@string/token_action_reset"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_ok">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/token_result_token_ok"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_view_key"
|
||||
android:drawableLeft="@drawable/ic_vpn_key_grey_24dp"
|
||||
android:drawableStart="@drawable/ic_vpn_key_grey_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/card_view_button"
|
||||
android:text="@string/token_action_view_key"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_reset_token_3"
|
||||
android:drawableLeft="@drawable/ic_bomb_24dp"
|
||||
android:drawableStart="@drawable/ic_bomb_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/android_red_dark"
|
||||
android:text="@string/token_action_reset"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_locked">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Token is locked!"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:id="@+id/button_unlock"
|
||||
android:background="?selectableItemBackground"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:id="@+id/button_unlock_drawable"
|
||||
android:src="@drawable/ic_vpn_key_grey_24dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/button_unlock_drawable"
|
||||
android:layout_toRightOf="@+id/button_unlock_drawable"
|
||||
android:id="@+id/button_unlock_title"
|
||||
android:textColor="@color/card_view_button"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/token_action_unlock"
|
||||
android:padding="0dp"
|
||||
android:minHeight="0dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/button_unlock_drawable"
|
||||
android:layout_toRightOf="@+id/button_unlock_drawable"
|
||||
android:layout_below="@+id/button_unlock_title"
|
||||
android:id="@+id/button_unlock_subtitle"
|
||||
tools:text="3 attempts left"
|
||||
style="?android:textAppearanceSmall"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_reset_token_4"
|
||||
android:drawableLeft="@drawable/ic_bomb_24dp"
|
||||
android:drawableStart="@drawable/ic_bomb_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/android_red_dark"
|
||||
android:text="@string/token_action_reset"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_locked_hard">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Token is locked!"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:id="@+id/button_unlock_impossible"
|
||||
android:background="?selectableItemBackground"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:tint="@color/md_grey_400"
|
||||
android:id="@+id/button_unlock_drawable_2"
|
||||
android:src="@drawable/ic_vpn_key_grey_24dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/button_unlock_drawable_2"
|
||||
android:layout_toRightOf="@+id/button_unlock_drawable_2"
|
||||
android:id="@+id/button_unlock_title_2"
|
||||
android:textColor="@color/md_grey_400"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/token_action_unlock"
|
||||
android:padding="0dp"
|
||||
android:minHeight="0dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/button_unlock_drawable_2"
|
||||
android:layout_toRightOf="@+id/button_unlock_drawable_2"
|
||||
android:layout_below="@+id/button_unlock_title_2"
|
||||
android:textColor="@color/md_grey_400"
|
||||
android:text="@string/token_unlock_attempts_none"
|
||||
style="?android:textAppearanceSmall"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_reset_token_5"
|
||||
android:drawableLeft="@drawable/ic_bomb_24dp"
|
||||
android:drawableStart="@drawable/ic_bomb_24dp"
|
||||
android:drawablePadding="12dp"
|
||||
android:textColor="@color/android_red_dark"
|
||||
android:text="@string/token_action_reset"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_empty">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/token_result_empty"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/button_setup"
|
||||
android:drawableLeft="@drawable/ic_person_add_grey_24dp"
|
||||
android:drawableStart="@drawable/ic_person_add_grey_24dp"
|
||||
android:drawableTint="@color/md_black_1000"
|
||||
android:drawablePadding="12dp"
|
||||
android:text="@string/token_action_setup"
|
||||
style="?borderlessButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -80,14 +80,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/security_token_import_radio" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/token_decision_file"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/security_token_file_radio" />
|
||||
android:text="@string/security_token_radio_use_existing" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/token_decision_reset"
|
||||
@@ -95,18 +88,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/security_token_reset_radio" />
|
||||
android:text="@string/security_token_radio_reset" />
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@id/yubikey_decision"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/security_token_import_fragment"
|
||||
android:layout_width="fill_parent"
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:layout_above="@+id/create_key_buttons">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/first_time_blank_security_token" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:background="?attr/colorButtonRow"
|
||||
android:id="@+id/create_key_buttons">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/create_key_back_button"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/btn_back"
|
||||
android:textAllCaps="true"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="left|center_vertical"
|
||||
android:clickable="true"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/create_key_next_button"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/first_time_blank_security_token_yes"
|
||||
android:textAllCaps="true"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:drawableRight="@drawable/ic_chevron_right_grey_24dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical|right"
|
||||
android:clickable="true"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
16
OpenKeychain/src/main/res/layout/fake_dialog.xml
Normal file
16
OpenKeychain/src/main/res/layout/fake_dialog.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<include layout="@layout/admin_pin_dialog" />
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -10,7 +10,7 @@
|
||||
android:measureAllChildren="false"
|
||||
android:minHeight="?listPreferredItemHeightSmall"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
custom:initialView="3"
|
||||
custom:initialView="4"
|
||||
tools:showIn="@layout/security_token_operation_activity">
|
||||
|
||||
<LinearLayout
|
||||
@@ -126,4 +126,34 @@
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<ScrollView
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/security_token_activity_4_error_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="24dp"
|
||||
android:textAppearance="@android:style/TextAppearance.Medium"
|
||||
android:textColor="@color/android_red_dark"
|
||||
tools:text="Error text" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/security_token_activity_4_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="8dp"
|
||||
android:text="Back" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
||||
25
OpenKeychain/src/main/res/layout/status_indicator_line.xml
Normal file
25
OpenKeychain/src/main/res/layout/status_indicator_line.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/status_layout1">
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.StatusIndicator
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/status_indicator"
|
||||
android:layout_margin="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/status_text"
|
||||
android:text="Searching in key list…"
|
||||
style="?android:textAppearanceMedium"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:id="@+id/card_view"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:transitionName="card"
|
||||
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
|
||||
card_view:cardElevation="2dp"
|
||||
card_view:cardUseCompatPadding="true"
|
||||
card_view:cardCornerRadius="4dp"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/CardViewHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/section_security_token"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_margin="14dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/yubi_icon"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/token_serno"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="Yubikey #"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/token_userid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="User ID"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/token_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="Key matches!"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_bind"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:text="@string/button_bind_key"
|
||||
android:textColor="@color/card_view_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
19
OpenKeychain/src/main/res/menu/token_debug.xml
Normal file
19
OpenKeychain/src/main/res/menu/token_debug.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:title="Debug / Token from Keyserver"
|
||||
android:id="@+id/menu_token_debug_keyserver"
|
||||
/>
|
||||
<item
|
||||
android:title="Debug / Token from Uri"
|
||||
android:id="@+id/menu_token_debug_uri"
|
||||
/>
|
||||
<item
|
||||
android:title="Debug / Locked Token"
|
||||
android:id="@+id/menu_token_debug_locked"
|
||||
/>
|
||||
<item
|
||||
android:title="Debug / Hard Locked Token"
|
||||
android:id="@+id/menu_token_debug_locked_hard"
|
||||
/>
|
||||
</menu>
|
||||
14
OpenKeychain/src/main/res/menu/token_setup.xml
Normal file
14
OpenKeychain/src/main/res/menu/token_setup.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:title="@string/token_menu_view_log"
|
||||
android:id="@+id/view_log"
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
<item
|
||||
android:title="@string/token_menu_change_pin"
|
||||
android:id="@+id/change_pin"
|
||||
app:showAsAction="never"
|
||||
/>
|
||||
</menu>
|
||||
@@ -1458,9 +1458,9 @@
|
||||
<string name="security_token_create">Halte den Security-Token gegen die Rückseite deines Geräts.</string>
|
||||
<string name="security_token_reset_or_import">Dieses Sicherheits-Token enthält bereits einen Schlüssel. Um es zu benutzen, benötigen wir zusätzliche Schlüsselinformationen. Diese Informationen können auf einem Keyserver gesucht oder aus einer Datei importiert werden.</string>
|
||||
<string name="btn_reset">Zurücksetzen</string>
|
||||
<string name="security_token_import_radio">Suche Schlüsselinformation auf Schlüsselserver</string>
|
||||
<string name="security_token_radio_use_existing">Suche Schlüsselinformation auf Schlüsselserver</string>
|
||||
<string name="security_token_file_radio">Importiere Schlüsselinformation von Datei</string>
|
||||
<string name="security_token_reset_radio">Security-Token zurücksetzen</string>
|
||||
<string name="security_token_radio_reset">Security-Token zurücksetzen</string>
|
||||
<string name="security_token_reset_warning">Das Zurücksetzen des Security-Tokens zerstört die darauf gespeicherten Schlüssel vollständig. Mit diesen Schlüssel verschlüsselte Nachrichten/Dateien können danach nicht mehr entschlüsselt werden!</string>
|
||||
<string name="snack_security_token_other">Es ist ein anderer Schlüssel auf dem Security-Token gespeichert!</string>
|
||||
<string name="security_token_error">Fehler: %s</string>
|
||||
|
||||
@@ -1475,9 +1475,9 @@
|
||||
<string name="security_token_create">Sostenga el token de seguridad contra la parte trasera de su dispositivo.</string>
|
||||
<string name="security_token_reset_or_import">Este token de seguridad ya contiene una clave. Para usarla, necesitamos información adicional de la clave. Esta información se puede buscar en un servidor de claves o se puede importar de un fichero.</string>
|
||||
<string name="btn_reset">Reiniciar</string>
|
||||
<string name="security_token_import_radio">Buscar información de la clave en el servidor de claves</string>
|
||||
<string name="security_token_radio_use_existing">Buscar información de la clave en el servidor de claves</string>
|
||||
<string name="security_token_file_radio">Importar información de la clave desde un fichero</string>
|
||||
<string name="security_token_reset_radio">Reinicializar token de seguridad</string>
|
||||
<string name="security_token_radio_reset">Reinicializar token de seguridad</string>
|
||||
<string name="security_token_reset_warning">Reinicializar el token de seguridad destruirá por completo las claves que almacene. ¡Acto seguido no podrá descifrar los mensajes/ficheros cifrados con esta clave!</string>
|
||||
<string name="snack_security_token_other">¡La clave almacenada en el token de seguridad es diferente!</string>
|
||||
<string name="security_token_error">Error: %s</string>
|
||||
|
||||
@@ -1291,7 +1291,7 @@
|
||||
<string name="security_token_serial_no">Serie Zbk: %s</string>
|
||||
<string name="security_token_key_holder_not_set"><![CDATA[Key holder: <not set>]]></string>
|
||||
<string name="btn_reset">Berrezarri</string>
|
||||
<string name="security_token_reset_radio">Berrezarri Segurtasun Lekukoa</string>
|
||||
<string name="security_token_radio_reset">Berrezarri Segurtasun Lekukoa</string>
|
||||
<string name="security_token_error">Akatsa: %s</string>
|
||||
<plurals name="security_token_error_pin">
|
||||
<item quantity="one">PIN okerra!\n%d saiakera gelditzen da.</item>
|
||||
|
||||
@@ -1475,9 +1475,9 @@
|
||||
<string name="security_token_create">Tenir le jeton de sécurité contre le dos de votre appareil.</string>
|
||||
<string name="security_token_reset_or_import">Ce jeton de sécurité contient déjà une clé. Des informations supplémentaires sont requises pour l\'utiliser. Elles peuvent être recherchées sur un serveur de clés ou importer d\'un fichier.</string>
|
||||
<string name="btn_reset">Réinitialiser</string>
|
||||
<string name="security_token_import_radio">Rechercher les informations de la clé sur le serveur de clés</string>
|
||||
<string name="security_token_radio_use_existing">Rechercher les informations de la clé sur le serveur de clés</string>
|
||||
<string name="security_token_file_radio">Importer les informations de la clé d\'un fichier</string>
|
||||
<string name="security_token_reset_radio">Réinitialiser le jeton de sécurité</string>
|
||||
<string name="security_token_radio_reset">Réinitialiser le jeton de sécurité</string>
|
||||
<string name="security_token_reset_warning">La réinitialisation du jeton de sécurité détruit complètement les clés qu\'il contient. Par la suite, vous ne pourrez plus déchiffrer les messages/les fichiers chiffrés avec cette clé !</string>
|
||||
<string name="snack_security_token_other">Une clé différente est stockée sur le jeton de sécurité !</string>
|
||||
<string name="security_token_error">Erreur : %s</string>
|
||||
|
||||
@@ -1446,9 +1446,9 @@
|
||||
<string name="security_token_create">あなたのデバイスの背面にセキュリティトークンを保持してください。</string>
|
||||
<string name="security_token_reset_or_import">このセキュリティトークンにはすでに鍵が含まれています。 使用するには、追加の鍵の情報が必要です。 この情報は、鍵サーバ上で検索することや、ファイルからインポートすることができます。</string>
|
||||
<string name="btn_reset">リセット</string>
|
||||
<string name="security_token_import_radio">鍵サーバ上で鍵情報を検索</string>
|
||||
<string name="security_token_radio_use_existing">鍵サーバ上で鍵情報を検索</string>
|
||||
<string name="security_token_file_radio">ファイルから鍵情報をインポート</string>
|
||||
<string name="security_token_reset_radio">セキュリティトークンのリセット</string>
|
||||
<string name="security_token_radio_reset">セキュリティトークンのリセット</string>
|
||||
<string name="security_token_reset_warning">セキュリティトークンをリセットすると、その上の鍵を完全に破壊します。その後は、この鍵で暗号化されたメッセージ/ファイルを復号化することができなくなります!</string>
|
||||
<string name="snack_security_token_other">違う鍵がセキュリティトークンに格納されています!</string>
|
||||
<string name="security_token_error">エラー: %s</string>
|
||||
|
||||
@@ -580,7 +580,7 @@
|
||||
<string name="nfc_settings">Innstillinger</string>
|
||||
<string name="snack_security_token_view">Vis</string>
|
||||
<string name="btn_reset">Tilbakestill</string>
|
||||
<string name="security_token_reset_radio">Tilbakestill sikkerhetssymbol</string>
|
||||
<string name="security_token_radio_reset">Tilbakestill sikkerhetssymbol</string>
|
||||
<string name="security_token_error">Feil: %s</string>
|
||||
<string name="security_token_error_unknown">Ukjent feil</string>
|
||||
<string name="security_token_error_try_again">Prøv igjen</string>
|
||||
|
||||
@@ -1476,9 +1476,9 @@
|
||||
<string name="security_token_create">Hou het Security Token tegen de achterkant van je apparaat.</string>
|
||||
<string name="security_token_reset_or_import">Dit Security Token bevat al een sleutel. Om deze te gebruiken hebben we extra sleutelinformatie nodig. Deze informatie kan gevonden worden op een sleutelserver, of geïmporteerd uit een bestand.</string>
|
||||
<string name="btn_reset">Reset</string>
|
||||
<string name="security_token_import_radio">Zoek sleutelinformatie op sleutelserver</string>
|
||||
<string name="security_token_radio_use_existing">Zoek sleutelinformatie op sleutelserver</string>
|
||||
<string name="security_token_file_radio">Importeer sleutelinformatie uit bestand</string>
|
||||
<string name="security_token_reset_radio">Reset Security Token</string>
|
||||
<string name="security_token_radio_reset">Reset Security Token</string>
|
||||
<string name="security_token_reset_warning">Het opnieuw instellen van het Security Token maakt alle sleutels op het token ongedaan. Het is daarna niet meer mogelijk om berichten/bestanden te ontsleutelen met deze sleutel!</string>
|
||||
<string name="snack_security_token_other">Andere sleutel opgeslagen op Security Token!</string>
|
||||
<string name="security_token_error">Error: %s</string>
|
||||
|
||||
@@ -1390,7 +1390,7 @@
|
||||
<string name="security_token_status_partly">O Token de Segurança coincide, parcialmente associado à chave</string>
|
||||
<string name="security_token_create">Segure o Token de Segurança contra as costas do seu dispositivo.</string>
|
||||
<string name="btn_reset">Redefinir</string>
|
||||
<string name="security_token_reset_radio">Redefinir Token de Segurança</string>
|
||||
<string name="security_token_radio_reset">Redefinir Token de Segurança</string>
|
||||
<string name="security_token_reset_warning">Resetar o Token de Segurança destrói completamente as chaves nele. Após isto, você não será capaz de decriptar mensagens/arquivos encruiptados com estas chaves!</string>
|
||||
<string name="snack_security_token_other">Uma chave diferente está armazenada no Token de Segurança!</string>
|
||||
<string name="security_token_error">Erro: %s</string>
|
||||
|
||||
@@ -1544,9 +1544,9 @@
|
||||
<string name="security_token_create">Держите токен безопасности возле задней части вашего устройства.</string>
|
||||
<string name="security_token_reset_or_import">Этот токен безопасности уже содержит ключ. Чтобы его использовать, нужна дополнительная информация о ключе. Её можно найти на сервере ключей или импортировать из файла.</string>
|
||||
<string name="btn_reset">Сброс</string>
|
||||
<string name="security_token_import_radio">Найти информацию о ключе на сервере ключей</string>
|
||||
<string name="security_token_radio_use_existing">Найти информацию о ключе на сервере ключей</string>
|
||||
<string name="security_token_file_radio">Импортировать информацию о ключе из файла</string>
|
||||
<string name="security_token_reset_radio">Сбросить токен безопасности</string>
|
||||
<string name="security_token_radio_reset">Сбросить токен безопасности</string>
|
||||
<string name="security_token_reset_warning">Сброс токена безопасности полностью уничтожает ключи на нём. После этого вы не сможете расшифровать сообщения или файлы, зашифрованные с помощью данных ключей!</string>
|
||||
<string name="snack_security_token_other">На токене безопасности хранится другой ключ!</string>
|
||||
<string name="security_token_error">Ошибка: %s</string>
|
||||
|
||||
@@ -1512,9 +1512,9 @@
|
||||
<string name="security_token_create">Тримайте маркер безпеки навпроти зворотнього боку Вашого пристрою.</string>
|
||||
<string name="security_token_reset_or_import">Маркер безпеки вже містить ключ. Щоб використовувати його, нам потрібна додаткова інформація. Ця інформація може бути знайдена на сервері ключів чи імпортована з файлу.</string>
|
||||
<string name="btn_reset">Скинути</string>
|
||||
<string name="security_token_import_radio">Пошук інформації про ключ на сервері ключів</string>
|
||||
<string name="security_token_radio_use_existing">Пошук інформації про ключ на сервері ключів</string>
|
||||
<string name="security_token_file_radio">Імпорт інформації про ключ з файлу</string>
|
||||
<string name="security_token_reset_radio">Очистити маркер безпеки</string>
|
||||
<string name="security_token_radio_reset">Очистити маркер безпеки</string>
|
||||
<string name="security_token_reset_warning">Скидання маркера безпеки повністю знищить всі ключі на ньому. Ви не зможете розшифрувати повідомлення/файли зашифровані цим ключем.</string>
|
||||
<string name="snack_security_token_other">Різні ключі збережено на маркері безпеки!</string>
|
||||
<string name="security_token_error">Помилка: %s</string>
|
||||
|
||||
@@ -1359,7 +1359,7 @@
|
||||
<string name="security_token_status_partly">安全信息匹配并且已部分绑定到密钥</string>
|
||||
<string name="security_token_create">保持安全令牌在您的手机背部</string>
|
||||
<string name="btn_reset">重置</string>
|
||||
<string name="security_token_reset_radio">重置安全令牌</string>
|
||||
<string name="security_token_radio_reset">重置安全令牌</string>
|
||||
<string name="security_token_reset_warning">重置安全令牌将完全摧毁其内部的密钥。之后您将无法使用该密钥加解密消息或文件!</string>
|
||||
<string name="snack_security_token_other">安全令牌中存有不同的密钥!</string>
|
||||
<string name="security_token_error">错误: %s</string>
|
||||
|
||||
@@ -1593,11 +1593,10 @@
|
||||
<string name="security_token_status_unbound">"Security Token matches, can be bound to key"</string>
|
||||
<string name="security_token_status_partly">"Security Token matches, partly bound to key"</string>
|
||||
<string name="security_token_create">"Hold Security Token against the back of your device."</string>
|
||||
<string name="security_token_reset_or_import">"This Security Token already contains a key. To use it, we need additional key information. This information can be searched for on a keyserver or imported from a file."</string>
|
||||
<string name="security_token_reset_or_import">"This Security Token already contains a key."</string>
|
||||
<string name="btn_reset">"Reset"</string>
|
||||
<string name="security_token_import_radio">"Search key information on keyserver"</string>
|
||||
<string name="security_token_file_radio">"Import key information from file"</string>
|
||||
<string name="security_token_reset_radio">"Reset Security Token"</string>
|
||||
<string name="security_token_radio_use_existing">"Use existing key"</string>
|
||||
<string name="security_token_radio_reset">"Reset token"</string>
|
||||
<string name="security_token_reset_warning">"Resetting the Security Token completely destroys the keys on it. Afterwards, you will not be able to decrypt messages/files encrypted with this key!"</string>
|
||||
<string name="snack_security_token_other">Different key stored on Security Token!</string>
|
||||
<string name="security_token_error">"Error: %s"</string>
|
||||
@@ -1919,4 +1918,77 @@
|
||||
<string name="transfer_error_wifi_text_instructions">"Make sure you are on the same network, then scan again."</string>
|
||||
<string name="transfer_error_wifi_text_instructions_ssid">"Make sure you are on the "%s" network, then scan again."</string>
|
||||
|
||||
<string name="status_check_key">Checking key status…</string>
|
||||
<string name="status_search_local">Searching in key list…</string>
|
||||
<string name="status_search_uri">Searching at token Uri…</string>
|
||||
<string name="status_search_keyserver">Searching on keyservers…</string>
|
||||
<string name="status_import">Importing key…</string>
|
||||
<string name="status_token_promote">Setting up key…</string>
|
||||
<string name="status_token_check">Checking key setup…</string>
|
||||
<string name="status_content_uri">Reading file…</string>
|
||||
|
||||
<string name="token_reset_confirm_title">Reset Security Token?</string>
|
||||
<string name="token_reset_confirm_message">This will irrecoverably delete the key stored on this Security Token. You will no longer be able to use this key for decryption! Are you sure?</string>
|
||||
<string name="token_reset_confirm_ok">Reset</string>
|
||||
<string name="token_status_title">Gathering information for Security Token…</string>
|
||||
<string name="token_result_not_found">Key not found!</string>
|
||||
<string name="token_result_key_found">Key found!</string>
|
||||
<string name="token_result_token_ok">Ready for use!</string>
|
||||
<string name="token_result_empty">Token is empty</string>
|
||||
<string name="token_action_retry">Retry Search</string>
|
||||
<string name="token_action_load_from_file">Load from File</string>
|
||||
<string name="token_action_reset">Reset Security Token</string>
|
||||
<string name="token_action_import">Import</string>
|
||||
<string name="token_action_view_key">View Key</string>
|
||||
<string name="token_action_setup">"Set up with new key"</string>
|
||||
<string name="token_action_unlock">Unlock using admin pin</string>
|
||||
<string name="token_unlock_attempts_none">"No unlock attempts left"</string>
|
||||
<plurals name="token_unlock_attempts">
|
||||
<item quantity="one">"1 attempt left"</item>
|
||||
<item quantity="other">"%d attempts left"</item>
|
||||
</plurals>
|
||||
<string name="token_error_locked_indefinitely">Too many reset attempts. Token is locked irrecoverably!</string>
|
||||
|
||||
<string name="token_menu_change_pin">Change Pin</string>
|
||||
<string name="token_menu_view_log">View Log</string>
|
||||
|
||||
<string name="token_unlock_ok">Unlock</string>
|
||||
<string name="token_hint_admin_pin">Admin Pin</string>
|
||||
<string name="token_hint_new_pin">New Pin</string>
|
||||
<string name="token_hint_new_pin_repeat">New Pin (repeat)</string>
|
||||
<string name="token_error_admin_min8">Admin pin must be 8 characters or longer!</string>
|
||||
<string name="token_error_pin_min6">New pin must be 6 characters or longer!</string>
|
||||
<string name="token_error_pin_repeat">Doesn\'t match pin!</string>
|
||||
|
||||
<string name="msg_ret_curi_error_io">"Error reading data!"</string>
|
||||
<string name="msg_ret_curi_error_no_match">"No matching key found"</string>
|
||||
<string name="msg_ret_curi_error_not_found">"Couldn't open file!"</string>
|
||||
<string name="msg_ret_curi_found">"Found key: %s"</string>
|
||||
<string name="msg_ret_curi_mismatch">"Key doesn't match"</string>
|
||||
<string name="msg_ret_curi_ok">"Key found"</string>
|
||||
<string name="msg_ret_curi_open">"Opening Uri: %s"</string>
|
||||
<string name="msg_ret_curi_start">"Loading key from file or document…"</string>
|
||||
<string name="msg_ret_ks_error">"Unknown error searching for key!"</string>
|
||||
<string name="msg_ret_ks_error_not_found">"Key not found"</string>
|
||||
<string name="msg_ret_ks_fp_match">"Retrieved key's fingerprint matches"</string>
|
||||
<string name="msg_ret_ks_fp_mismatch">"Retrieved key's fingerprint doesn't match!"</string>
|
||||
<string name="msg_ret_ks_ok">"Key found"</string>
|
||||
<string name="msg_ret_ks_start">"Looking for key on keyservers…"</string>
|
||||
<string name="msg_ret_local_search">"Searching for key: %s"</string>
|
||||
<string name="msg_ret_local_fp_match">"Local key's fingerprint matches"</string>
|
||||
<string name="msg_ret_local_fp_mismatch">"Local key's fingerprint doesn't match!"</string>
|
||||
<string name="msg_ret_local_not_found">"Key not found"</string>
|
||||
<string name="msg_ret_local_none_found">"No matching key found"</string>
|
||||
<string name="msg_ret_local_ok">"Key found"</string>
|
||||
<string name="msg_ret_local_secret">"Local key contains secret key material"</string>
|
||||
<string name="msg_ret_local_start">"Looking for key in local key list…"</string>
|
||||
<string name="msg_ret_uri_error_fetch">"Unknown error fetching Uri!"</string>
|
||||
<string name="msg_ret_uri_error_parse">"Token Uri is malformed!"</string>
|
||||
<string name="msg_ret_uri_error_no_match">"No matching key found at Uri"</string>
|
||||
<string name="msg_ret_uri_fetching">"Fetching Uri: %s"</string>
|
||||
<string name="msg_ret_uri_ok">"Key found"</string>
|
||||
<string name="msg_ret_uri_start">"Looking for key at token Uri…"</string>
|
||||
<string name="msg_ret_uri_null">"No Uri saved on Security Token"</string>
|
||||
<string name="msg_ret_uri_test">"Checking if found key matches: %s"</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
@@ -164,9 +165,8 @@ public class PromoteKeyOperationTest {
|
||||
long keyId = KeyringTestingHelper.getSubkeyId(mStaticRing, 1);
|
||||
|
||||
PromoteKeyResult result = op.execute(
|
||||
PromoteKeyringParcel.createPromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid, new long[] {
|
||||
keyId
|
||||
}), null);
|
||||
PromoteKeyringParcel.createPromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid,
|
||||
Arrays.asList(mStaticRing.getPublicKey(keyId).getFingerprint())), null);
|
||||
|
||||
Assert.assertTrue("promotion must succeed", result.success());
|
||||
|
||||
|
||||
1
graphics/drawables/ic_bomb.svg
Normal file
1
graphics/drawables/ic_bomb.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M11.25,6A3.25,3.25 0 0,1 14.5,2.75A3.25,3.25 0 0,1 17.75,6C17.75,6.42 18.08,6.75 18.5,6.75C18.92,6.75 19.25,6.42 19.25,6V5.25H20.75V6A2.25,2.25 0 0,1 18.5,8.25A2.25,2.25 0 0,1 16.25,6A1.75,1.75 0 0,0 14.5,4.25A1.75,1.75 0 0,0 12.75,6H14V7.29C16.89,8.15 19,10.83 19,14A7,7 0 0,1 12,21A7,7 0 0,1 5,14C5,10.83 7.11,8.15 10,7.29V6H11.25M22,6H24V7H22V6M19,4V2H20V4H19M20.91,4.38L22.33,2.96L23.04,3.67L21.62,5.09L20.91,4.38Z" /></svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -22,7 +22,7 @@ SRC_DIR=./drawables/
|
||||
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
|
||||
|
||||
|
||||
for NAME in "ic_live_help" "ic_send" "ic_cloud_unknown" "ic_cloud_off" "ic_wifi_lock" "broken_heart" "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
|
||||
for NAME in "ic_bomb" "ic_live_help" "ic_send" "ic_cloud_unknown" "ic_cloud_off" "ic_wifi_lock" "broken_heart" "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
|
||||
do
|
||||
echo $NAME
|
||||
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
||||
|
||||
Reference in New Issue
Block a user