diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index d3bf9c3d5..a76982975 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -806,6 +806,13 @@ android:taskAffinity=":Nfc" android:theme="@style/Theme.Keychain.Light.Dialog" /> + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index b111efa5b..2932767b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -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 } } - 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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index 22b96db6a..80a4c5862 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -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 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 searchElements, byte[] needle) { + for (byte[] searchElement : searchElements) { + if (Arrays.equals(needle, searchElement)) { + return true; } } - return null; + return false; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GenericOperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GenericOperationResult.java new file mode 100644 index 000000000..2ae0e5479 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GenericOperationResult.java @@ -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 CREATOR = new Creator() { + public GenericOperationResult createFromParcel(final Parcel source) { + return new GenericOperationResult(source); + } + + public GenericOperationResult[] newArray(final int size) { + return new GenericOperationResult[size]; + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f844acb0d..4c3266017 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 93b819af6..05413991f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -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 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); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 850c24640..d535f2338 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -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 fromStream(final InputStream stream) { + public boolean containsKeyWithAnyFingerprint(List expectedFingerprints) { + Iterator 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 fromStream(InputStream rawStream) { + final InputStream stream = rawStream.markSupported() ? rawStream: new BufferedInputStream(rawStream); return new IteratorWithIOThrow() { @@ -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()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java index c573aba9b..2a0b3c1fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java @@ -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()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 5b687f488..9802d182b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -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(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java new file mode 100644 index 000000000..9a2cb6b37 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java @@ -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 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 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); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java index e7a0e2d51..7cf0bc2c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.service; -import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java index c62815c7b..efa835531 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java @@ -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 getFingerprints(); public static PromoteKeyringParcel createPromoteKeyringParcel(long keyRingId, byte[] cardAid, - @Nullable long[] subKeyIds) { - return new AutoValue_PromoteKeyringParcel(keyRingId, cardAid, subKeyIds); + @Nullable List fingerprints) { + return new AutoValue_PromoteKeyringParcel(keyRingId, cardAid, fingerprints); } } \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 84c139d0b..4a0fe8069 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java new file mode 100644 index 000000000..2d9bf7c85 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java @@ -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); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index e64d846f6..35b5816b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java index 1079229b4..0c3985eff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java @@ -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( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java deleted file mode 100644 index 61a069e9d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -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); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java deleted file mode 100644 index 41cf0786c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -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 - 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 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 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(); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java index f100ee8bc..4e4e955b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java @@ -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) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index 782502741..022dc0f2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java new file mode 100644 index 000000000..c38f667de --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +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() { + @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); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ceea84ee8..09dfa51cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java deleted file mode 100644 index bbea3973b..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * - * 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 . - */ - -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 - implements LoaderCallbacks { - - 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 onCreateLoader(int id, Bundle args) { - return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId), - PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader 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 loader) { - - } - - @Override - public PromoteKeyringParcel createOperationInput() { - return PromoteKeyringParcel.createPromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds); - } - - @Override - public void onQueuedOperationSuccess(PromoteKeyResult result) { - result.createNotify(getActivity()).show(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java index 55ed00358..db7153e3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -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) */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index f7316d811..b2c8fd853 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -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 { + private long operationStartTime; + public interface Callback { T createOperationInput(); @@ -67,6 +71,19 @@ public class CryptoOperationHelper + implements Callback { + @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 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 :( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 514e1231a..c60d0908e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -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, CryptoOperationHelper.Callback { - - 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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java index a14287811..a6b8a137c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java @@ -151,7 +151,8 @@ public class IdentityLoader extends AsyncTaskLoader> { 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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ChangePinDialogHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ChangePinDialogHelper.java new file mode 100644 index 000000000..0db53ca2c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ChangePinDialogHelper.java @@ -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); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java new file mode 100644 index 000000000..f5f481930 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +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 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(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java new file mode 100644 index 000000000..aff9e9810 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +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 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.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.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 cryptoImportOperationHelper = + new CryptoOperationHelper<>(0, this, new AbstractCallback() { + @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 cryptoPromoteOperationHelper = + new CryptoOperationHelper<>(1, this, new AbstractCallback() { + @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; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java new file mode 100644 index 000000000..f51fee3c5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +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 loaderCallbacks = new LoaderCallbacks() { + @Override + public Loader 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.getParcelable(ARG_CONTENT_URI)); + } + throw new IllegalArgumentException("called with unknown loader id!"); + } + + @Override + public void onLoadFinished(Loader 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 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); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java new file mode 100644 index 000000000..fb4d0d35c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +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 { + private static final long MIN_OPERATION_TIME_MILLIS = 1500; + + + private KeyRetrievalResult cachedResult; + + protected final List fingerprints; + + + private PublicKeyRetrievalLoader(Context context, List 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 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 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 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 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 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 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); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java index 435240859..0a004746f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java @@ -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; } diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_bomb_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_bomb_24dp.png new file mode 100644 index 000000000..0f05efecb Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_bomb_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_bomb_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_bomb_24dp.png new file mode 100644 index 000000000..b1e3b159f Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_bomb_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_bomb_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_bomb_24dp.png new file mode 100644 index 000000000..5eee46118 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_bomb_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_bomb_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_bomb_24dp.png new file mode 100644 index 000000000..a09970b17 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_bomb_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_bomb_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_bomb_24dp.png new file mode 100644 index 000000000..1e2a035c1 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_bomb_24dp.png differ diff --git a/OpenKeychain/src/main/res/layout/admin_pin_dialog.xml b/OpenKeychain/src/main/res/layout/admin_pin_dialog.xml new file mode 100644 index 000000000..c2909cf4c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/admin_pin_dialog.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/create_security_token_import_fragment.xml b/OpenKeychain/src/main/res/layout/create_security_token_import_fragment.xml new file mode 100644 index 000000000..9a35ad763 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/create_security_token_import_fragment.xml @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + +