From 4499caef1e64d2e1afec37d360958f516da4dd40 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 18 Mar 2015 13:45:16 +0100 Subject: [PATCH 01/80] introduce CryptoOperationParcel for nfc data --- .../service/CertifyActionsParcel.java | 11 +++- .../service/input/CryptoOperationParcel.java | 52 +++++++++++++++++++ .../keychain/ui/CertifyKeyFragment.java | 4 +- 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index f4b941109..d6da18e6e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -23,15 +23,17 @@ import android.os.Parcelable; import java.io.Serializable; import java.util.ArrayList; +import java.util.Date; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.service.input.CryptoOperationParcel; /** * This class is a a transferable representation for a number of keyrings to * be certified. */ -public class CertifyActionsParcel implements Parcelable { +public class CertifyActionsParcel extends CryptoOperationParcel { // the master key id to certify with final public long mMasterKeyId; @@ -39,12 +41,15 @@ public class CertifyActionsParcel implements Parcelable { public ArrayList mCertifyActions = new ArrayList<>(); - public CertifyActionsParcel(long masterKeyId) { + public CertifyActionsParcel(Date operationTime, long masterKeyId) { + super(operationTime); mMasterKeyId = masterKeyId; mLevel = CertifyLevel.DEFAULT; } public CertifyActionsParcel(Parcel source) { + super(source); + mMasterKeyId = source.readLong(); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; @@ -58,6 +63,8 @@ public class CertifyActionsParcel implements Parcelable { @Override public void writeToParcel(Parcel destination, int flags) { + super.writeToParcel(destination, flags); + destination.writeLong(mMasterKeyId); destination.writeInt(mLevel.ordinal()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java new file mode 100644 index 000000000..2101755ad --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java @@ -0,0 +1,52 @@ +package org.sufficientlysecure.keychain.service.input; + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; + +import android.os.Parcel; +import android.os.Parcelable; + + +/** This is a base class for the input of crypto operations. + * + */ +public abstract class CryptoOperationParcel implements Parcelable { + + Date mOperationTime; + + // this map contains both decrypted session keys and signed hashes to be + // used in the crypto operation described by this parcel. + HashMap mCryptoData; + + protected CryptoOperationParcel(Date operationTime) { + mOperationTime = operationTime; + } + + protected CryptoOperationParcel(Parcel source) { + mOperationTime = new Date(source.readLong()); + + { + int count = source.readInt(); + mCryptoData = new HashMap<>(count); + for (int i = 0; i < count; i++) { + byte[] key = source.createByteArray(); + byte[] value = source.createByteArray(); + mCryptoData.put(ByteBuffer.wrap(key), value); + } + } + + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mOperationTime.getTime()); + + dest.writeInt(mCryptoData.size()); + for (HashMap.Entry entry : mCryptoData.entrySet()) { + dest.writeByteArray(entry.getKey().array()); + dest.writeByteArray(entry.getValue()); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 9cb4e5f65..049c6976d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -66,6 +66,8 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Date; + public class CertifyKeyFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { @@ -368,7 +370,7 @@ public class CertifyKeyFragment extends LoaderFragment Bundle data = new Bundle(); { // fill values for this action - CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); + CertifyActionsParcel parcel = new CertifyActionsParcel(new Date(), mSignMasterKeyId); parcel.mCertifyActions.addAll(certifyActions); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); From aca54e31eae450e7deec54cca6654ee202c7a90f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 18 Mar 2015 18:25:44 +0100 Subject: [PATCH 02/80] generalize nfc crypto input structure --- .../keychain/pgp/PgpEncryptDecryptTest.java | 10 +- OpenKeychain/src/main/AndroidManifest.xml | 6 + .../NfcSyncPGPContentSignerBuilder.java | 30 +- .../results/PgpSignEncryptResult.java | 15 +- .../operations/results/SignEncryptResult.java | 3 + .../keychain/pgp/CanonicalizedSecretKey.java | 47 +- ...ut.java => PgpSignEncryptInputParcel.java} | 139 +++-- .../keychain/pgp/PgpSignEncryptOperation.java | 12 +- .../keychain/pgp/SignEncryptParcel.java | 53 +- .../keychain/remote/OpenPgpService.java | 29 +- .../service/CertifyActionsParcel.java | 13 +- .../service/input/CryptoInputParcel.java | 81 +++ .../service/input/CryptoOperationParcel.java | 52 -- .../service/input/NfcOperationsParcel.java | 85 +++ .../keychain/ui/EncryptActivity.java | 49 +- .../keychain/ui/EncryptFilesActivity.java | 1 - .../keychain/ui/EncryptTextActivity.java | 1 - .../keychain/ui/NfcOperationActivity.java | 492 ++++++++++++++++++ 18 files changed, 883 insertions(+), 235 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/{PgpSignEncryptInput.java => PgpSignEncryptInputParcel.java} (50%) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index d782230c7..b404ac6d1 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -136,7 +136,7 @@ public class PgpEncryptDecryptTest { InputData data = new InputData(in, in.available()); - PgpSignEncryptInput b = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel b = new setSignatureTimestamp(); b.setSymmetricPassphrase(mPassphrase); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); @@ -220,7 +220,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInput b = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel b = new setSignatureTimestamp(); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); @@ -301,7 +301,7 @@ public class PgpEncryptDecryptTest { InputData data = new InputData(in, in.available()); - PgpSignEncryptInput b = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel b = new setSignatureTimestamp(); b.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId(), mStaticRing2.getMasterKeyId() @@ -393,7 +393,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInput b = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel b = new setSignatureTimestamp(); b.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId(), @@ -475,7 +475,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInput b = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel b = new setSignatureTimestamp(); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 91c84fb18..c5317ae24 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -680,6 +680,12 @@ android:taskAffinity=":Nfc" android:allowTaskReparenting="true" /> + + diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java index e0286ec15..0344b2173 100644 --- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java @@ -14,8 +14,12 @@ import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPDigestCalculator; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.security.Provider; import java.util.Date; +import java.util.HashMap; +import java.util.Map; + /** * This class is based on JcaPGPContentSignerBuilder. @@ -31,31 +35,27 @@ public class NfcSyncPGPContentSignerBuilder private int keyAlgorithm; private long keyID; - private byte[] signedHash; - private Date creationTimestamp; + private Map signedHashes; public static class NfcInteractionNeeded extends RuntimeException { public byte[] hashToSign; - public Date creationTimestamp; public int hashAlgo; - public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp) + public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo) { super("NFC interaction required!"); this.hashToSign = hashToSign; this.hashAlgo = hashAlgo; - this.creationTimestamp = creationTimestamp; } } - public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, byte[] signedHash, Date creationTimestamp) + public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, Map signedHashes) { this.keyAlgorithm = keyAlgorithm; this.hashAlgorithm = hashAlgorithm; this.keyID = keyID; - this.signedHash = signedHash; - this.creationTimestamp = creationTimestamp; + this.signedHashes = signedHashes; } public NfcSyncPGPContentSignerBuilder setProvider(Provider provider) @@ -125,14 +125,14 @@ public class NfcSyncPGPContentSignerBuilder } public byte[] getSignature() { - if (signedHash != null) { - // we already have the signed hash from a previous execution, return this! - return signedHash; - } else { - // catch this when signatureGenerator.generate() is executed and divert digest to card, - // when doing the operation again reuse creationTimestamp (this will be hashed) - throw new NfcInteractionNeeded(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp); + byte[] digest = digestCalculator.getDigest(); + ByteBuffer buf = ByteBuffer.wrap(digest); + if (signedHashes.containsKey(buf)) { + return (byte[]) signedHashes.get(buf); } + // catch this when signatureGenerator.generate() is executed and divert digest to card, + // when doing the operation again reuse creationTimestamp (this will be hashed) + throw new NfcInteractionNeeded(digest, getHashAlgorithm()); } public byte[] getDigest() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java index de2f64404..a1204c0b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java @@ -35,7 +35,6 @@ public class PgpSignEncryptResult extends OperationResult { long mNfcKeyId; byte[] mNfcHash; int mNfcAlgo; - Date mNfcTimestamp; String mNfcPassphrase; byte[] mDetachedSignature; @@ -47,11 +46,10 @@ public class PgpSignEncryptResult extends OperationResult { mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; } - public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) { + public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, String passphrase) { mNfcKeyId = nfcKeyId; mNfcHash = nfcHash; mNfcAlgo = nfcAlgo; - mNfcTimestamp = nfcTimestamp; mNfcPassphrase = passphrase; } @@ -71,10 +69,6 @@ public class PgpSignEncryptResult extends OperationResult { return mNfcAlgo; } - public Date getNfcTimestamp() { - return mNfcTimestamp; - } - public String getNfcPassphrase() { return mNfcPassphrase; } @@ -95,7 +89,6 @@ public class PgpSignEncryptResult extends OperationResult { super(source); mNfcHash = source.readInt() != 0 ? source.createByteArray() : null; mNfcAlgo = source.readInt(); - mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null; mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null; } @@ -112,12 +105,6 @@ public class PgpSignEncryptResult extends OperationResult { dest.writeInt(0); } dest.writeInt(mNfcAlgo); - if (mNfcTimestamp != null) { - dest.writeInt(1); - dest.writeLong(mNfcTimestamp.getTime()); - } else { - dest.writeInt(0); - } if (mDetachedSignature != null) { dest.writeInt(1); dest.writeByteArray(mDetachedSignature); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index ed0de65b0..23e8094b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -21,6 +21,9 @@ import android.os.Parcel; import java.util.ArrayList; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; + + public class SignEncryptResult extends OperationResult { ArrayList mResults; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index ab91d7747..df409902f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -42,10 +42,13 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.Log; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; + /** * Wrapper for a PGPSecretKey. @@ -183,13 +186,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return PgpConstants.sPreferredHashAlgorithms; } - private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash, - Date nfcCreationTimestamp) { + private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, + Map signedHashes) { if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { // use synchronous "NFC based" SignerBuilder return new NfcSyncPGPContentSignerBuilder( mSecretKey.getPublicKey().getAlgorithm(), hashAlgo, - mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp) + mSecretKey.getKeyID(), signedHashes) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); } else { // content signer based on signing key algorithm and chosen hash algorithm @@ -200,28 +203,24 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext, - byte[] nfcSignedHash, Date nfcCreationTimestamp) + Map signedHashes, Date creationTimestamp) throws PgpGeneralException { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } - if (nfcSignedHash != null && nfcCreationTimestamp == null) { - throw new PgpGeneralException("Got nfc hash without timestamp!!"); - } // We explicitly create a signature creation timestamp in this place. // That way, we can inject an artificial one from outside, ie the one // used in previous runs of this function. - if (nfcCreationTimestamp == null) { + if (creationTimestamp == null) { // to sign using nfc PgpSignEncrypt is executed two times. // the first time it stops to return the PendingIntent for nfc connection and signing the hash // the second time the signed hash is used. // to get the same hash we cache the timestamp for the second round! - nfcCreationTimestamp = new Date(); + creationTimestamp = new Date(); } - PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, - nfcSignedHash, nfcCreationTimestamp); + PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes); int signatureType; if (cleartext) { @@ -237,7 +236,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback()); - spGen.setSignatureCreationTime(false, nfcCreationTimestamp); + spGen.setSignatureCreationTime(false, creationTimestamp); signatureGenerator.setHashedSubpackets(spGen.generate()); return signatureGenerator; } catch (PgpKeyNotFoundException | PGPException e) { @@ -267,8 +266,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { * @param userIds User IDs to certify * @return A keyring with added certifications */ - public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List userIds, - byte[] nfcSignedHash, Date nfcCreationTimestamp) { + public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, + List userIds, + HashMap signedHashes, Date creationTimestamp) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } @@ -283,7 +283,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { PGPSignatureGenerator signatureGenerator; { PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( - PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp); + PgpConstants.CERTIFY_HASH_ALGO, signedHashes); signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); try { @@ -296,9 +296,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { { // supply signatureGenerator with a SubpacketVector PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - if (nfcCreationTimestamp != null) { - spGen.setSignatureCreationTime(false, nfcCreationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); + if (creationTimestamp != null) { + spGen.setSignatureCreationTime(false, creationTimestamp); + Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp); } PGPSignatureSubpacketVector packetVector = spGen.generate(); signatureGenerator.setHashedSubpackets(packetVector); @@ -331,7 +331,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { * @return A keyring with added certifications */ public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing, - List userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) { + List userAttributes, + HashMap signedHashes, Date creationTimestamp) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } @@ -346,7 +347,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { PGPSignatureGenerator signatureGenerator; { PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( - PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp); + PgpConstants.CERTIFY_HASH_ALGO, signedHashes); signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); try { @@ -359,9 +360,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { { // supply signatureGenerator with a SubpacketVector PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - if (nfcCreationTimestamp != null) { - spGen.setSignatureCreationTime(false, nfcCreationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); + if (creationTimestamp != null) { + spGen.setSignatureCreationTime(false, creationTimestamp); + Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp); } PGPSignatureSubpacketVector packetVector = spGen.generate(); signatureGenerator.setHashedSubpackets(packetVector); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java similarity index 50% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java index 2dec4b9c2..00ecc179e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -20,10 +20,17 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import java.nio.ByteBuffer; import java.util.Date; +import java.util.Map; -public class PgpSignEncryptInput { +import android.os.Parcel; +import android.os.Parcelable; + + +public class PgpSignEncryptInputParcel implements Parcelable { protected String mVersionHeader = null; protected boolean mEnableAsciiArmorOutput = false; @@ -36,13 +43,71 @@ public class PgpSignEncryptInput { protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED; protected String mSignaturePassphrase = null; protected long mAdditionalEncryptId = Constants.key.none; - protected byte[] mNfcSignedHash = null; - protected Date mNfcCreationTimestamp = null; protected boolean mFailOnMissingEncryptionKeyIds = false; protected String mCharset; protected boolean mCleartextSignature; protected boolean mDetachedSignature = false; protected boolean mHiddenRecipients = false; + protected CryptoInputParcel mCryptoInput = new CryptoInputParcel(null); + + public PgpSignEncryptInputParcel() { + + } + + PgpSignEncryptInputParcel(Parcel source) { + + // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable + mVersionHeader = source.readString(); + mEnableAsciiArmorOutput = source.readInt() == 1; + mCompressionId = source.readInt(); + mEncryptionMasterKeyIds = source.createLongArray(); + mSymmetricPassphrase = source.readString(); + mSymmetricEncryptionAlgorithm = source.readInt(); + mSignatureMasterKeyId = source.readLong(); + mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null; + mSignatureHashAlgorithm = source.readInt(); + mSignaturePassphrase = source.readString(); + mAdditionalEncryptId = source.readLong(); + mFailOnMissingEncryptionKeyIds = source.readInt() == 1; + mCharset = source.readString(); + mCleartextSignature = source.readInt() == 1; + mDetachedSignature = source.readInt() == 1; + mHiddenRecipients = source.readInt() == 1; + + mCryptoInput = source.readParcelable(PgpSignEncryptInputParcel.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mVersionHeader); + dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); + dest.writeInt(mCompressionId); + dest.writeLongArray(mEncryptionMasterKeyIds); + dest.writeString(mSymmetricPassphrase); + dest.writeInt(mSymmetricEncryptionAlgorithm); + dest.writeLong(mSignatureMasterKeyId); + if (mSignatureSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSignatureSubKeyId); + } else { + dest.writeInt(0); + } + dest.writeInt(mSignatureHashAlgorithm); + dest.writeString(mSignaturePassphrase); + dest.writeLong(mAdditionalEncryptId); + dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); + dest.writeString(mCharset); + dest.writeInt(mCleartextSignature ? 1 : 0); + dest.writeInt(mDetachedSignature ? 1 : 0); + dest.writeInt(mHiddenRecipients ? 1 : 0); + + dest.writeParcelable(mCryptoInput, 0); + } public String getCharset() { return mCharset; @@ -56,19 +121,11 @@ public class PgpSignEncryptInput { return mFailOnMissingEncryptionKeyIds; } - public Date getNfcCreationTimestamp() { - return mNfcCreationTimestamp; - } - - public byte[] getNfcSignedHash() { - return mNfcSignedHash; - } - public long getAdditionalEncryptId() { return mAdditionalEncryptId; } - public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) { + public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) { mAdditionalEncryptId = additionalEncryptId; return this; } @@ -77,7 +134,7 @@ public class PgpSignEncryptInput { return mSignaturePassphrase; } - public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) { + public PgpSignEncryptInputParcel setSignaturePassphrase(String signaturePassphrase) { mSignaturePassphrase = signaturePassphrase; return this; } @@ -86,7 +143,7 @@ public class PgpSignEncryptInput { return mSignatureHashAlgorithm; } - public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { + public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) { mSignatureHashAlgorithm = signatureHashAlgorithm; return this; } @@ -95,7 +152,7 @@ public class PgpSignEncryptInput { return mSignatureSubKeyId; } - public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { + public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) { mSignatureSubKeyId = signatureSubKeyId; return this; } @@ -104,7 +161,7 @@ public class PgpSignEncryptInput { return mSignatureMasterKeyId; } - public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { + public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) { mSignatureMasterKeyId = signatureMasterKeyId; return this; } @@ -113,7 +170,7 @@ public class PgpSignEncryptInput { return mSymmetricEncryptionAlgorithm; } - public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; return this; } @@ -122,7 +179,7 @@ public class PgpSignEncryptInput { return mSymmetricPassphrase; } - public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) { + public PgpSignEncryptInputParcel setSymmetricPassphrase(String symmetricPassphrase) { mSymmetricPassphrase = symmetricPassphrase; return this; } @@ -131,7 +188,7 @@ public class PgpSignEncryptInput { return mEncryptionMasterKeyIds; } - public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { + public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { mEncryptionMasterKeyIds = encryptionMasterKeyIds; return this; } @@ -140,7 +197,7 @@ public class PgpSignEncryptInput { return mCompressionId; } - public PgpSignEncryptInput setCompressionId(int compressionId) { + public PgpSignEncryptInputParcel setCompressionId(int compressionId) { mCompressionId = compressionId; return this; } @@ -153,28 +210,22 @@ public class PgpSignEncryptInput { return mVersionHeader; } - public PgpSignEncryptInput setVersionHeader(String versionHeader) { + public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) { mVersionHeader = versionHeader; return this; } - public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { mEnableAsciiArmorOutput = enableAsciiArmorOutput; return this; } - public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { + public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; return this; } - public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) { - mNfcSignedHash = signedHash; - mNfcCreationTimestamp = creationTimestamp; - return this; - } - - public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) { + public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) { this.mCleartextSignature = cleartextSignature; return this; } @@ -183,7 +234,7 @@ public class PgpSignEncryptInput { return mCleartextSignature; } - public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { + public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) { this.mDetachedSignature = detachedSignature; return this; } @@ -192,7 +243,7 @@ public class PgpSignEncryptInput { return mDetachedSignature; } - public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) { + public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) { this.mHiddenRecipients = hiddenRecipients; return this; } @@ -200,5 +251,29 @@ public class PgpSignEncryptInput { public boolean isHiddenRecipients() { return mHiddenRecipients; } + + public PgpSignEncryptInputParcel setCryptoInput(CryptoInputParcel cryptoInput) { + mCryptoInput = cryptoInput; + return this; + } + + public Map getCryptoData() { + return mCryptoInput.getCryptoData(); + } + + public Date getSignatureTime() { + return mCryptoInput.getSignatureTime(); + } + + public static final Creator CREATOR = new Creator() { + public PgpSignEncryptInputParcel createFromParcel(final Parcel source) { + return new PgpSignEncryptInputParcel(source); + } + + public PgpSignEncryptInputParcel[] newArray(final int size) { + return new PgpSignEncryptInputParcel[size]; + } + }; + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 94e04060d..2e515137a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -44,6 +44,8 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -71,7 +73,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * * For a high-level operation based on URIs, see SignEncryptOperation. * - * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput + * @see PgpSignEncryptInputParcel * @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult * @see org.sufficientlysecure.keychain.operations.SignEncryptOperation * @@ -99,7 +101,7 @@ public class PgpSignEncryptOperation extends BaseOperation { /** * Signs and/or encrypts data based on parameters of class */ - public PgpSignEncryptResult execute(PgpSignEncryptInput input, + public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, InputData inputData, OutputStream outputStream) { int indent = 0; @@ -282,7 +284,8 @@ public class PgpSignEncryptOperation extends BaseOperation { try { boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption; signatureGenerator = signingKey.getSignatureGenerator( - input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); + input.getSignatureHashAlgorithm(), cleartext, + input.getCryptoData(), input.getSignatureTime()); } catch (PgpGeneralException e) { log.add(LogType.MSG_PSE_ERROR_NFC, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); @@ -489,7 +492,8 @@ public class PgpSignEncryptOperation extends BaseOperation { new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log); // Note that the checked key here is the master key, not the signing key // (although these are always the same on Yubikeys) - result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase()); + result.setNfcData(signingKey.getKeyId(), e.hashToSign, e.hashAlgo, + input.getSignaturePassphrase()); Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign)); return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java index 8e71e8815..1b14e78fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.pgp; import android.net.Uri; import android.os.Parcel; -import android.os.Parcelable; import java.util.ArrayList; import java.util.Collection; @@ -40,7 +39,7 @@ import java.util.List; * left, which will be returned in a byte array as part of the result parcel. * */ -public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable { +public class SignEncryptParcel extends PgpSignEncryptInputParcel { public ArrayList mInputUris = new ArrayList<>(); public ArrayList mOutputUris = new ArrayList<>(); @@ -51,26 +50,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable } public SignEncryptParcel(Parcel src) { - - // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable - mVersionHeader = src.readString(); - mEnableAsciiArmorOutput = src.readInt() == 1; - mCompressionId = src.readInt(); - mEncryptionMasterKeyIds = src.createLongArray(); - mSymmetricPassphrase = src.readString(); - mSymmetricEncryptionAlgorithm = src.readInt(); - mSignatureMasterKeyId = src.readLong(); - mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null; - mSignatureHashAlgorithm = src.readInt(); - mSignaturePassphrase = src.readString(); - mAdditionalEncryptId = src.readLong(); - mNfcSignedHash = src.createByteArray(); - mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null; - mFailOnMissingEncryptionKeyIds = src.readInt() == 1; - mCharset = src.readString(); - mCleartextSignature = src.readInt() == 1; - mDetachedSignature = src.readInt() == 1; - mHiddenRecipients = src.readInt() == 1; + super(src); mInputUris = src.createTypedArrayList(Uri.CREATOR); mOutputUris = src.createTypedArrayList(Uri.CREATOR); @@ -108,34 +88,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable } public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mVersionHeader); - dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); - dest.writeInt(mCompressionId); - dest.writeLongArray(mEncryptionMasterKeyIds); - dest.writeString(mSymmetricPassphrase); - dest.writeInt(mSymmetricEncryptionAlgorithm); - dest.writeLong(mSignatureMasterKeyId); - if (mSignatureSubKeyId != null) { - dest.writeInt(1); - dest.writeLong(mSignatureSubKeyId); - } else { - dest.writeInt(0); - } - dest.writeInt(mSignatureHashAlgorithm); - dest.writeString(mSignaturePassphrase); - dest.writeLong(mAdditionalEncryptId); - dest.writeByteArray(mNfcSignedHash); - if (mNfcCreationTimestamp != null) { - dest.writeInt(1); - dest.writeLong(mNfcCreationTimestamp.getTime()); - } else { - dest.writeInt(0); - } - dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); - dest.writeString(mCharset); - dest.writeInt(mCleartextSignature ? 1 : 0); - dest.writeInt(mDetachedSignature ? 1 : 0); - dest.writeInt(mHiddenRecipients ? 1 : 0); + super.writeToParcel(dest, flags); dest.writeTypedList(mInputUris); dest.writeTypedList(mOutputUris); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index a4bc95602..f15bf7925 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEnt import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.ImportKeysActivity; import org.sufficientlysecure.keychain.ui.NfcActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -258,11 +259,13 @@ public class OpenPgpService extends RemoteService { } // carefully: only set if timestamp exists - Date nfcCreationDate = null; + Date nfcCreationDate; long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp); if (nfcCreationTimestamp != -1) { nfcCreationDate = new Date(nfcCreationTimestamp); + } else { + nfcCreationDate = new Date(); } // Get Input- and OutputStream from ParcelFileDescriptor @@ -275,15 +278,18 @@ public class OpenPgpService extends RemoteService { long inputLength = is.available(); InputData inputData = new InputData(is, inputLength); + CryptoInputParcel cryptoInput = new CryptoInputParcel(nfcCreationDate); + cryptoInput.addCryptoData(null, nfcSignedHash); // TODO fix + // sign-only - PgpSignEncryptInput pseInput = new PgpSignEncryptInput() + PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel() .setEnableAsciiArmorOutput(asciiArmor) .setCleartextSignature(cleartextSign) .setDetachedSignature(!cleartextSign) .setVersionHeader(null) .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) .setSignatureMasterKeyId(signKeyId) - .setNfcState(nfcSignedHash, nfcCreationDate); + .setCryptoInput(cryptoInput); // execute PGP operation! PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); @@ -298,7 +304,7 @@ public class OpenPgpService extends RemoteService { // return PendingIntent to execute NFC activity // pass through the signature creation timestamp to be used again on second execution // of PgpSignEncrypt when we have the signed hash! - data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); + data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, nfcCreationDate.getTime()); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -389,7 +395,7 @@ public class OpenPgpService extends RemoteService { long inputLength = is.available(); InputData inputData = new InputData(is, inputLength, originalFilename); - PgpSignEncryptInput pseInput = new PgpSignEncryptInput(); + PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel(); pseInput.setEnableAsciiArmorOutput(asciiArmor) .setVersionHeader(null) .setCompressionId(compressionId) @@ -412,16 +418,21 @@ public class OpenPgpService extends RemoteService { byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); // carefully: only set if timestamp exists - Date nfcCreationDate = null; + Date nfcCreationDate; long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); if (nfcCreationTimestamp != -1) { nfcCreationDate = new Date(nfcCreationTimestamp); + } else { + nfcCreationDate = new Date(); } + CryptoInputParcel cryptoInput = new CryptoInputParcel(nfcCreationDate); + cryptoInput.addCryptoData(null, nfcSignedHash); // TODO fix! + // sign and encrypt pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) .setSignatureMasterKeyId(signKeyId) - .setNfcState(nfcSignedHash, nfcCreationDate) + .setCryptoInput(cryptoInput) .setAdditionalEncryptId(signKeyId); // add sign key for encryption } @@ -439,7 +450,7 @@ public class OpenPgpService extends RemoteService { // return PendingIntent to execute NFC activity // pass through the signature creation timestamp to be used again on second execution // of PgpSignEncrypt when we have the signed hash! - data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime()); + data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, 0L); // TODO fix // return PendingIntent to be executed by client Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_INTENT, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index d6da18e6e..485d5de72 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -26,31 +26,31 @@ import java.util.ArrayList; import java.util.Date; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.service.input.CryptoOperationParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; /** * This class is a a transferable representation for a number of keyrings to * be certified. */ -public class CertifyActionsParcel extends CryptoOperationParcel { +public class CertifyActionsParcel implements Parcelable { // the master key id to certify with final public long mMasterKeyId; public CertifyLevel mLevel; public ArrayList mCertifyActions = new ArrayList<>(); + public CryptoInputParcel mCryptoInput; public CertifyActionsParcel(Date operationTime, long masterKeyId) { - super(operationTime); mMasterKeyId = masterKeyId; + mCryptoInput = new CryptoInputParcel(operationTime); mLevel = CertifyLevel.DEFAULT; } public CertifyActionsParcel(Parcel source) { - super(source); - mMasterKeyId = source.readLong(); + mCryptoInput = source.readParcelable(CertifyActionsParcel.class.getClassLoader()); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; @@ -63,9 +63,8 @@ public class CertifyActionsParcel extends CryptoOperationParcel { @Override public void writeToParcel(Parcel destination, int flags) { - super.writeToParcel(destination, flags); - destination.writeLong(mMasterKeyId); + destination.writeParcelable(mCryptoInput, 0); destination.writeInt(mLevel.ordinal()); destination.writeSerializable(mCertifyActions); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java new file mode 100644 index 000000000..e02eda4b3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -0,0 +1,81 @@ +package org.sufficientlysecure.keychain.service.input; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import android.os.Parcel; +import android.os.Parcelable; + + +/** This is a base class for the input of crypto operations. + * + */ +public class CryptoInputParcel implements Parcelable { + + Date mSignatureTime; + + // this map contains both decrypted session keys and signed hashes to be + // used in the crypto operation described by this parcel. + private HashMap mCryptoData = new HashMap<>(); + + public CryptoInputParcel(Date signatureTime) { + mSignatureTime = signatureTime == null ? new Date() : signatureTime; + } + + protected CryptoInputParcel(Parcel source) { + mSignatureTime = new Date(source.readLong()); + + { + int count = source.readInt(); + mCryptoData = new HashMap<>(count); + for (int i = 0; i < count; i++) { + byte[] key = source.createByteArray(); + byte[] value = source.createByteArray(); + mCryptoData.put(ByteBuffer.wrap(key), value); + } + } + + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mSignatureTime.getTime()); + + dest.writeInt(mCryptoData.size()); + for (HashMap.Entry entry : mCryptoData.entrySet()) { + dest.writeByteArray(entry.getKey().array()); + dest.writeByteArray(entry.getValue()); + } + } + + public void addCryptoData(byte[] hash, byte[] signedHash) { + mCryptoData.put(ByteBuffer.wrap(hash), signedHash); + } + + public Map getCryptoData() { + return Collections.unmodifiableMap(mCryptoData); + } + + public Date getSignatureTime() { + return mSignatureTime; + } + + public static final Creator CREATOR = new Creator() { + public CryptoInputParcel createFromParcel(final Parcel source) { + return new CryptoInputParcel(source); + } + + public CryptoInputParcel[] newArray(final int size) { + return new CryptoInputParcel[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java deleted file mode 100644 index 2101755ad..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoOperationParcel.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.sufficientlysecure.keychain.service.input; - -import java.nio.ByteBuffer; -import java.util.Date; -import java.util.HashMap; - -import android.os.Parcel; -import android.os.Parcelable; - - -/** This is a base class for the input of crypto operations. - * - */ -public abstract class CryptoOperationParcel implements Parcelable { - - Date mOperationTime; - - // this map contains both decrypted session keys and signed hashes to be - // used in the crypto operation described by this parcel. - HashMap mCryptoData; - - protected CryptoOperationParcel(Date operationTime) { - mOperationTime = operationTime; - } - - protected CryptoOperationParcel(Parcel source) { - mOperationTime = new Date(source.readLong()); - - { - int count = source.readInt(); - mCryptoData = new HashMap<>(count); - for (int i = 0; i < count; i++) { - byte[] key = source.createByteArray(); - byte[] value = source.createByteArray(); - mCryptoData.put(ByteBuffer.wrap(key), value); - } - } - - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mOperationTime.getTime()); - - dest.writeInt(mCryptoData.size()); - for (HashMap.Entry entry : mCryptoData.entrySet()) { - dest.writeByteArray(entry.getKey().array()); - dest.writeByteArray(entry.getValue()); - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java new file mode 100644 index 000000000..5d35e94e0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java @@ -0,0 +1,85 @@ +package org.sufficientlysecure.keychain.service.input; + +import java.util.Date; + +import android.os.Parcel; +import android.os.Parcelable; + + +public class NfcOperationsParcel implements Parcelable { + + public enum NfcOperationType { + NFC_SIGN, NFC_DECRYPT + } + + public Date mSignatureTime; + public final NfcOperationType mType; + public final byte[][] mInputHash; + public final int[] mSignAlgo; + + private NfcOperationsParcel(NfcOperationType type, byte[] inputHash, int signAlgo, Date signatureTime) { + mType = type; + mInputHash = new byte[][] { inputHash }; + mSignAlgo = new int[] { signAlgo }; + mSignatureTime = signatureTime; + } + + public NfcOperationsParcel(Parcel source) { + mType = NfcOperationType.values()[source.readInt()]; + + { + int count = source.readInt(); + mInputHash = new byte[count][]; + mSignAlgo = new int[count]; + for (int i = 0; i < count; i++) { + mInputHash[i] = source.createByteArray(); + mSignAlgo[i] = source.readInt(); + } + } + + mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null; + + } + + public static NfcOperationsParcel createNfcSignOperation( + byte[] inputHash, int signAlgo, Date signatureTime) { + return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, inputHash, signAlgo, signatureTime); + } + + public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) { + return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, inputHash, 0, null); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType.ordinal()); + dest.writeInt(mInputHash.length); + for (int i = 0; i < mInputHash.length; i++) { + dest.writeByteArray(mInputHash[i]); + dest.writeInt(mSignAlgo[i]); + } + if (mSignatureTime != null) { + dest.writeInt(1); + dest.writeLong(mSignatureTime.getTime()); + } else { + dest.writeInt(0); + } + + } + + public static final Creator CREATOR = new Creator() { + public NfcOperationsParcel createFromParcel(final Parcel source) { + return new NfcOperationsParcel(source); + } + + public NfcOperationsParcel[] newArray(final int size) { + return new NfcOperationsParcel[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 35dfcb87c..75f22f01c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -25,15 +25,15 @@ import android.os.Message; import android.os.Messenger; import android.view.View; -import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; -import java.util.Date; public abstract class EncryptActivity extends BaseActivity { @@ -42,8 +42,6 @@ public abstract class EncryptActivity extends BaseActivity { // For NFC data protected String mSigningKeyPassphrase = null; - protected Date mNfcTimestamp = null; - protected byte[] mNfcHash = null; @Override public void onCreate(Bundle savedInstanceState) { @@ -64,17 +62,12 @@ public abstract class EncryptActivity extends BaseActivity { startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); } - protected void startNfcSign(long keyId, String pin, byte[] hashToSign, int hashAlgo) { - // build PendingIntent for Yubikey NFC operations - Intent intent = new Intent(this, NfcActivity.class); - intent.setAction(NfcActivity.ACTION_SIGN_HASH); + protected void startNfcSign(long keyId, String pin, NfcOperationsParcel nfcOps) { - // pass params through to activity that it can be returned again later to repeat pgp operation - intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService - intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId); - intent.putExtra(NfcActivity.EXTRA_PIN, pin); - intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign); - intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo); + Intent intent = new Intent(this, NfcOperationActivity.class); + intent.putExtra(NfcOperationActivity.EXTRA_PIN, pin); + intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps); + // TODO respect keyid(?) startActivityForResult(intent, REQUEST_CODE_NFC); } @@ -93,8 +86,9 @@ public abstract class EncryptActivity extends BaseActivity { case REQUEST_CODE_NFC: { if (resultCode == RESULT_OK && data != null) { - mNfcHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); - startEncrypt(); + CryptoInputParcel cryptoInput = + data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); + startEncrypt(cryptoInput); return; } break; @@ -108,6 +102,10 @@ public abstract class EncryptActivity extends BaseActivity { } public void startEncrypt() { + startEncrypt(null); + } + + public void startEncrypt(CryptoInputParcel cryptoInput) { if (!inputIsValid()) { // Notify was created by inputIsValid. return; @@ -117,8 +115,13 @@ public abstract class EncryptActivity extends BaseActivity { Intent intent = new Intent(this, KeychainIntentService.class); intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT); + final SignEncryptParcel input = createEncryptBundle(); + if (cryptoInput != null) { + input.setCryptoInput(cryptoInput); + } + Bundle data = new Bundle(); - data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle()); + data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after encrypting is done in KeychainIntentService @@ -141,9 +144,13 @@ public abstract class EncryptActivity extends BaseActivity { } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == PgpSignEncryptResult.RESULT_PENDING_NFC) { - mNfcTimestamp = pgpResult.getNfcTimestamp(); - startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), - pgpResult.getNfcHash(), pgpResult.getNfcAlgo()); + NfcOperationsParcel parcel = NfcOperationsParcel.createNfcSignOperation( + pgpResult.getNfcHash(), + pgpResult.getNfcAlgo(), + input.getSignatureTime()); + startNfcSign(pgpResult.getNfcKeyId(), + pgpResult.getNfcPassphrase(), parcel); + } else { throw new RuntimeException("Unhandled pending result!"); } @@ -158,8 +165,6 @@ public abstract class EncryptActivity extends BaseActivity { // no matter the result, reset parameters mSigningKeyPassphrase = null; - mNfcHash = null; - mNfcTimestamp = null; } } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index f1784fab3..49a0f5016 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -252,7 +252,6 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi data.setEncryptionMasterKeyIds(mEncryptionKeyIds); data.setSignatureMasterKeyId(mSigningKeyId); data.setSignaturePassphrase(mSigningKeyPassphrase); - data.setNfcState(mNfcHash, mNfcTimestamp); } return data; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index 14f2c492d..894c1202d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -232,7 +232,6 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv data.setEncryptionMasterKeyIds(mEncryptionKeyIds); data.setSignatureMasterKeyId(mSigningKeyId); data.setSignaturePassphrase(mSigningKeyPassphrase); - data.setNfcState(mNfcHash, mNfcTimestamp); } return data; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java new file mode 100644 index 000000000..c1403d748 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -0,0 +1,492 @@ +/** + * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann + * + * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Build; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.Toast; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; + + +/** + * 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 + */ +@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) +public class NfcOperationActivity extends BaseActivity { + + public static final String EXTRA_PIN = "pin"; + public static final String EXTRA_NFC_OPS = "nfc_operations"; + + public static final String RESULT_DATA = "result_data"; + + private static final int TIMEOUT = 100000; + + private NfcAdapter mNfcAdapter; + private IsoDep mIsoDep; + + private String mPin; + + NfcOperationsParcel mNfcOperations; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(Constants.TAG, "NfcOperationActivity.onCreate"); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); + } + + Bundle data = intent.getExtras(); + + mNfcOperations = data.getParcelable(EXTRA_NFC_OPS); + mPin = data.getString(EXTRA_PIN); + + } + + @Override + protected void initLayout() { + setContentView(R.layout.nfc_activity); + } + + /** + * Called when the system is about to start resuming a previous activity, + * disables NFC Foreground Dispatch + */ + public void onPause() { + super.onPause(); + Log.d(Constants.TAG, "NfcOperationActivity.onPause"); + + disableNfcForegroundDispatch(); + } + + /** + * Called when the activity will start interacting with the user, + * enables NFC Foreground Dispatch + */ + public void onResume() { + super.onResume(); + Log.d(Constants.TAG, "NfcOperationActivity.onResume"); + + enableNfcForegroundDispatch(); + } + + /** + * This activity is started as a singleTop activity. + * All new NFC Intents which are delivered to this activity are handled here + */ + public void onNewIntent(Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + handleNdefDiscoveredIntent(intent); + } catch (IOException e) { + Log.e(Constants.TAG, "Connection error!", e); + toast("Connection Error: " + e.getMessage()); + setResult(RESULT_CANCELED); + finish(); + } + } + } + + /** Handle NFC communication and return a result. + * + * This method is called by onNewIntent above upon discovery of an NFC tag. + * It handles initialization and login to the application, subsequently + * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then + * finishes the activity with an appropiate result. + * + * On general communication, see also + * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx + * + * References to pages are generally related to the OpenPGP Application + * on ISO SmartCard Systems specification. + * + */ + private void handleNdefDiscoveredIntent(Intent intent) throws IOException { + + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + // Connect to the detected tag, setting a couple of settings + mIsoDep = IsoDep.get(detectedTag); + mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation + mIsoDep.connect(); + + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. + // See specification, page 51 + String accepted = "9000"; + + // Command APDU (page 51) for SELECT FILE command (page 29) + String opening = + "00" // CLA + + "A4" // INS + + "04" // P1 + + "00" // P2 + + "06" // Lc (number of bytes) + + "D27600012401" // Data (6 bytes) + + "00"; // Le + if ( ! card(opening).equals(accepted)) { // activate connection + toast("Opening Error!"); + setResult(RESULT_CANCELED); + finish(); + return; + } + + // Command APDU for VERIFY command (page 32) + String login = + "00" // CLA + + "20" // INS + + "00" // P1 + + "82" // P2 (PW1) + + String.format("%02x", mPin.length()) // Lc + + Hex.toHexString(mPin.getBytes()); + if ( ! card(login).equals(accepted)) { // login + toast("Wrong PIN!"); + setResult(RESULT_CANCELED); + finish(); + return; + } + + CryptoInputParcel resultData = new CryptoInputParcel(mNfcOperations.mSignatureTime); + + switch (mNfcOperations.mType) { + + case NFC_DECRYPT: + + for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { + byte[] hash = mNfcOperations.mInputHash[i]; + byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); + resultData.addCryptoData(hash, decryptedSessionKey); + } + break; + + case NFC_SIGN: + for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { + byte[] hash = mNfcOperations.mInputHash[i]; + int algo = mNfcOperations.mSignAlgo[i]; + byte[] signedHash = nfcCalculateSignature(hash, algo); + resultData.addCryptoData(hash, signedHash); + } + break; + } + + // give data through for new service call + Intent result = new Intent(); + result.putExtra(RESULT_DATA, resultData); + setResult(RESULT_OK, result); + finish(); + + } + + /** + * Gets the user ID + * + * @return the user id as "name " + * @throws java.io.IOException + */ + public String getUserId() throws IOException { + String info = "00CA006500"; + String data = "00CA005E00"; + return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">"; + } + + /** Return the key id from application specific data stored on tag, or null + * if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The long key id of the requested key, or null if not found. + */ + public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException { + byte[] fp = nfcGetFingerprint(isoDep, idx); + if (fp == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(fp); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); + } + + /** Return fingerprints of all keys from application specific data stored + * on tag, or null if data not available. + * + * @return The fingerprints of all subkeys in a contiguous byte array. + */ + public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException { + String data = "00CA006E00"; + byte[] buf = isoDep.transceive(Hex.decode(data)); + + Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); + Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); + + Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); + if (fptlv == null) { + return null; + } + + return fptlv.mV; + } + + /** Return the fingerprint from application specific data stored on tag, or + * null if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The fingerprint of the requested key, or null if not found. + */ + public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException { + byte[] data = nfcGetFingerprints(isoDep); + + // return the master key fingerprint + ByteBuffer fpbuf = ByteBuffer.wrap(data); + byte[] fp = new byte[20]; + fpbuf.position(idx*20); + fpbuf.get(fp, 0, 20); + + return fp; + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + * @throws java.io.IOException + */ + public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { + + // dsi, including Lc + String dsi; + + Log.i(Constants.TAG, "Hash: " + hashAlgo); + switch (hashAlgo) { + case HashAlgorithmTags.SHA1: + if (hash.length != 20) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!"); + } + dsi = "23" // Lc + + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes + + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes + + "0605" + "2B0E03021A" // OID of SHA1 + + "0500" // TLV coding of ZERO + + "0414" + getHex(hash); // 0x14 are 20 hash bytes + break; + case HashAlgorithmTags.RIPEMD160: + if (hash.length != 20) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!"); + } + dsi = "233021300906052B2403020105000414" + getHex(hash); + break; + case HashAlgorithmTags.SHA224: + if (hash.length != 28) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!"); + } + dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); + break; + case HashAlgorithmTags.SHA256: + if (hash.length != 32) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!"); + } + dsi = "333031300D060960864801650304020105000420" + getHex(hash); + break; + case HashAlgorithmTags.SHA384: + if (hash.length != 48) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!"); + } + dsi = "433041300D060960864801650304020205000430" + getHex(hash); + break; + case HashAlgorithmTags.SHA512: + if (hash.length != 64) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!"); + } + dsi = "533051300D060960864801650304020305000440" + getHex(hash); + break; + default: + throw new RuntimeException("Not supported hash algo!"); + } + + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) + String apdu = + "002A9E9A" // CLA, INS, P1, P2 + + dsi // digital signature input + + "00"; // Le + + String response = card(apdu); + + // split up response into signature and status + String status = response.substring(response.length()-4); + String signature = response.substring(0, response.length() - 4); + + // while we are getting 0x61 status codes, retrieve more data + while (status.substring(0, 2).equals("61")) { + Log.d(Constants.TAG, "requesting more data, status " + status); + // Send GET RESPONSE command + response = card("00C00000" + status.substring(2)); + status = response.substring(response.length()-4); + signature += response.substring(0, response.length()-4); + } + + Log.d(Constants.TAG, "final response:" + status); + + if ( ! status.equals("9000")) { + toast("Bad NFC response code: " + status); + return null; + } + + // Make sure the signature we received is actually the expected number of bytes long! + if (signature.length() != 256 && signature.length() != 512) { + toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); + return null; + } + + return Hex.decode(signature); + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + * @throws java.io.IOException + */ + public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { + String firstApdu = "102a8086fe"; + String secondApdu = "002a808603"; + String le = "00"; + + byte[] one = new byte[254]; + // leave out first byte: + System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); + + byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; + for (int i = 0; i < two.length; i++) { + two[i] = encryptedSessionKey[i + one.length + 1]; + } + + String first = card(firstApdu + getHex(one)); + String second = card(secondApdu + getHex(two) + le); + + String decryptedSessionKey = getDataField(second); + + Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + + return Hex.decode(decryptedSessionKey); + } + + /** + * Prints a message to the screen + * + * @param text the text which should be contained within the toast + */ + private void toast(String text) { + Toast.makeText(this, text, Toast.LENGTH_LONG).show(); + } + + /** + * Receive new NFC Intents to this activity only by enabling foreground dispatch. + * This can only be done in onResume! + */ + public void enableNfcForegroundDispatch() { + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + Intent nfcI = new Intent(this, NfcOperationActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); + IntentFilter[] writeTagFilters = new IntentFilter[]{ + new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + }; + + // https://code.google.com/p/android/issues/detail?id=62918 + // maybe mNfcAdapter.enableReaderMode(); ? + try { + mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); + } catch (IllegalStateException e) { + Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e); + } + Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); + } + + /** + * Disable foreground dispatch in onPause! + */ + public void disableNfcForegroundDispatch() { + mNfcAdapter.disableForegroundDispatch(this); + Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); + } + + /** + * Gets the name of the user out of the raw card output regarding card holder related data + * + * @param name the raw card holder related data from the card + * @return the name given in this data + */ + public String getName(String name) { + String slength; + int ilength; + name = name.substring(6); + slength = name.substring(0, 2); + ilength = Integer.parseInt(slength, 16) * 2; + name = name.substring(2, ilength + 2); + name = (new String(Hex.decode(name))).replace('<', ' '); + return (name); + } + + /** + * Reduces the raw data from the card by four characters + * + * @param output the raw data from the card + * @return the data field of that data + */ + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + /** + * Communicates with the OpenPgpCard via the APDU + * + * @param hex the hexadecimal APDU + * @return The answer from the card + * @throws java.io.IOException throws an exception if something goes wrong + */ + public String card(String hex) throws IOException { + return getHex(mIsoDep.transceive(Hex.decode(hex))); + } + + /** + * Converts a byte array into an hex string + * + * @param raw the byte array representation + * @return the hexadecimal string representation + */ + public static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } +} From d46fc3740bbfc3bac0b1133a3e9d47c7ce3e06e2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 18 Mar 2015 21:12:31 +0100 Subject: [PATCH 03/80] yubikey certifications! --- .../keychain/operations/CertifyOperation.java | 46 +++--- .../operations/results/CertifyResult.java | 10 +- .../results/InputPendingResult.java | 76 +++++++++ .../operations/results/OperationResult.java | 2 +- .../operations/results/SignEncryptResult.java | 1 + .../keychain/pgp/CanonicalizedSecretKey.java | 151 +++--------------- .../keychain/pgp/PgpCertifyOperation.java | 149 +++++++++++++++++ .../keychain/pgp/PgpSignEncryptOperation.java | 4 +- .../service/CertifyActionsParcel.java | 14 +- .../service/KeychainIntentServiceHandler.java | 2 + .../service/input/NfcOperationsParcel.java | 81 ++++++++-- .../keychain/ui/CertifyKeyFragment.java | 80 +++------- .../keychain/ui/CryptoOperationFragment.java | 89 +++++++++++ .../keychain/ui/NfcOperationActivity.java | 10 +- OpenKeychain/src/main/res/values/strings.xml | 2 +- 15 files changed, 476 insertions(+), 241 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index ebf0dc70b..140e03764 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -30,6 +30,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation; +import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; @@ -38,6 +40,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -73,10 +77,6 @@ public class CertifyOperation extends BaseOperation { mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); log.add(LogType.MSG_CRT_UNLOCK, 1); certificationKey = secretKeyRing.getSecretKey(); - if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) { - log.add(LogType.MSG_CRT_ERROR_DIVERT, 2); - return new CertifyResult(CertifyResult.RESULT_ERROR, log); - } // certification is always with the master key id, so use that one String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); @@ -102,6 +102,8 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.getSignatureTime()); + // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { @@ -122,28 +124,21 @@ public class CertifyOperation extends BaseOperation { CanonicalizedPublicKeyRing publicRing = mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId); - UncachedKeyRing certifiedKey = null; - if (action.mUserIds != null) { - log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(), - KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); + PgpCertifyOperation op = new PgpCertifyOperation(); + PgpCertifyResult result = op.certify(certificationKey, publicRing, + log, 2, action, parcel.getSignatureData(), parcel.getSignatureTime()); - certifiedKey = certificationKey.certifyUserIds( - publicRing, action.mUserIds, null, null); - } - - if (action.mUserAttributes != null) { - log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(), - KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); - - certifiedKey = certificationKey.certifyUserAttributes( - publicRing, action.mUserAttributes, null, null); - } - - if (certifiedKey == null) { + if (!result.success()) { certifyError += 1; - log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3); + continue; } - certifiedKeys.add(certifiedKey); + if (result.nfcInputRequired()) { + NfcOperationsParcel requiredInput = result.getRequiredInput(); + allRequiredInput.addAll(requiredInput); + continue; + } + + certifiedKeys.add(result.getCertifiedRing()); } catch (NotFoundException e) { certifyError += 1; @@ -152,6 +147,11 @@ public class CertifyOperation extends BaseOperation { } + if ( ! allRequiredInput.isEmpty()) { + log.add(LogType.MSG_CRT_NFC_RETURN, 1); + return new CertifyResult(log, allRequiredInput.build()); + } + log.add(LogType.MSG_CRT_SAVING, 1); // Check if we were cancelled diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index 94684851a..33591fa03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -30,16 +31,19 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -public class CertifyResult extends OperationResult { - +public class CertifyResult extends InputPendingResult { int mCertifyOk, mCertifyError, mUploadOk, mUploadError; public CertifyResult(int result, OperationLog log) { super(result, log); } + public CertifyResult(OperationLog log, NfcOperationsParcel requiredInput) { + super(log, requiredInput); + } + public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) { - this(result, log); + super(result, log); mCertifyOk = certifyOk; mCertifyError = certifyError; mUploadOk = uploadOk; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java new file mode 100644 index 000000000..b681aba60 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -0,0 +1,76 @@ +package org.sufficientlysecure.keychain.operations.results; + + +import android.os.Parcel; + +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; + + +public class InputPendingResult extends OperationResult { + + // the fourth bit indicates a "data pending" result! (it's also a form of non-success) + public static final int RESULT_PENDING = RESULT_ERROR + 8; + + public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16; + public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32; + + final NfcOperationsParcel mRequiredInput; + final Long mKeyIdPassphraseNeeded; + + public InputPendingResult(int result, OperationLog log) { + super(result, log); + mRequiredInput = null; + mKeyIdPassphraseNeeded = null; + } + + public InputPendingResult(OperationLog log, NfcOperationsParcel requiredInput) { + super(RESULT_PENDING_NFC, log); + mRequiredInput = requiredInput; + mKeyIdPassphraseNeeded = null; + } + + public InputPendingResult(OperationLog log, long keyIdPassphraseNeeded) { + super(RESULT_PENDING_PASSPHRASE, log); + mRequiredInput = null; + mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; + } + + public InputPendingResult(Parcel source) { + super(source); + mRequiredInput = source.readParcelable(getClass().getClassLoader()); + mKeyIdPassphraseNeeded = source.readInt() != 0 ? source.readLong() : null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mRequiredInput, 0); + if (mKeyIdPassphraseNeeded != null) { + dest.writeInt(1); + dest.writeLong(mKeyIdPassphraseNeeded); + } else { + dest.writeInt(0); + } + } + + public boolean isPending() { + return (mResult & RESULT_PENDING) == RESULT_PENDING; + } + + public boolean isNfcPending() { + return (mResult & RESULT_PENDING_NFC) == RESULT_PENDING_NFC; + } + + public boolean isPassphrasePending() { + return (mResult & RESULT_PENDING_PASSPHRASE) == RESULT_PENDING_PASSPHRASE; + } + + public NfcOperationsParcel getNfcOperationsParcel() { + return mRequiredInput; + } + + public long getPassphraseKeyId() { + return mKeyIdPassphraseNeeded; + } + +} 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 068e314d5..989fb395e 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 @@ -697,9 +697,9 @@ public abstract class OperationResult implements Parcelable { MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found), MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing), MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), - MSG_CRT_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_crt_error_divert), MSG_CRT (LogLevel.START, R.string.msg_crt), MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch), + MSG_CRT_NFC_RETURN (LogLevel.OK, R.string.msg_crt_nfc_return), MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save), MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving), MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index 23e8094b9..5a0a51ee5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -31,6 +31,7 @@ public class SignEncryptResult extends OperationResult { public static final int RESULT_PENDING = RESULT_ERROR + 8; + public PgpSignEncryptResult getPending() { for (PgpSignEncryptResult sub : mResults) { if (sub.isPending()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index df409902f..c4d0d488e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -202,8 +202,26 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } } - public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext, - Map signedHashes, Date creationTimestamp) + public PGPSignatureGenerator getCertSignatureGenerator(Map signedHashes) { + PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( + PgpConstants.CERTIFY_HASH_ALGO, signedHashes); + + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { + throw new PrivateKeyNotUnlockedException(); + } + + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + try { + signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey); + return signatureGenerator; + } catch (PGPException e) { + Log.e(Constants.TAG, "signing error", e); + return null; + } + } + + public PGPSignatureGenerator getDataSignatureGenerator(int hashAlgo, boolean cleartext, + Map signedHashes, Date creationTimestamp) throws PgpGeneralException { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); @@ -259,135 +277,6 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } } - /** - * Certify the given pubkeyid with the given masterkeyid. - * - * @param publicKeyRing Keyring to add certification to. - * @param userIds User IDs to certify - * @return A keyring with added certifications - */ - public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, - List userIds, - HashMap signedHashes, Date creationTimestamp) { - if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { - throw new PrivateKeyNotUnlockedException(); - } - if (!isMasterKey()) { - throw new AssertionError("tried to certify with non-master key, this is a programming error!"); - } - if (publicKeyRing.getMasterKeyId() == getKeyId()) { - throw new AssertionError("key tried to self-certify, this is a programming error!"); - } - - // create a signatureGenerator from the supplied masterKeyId and passphrase - PGPSignatureGenerator signatureGenerator; - { - PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( - PgpConstants.CERTIFY_HASH_ALGO, signedHashes); - - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - try { - signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey); - } catch (PGPException e) { - Log.e(Constants.TAG, "signing error", e); - return null; - } - } - - { // supply signatureGenerator with a SubpacketVector - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - if (creationTimestamp != null) { - spGen.setSignatureCreationTime(false, creationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp); - } - PGPSignatureSubpacketVector packetVector = spGen.generate(); - signatureGenerator.setHashedSubpackets(packetVector); - } - - // get the master subkey (which we certify for) - PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey(); - - // fetch public key ring, add the certification and return it - try { - for (String userId : userIds) { - PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); - publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); - } - } catch (PGPException e) { - Log.e(Constants.TAG, "signing error", e); - return null; - } - - PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey); - - return new UncachedKeyRing(ring); - } - - /** - * Certify the given user attributes with the given masterkeyid. - * - * @param publicKeyRing Keyring to add certification to. - * @param userAttributes User IDs to certify, or all if null - * @return A keyring with added certifications - */ - public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing, - List userAttributes, - HashMap signedHashes, Date creationTimestamp) { - if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { - throw new PrivateKeyNotUnlockedException(); - } - if (!isMasterKey()) { - throw new AssertionError("tried to certify with non-master key, this is a programming error!"); - } - if (publicKeyRing.getMasterKeyId() == getKeyId()) { - throw new AssertionError("key tried to self-certify, this is a programming error!"); - } - - // create a signatureGenerator from the supplied masterKeyId and passphrase - PGPSignatureGenerator signatureGenerator; - { - PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( - PgpConstants.CERTIFY_HASH_ALGO, signedHashes); - - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - try { - signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey); - } catch (PGPException e) { - Log.e(Constants.TAG, "signing error", e); - return null; - } - } - - { // supply signatureGenerator with a SubpacketVector - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - if (creationTimestamp != null) { - spGen.setSignatureCreationTime(false, creationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp); - } - PGPSignatureSubpacketVector packetVector = spGen.generate(); - signatureGenerator.setHashedSubpackets(packetVector); - } - - // get the master subkey (which we certify for) - PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey(); - - // fetch public key ring, add the certification and return it - try { - for (WrappedUserAttribute userAttribute : userAttributes) { - PGPUserAttributeSubpacketVector vector = userAttribute.getVector(); - PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey); - publicKey = PGPPublicKey.addCertification(publicKey, vector, sig); - } - } catch (PGPException e) { - Log.e(Constants.TAG, "signing error", e); - return null; - } - - PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey); - - return new UncachedKeyRing(ring); - } - static class PrivateKeyNotUnlockedException extends RuntimeException { // this exception is a programming error which happens when an operation which requires // the private key is called without a previous call to unlock() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java new file mode 100644 index 000000000..3c9daadbb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -0,0 +1,149 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + + +public class PgpCertifyOperation { + + public PgpCertifyResult certify( + CanonicalizedSecretKey secretKey, + CanonicalizedPublicKeyRing publicRing, + OperationLog log, + int indent, + CertifyAction action, + Map signedHashes, + Date creationTimestamp) { + + if (!secretKey.isMasterKey()) { + throw new AssertionError("tried to certify with non-master key, this is a programming error!"); + } + if (publicRing.getMasterKeyId() == secretKey.getKeyId()) { + throw new AssertionError("key tried to self-certify, this is a programming error!"); + } + + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator = secretKey.getCertSignatureGenerator(signedHashes); + + { // supply signatureGenerator with a SubpacketVector + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + if (creationTimestamp != null) { + spGen.setSignatureCreationTime(false, creationTimestamp); + Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp); + } + PGPSignatureSubpacketVector packetVector = spGen.generate(); + signatureGenerator.setHashedSubpackets(packetVector); + } + + // get the master subkey (which we certify for) + PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey(); + + NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp); + + try { + if (action.mUserIds != null) { + log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(), + KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); + + // fetch public key ring, add the certification and return it + for (String userId : action.mUserIds) { + try { + PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); + } catch (NfcInteractionNeeded e) { + requiredInput.addHash(e.hashToSign, e.hashAlgo); + } + } + + } + + if (action.mUserAttributes != null) { + log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(), + KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); + + // fetch public key ring, add the certification and return it + for (WrappedUserAttribute userAttribute : action.mUserAttributes) { + PGPUserAttributeSubpacketVector vector = userAttribute.getVector(); + try { + PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, vector, sig); + } catch (NfcInteractionNeeded e) { + requiredInput.addHash(e.hashToSign, e.hashAlgo); + } + } + + } + } catch (PGPException e) { + Log.e(Constants.TAG, "signing error", e); + return new PgpCertifyResult(); + } + + if (!requiredInput.isEmpty()) { + return new PgpCertifyResult(requiredInput.build()); + } + + PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicRing.getRing(), publicKey); + return new PgpCertifyResult(new UncachedKeyRing(ring)); + + } + + public static class PgpCertifyResult { + + final NfcOperationsParcel mRequiredInput; + final UncachedKeyRing mCertifiedRing; + + PgpCertifyResult() { + mRequiredInput = null; + mCertifiedRing = null; + } + + PgpCertifyResult(NfcOperationsParcel requiredInput) { + mRequiredInput = requiredInput; + mCertifiedRing = null; + } + + PgpCertifyResult(UncachedKeyRing certifiedRing) { + mRequiredInput = null; + mCertifiedRing = certifiedRing; + } + + public boolean success() { + return mCertifiedRing != null || mRequiredInput != null; + } + + public boolean nfcInputRequired() { + return mRequiredInput != null; + } + + public UncachedKeyRing getCertifiedRing() { + return mCertifiedRing; + } + + public NfcOperationsParcel getRequiredInput() { + return mRequiredInput; + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 2e515137a..7253d9b18 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -44,8 +44,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -283,7 +281,7 @@ public class PgpSignEncryptOperation extends BaseOperation { try { boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption; - signatureGenerator = signingKey.getSignatureGenerator( + signatureGenerator = signingKey.getDataSignatureGenerator( input.getSignatureHashAlgorithm(), cleartext, input.getCryptoData(), input.getSignatureTime()); } catch (PgpGeneralException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index 485d5de72..63a7bf371 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -22,8 +22,10 @@ import android.os.Parcel; import android.os.Parcelable; import java.io.Serializable; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Date; +import java.util.Map; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -42,9 +44,9 @@ public class CertifyActionsParcel implements Parcelable { public ArrayList mCertifyActions = new ArrayList<>(); public CryptoInputParcel mCryptoInput; - public CertifyActionsParcel(Date operationTime, long masterKeyId) { + public CertifyActionsParcel(CryptoInputParcel cryptoInput, long masterKeyId) { mMasterKeyId = masterKeyId; - mCryptoInput = new CryptoInputParcel(operationTime); + mCryptoInput = cryptoInput != null ? cryptoInput : new CryptoInputParcel(new Date()); mLevel = CertifyLevel.DEFAULT; } @@ -70,6 +72,14 @@ public class CertifyActionsParcel implements Parcelable { destination.writeSerializable(mCertifyActions); } + public Map getSignatureData() { + return mCryptoInput.getCryptoData(); + } + + public Date getSignatureTime() { + return mCryptoInput.getSignatureTime(); + } + public static final Creator CREATOR = new Creator() { public CertifyActionsParcel createFromParcel(final Parcel source) { return new CertifyActionsParcel(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index bd047518d..05d80b4e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.service; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -26,6 +27,7 @@ import android.support.v4.app.FragmentManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java index 5d35e94e0..b9ee9e6ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java @@ -1,5 +1,7 @@ package org.sufficientlysecure.keychain.service.input; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import android.os.Parcel; @@ -14,13 +16,14 @@ public class NfcOperationsParcel implements Parcelable { public Date mSignatureTime; public final NfcOperationType mType; - public final byte[][] mInputHash; - public final int[] mSignAlgo; + public final byte[][] mInputHashes; + public final int[] mSignAlgos; - private NfcOperationsParcel(NfcOperationType type, byte[] inputHash, int signAlgo, Date signatureTime) { + private NfcOperationsParcel(NfcOperationType type, byte[][] inputHashes, + int[] signAlgos, Date signatureTime) { mType = type; - mInputHash = new byte[][] { inputHash }; - mSignAlgo = new int[] { signAlgo }; + mInputHashes = inputHashes; + mSignAlgos = signAlgos; mSignatureTime = signatureTime; } @@ -29,11 +32,11 @@ public class NfcOperationsParcel implements Parcelable { { int count = source.readInt(); - mInputHash = new byte[count][]; - mSignAlgo = new int[count]; + mInputHashes = new byte[count][]; + mSignAlgos = new int[count]; for (int i = 0; i < count; i++) { - mInputHash[i] = source.createByteArray(); - mSignAlgo[i] = source.readInt(); + mInputHashes[i] = source.createByteArray(); + mSignAlgos[i] = source.readInt(); } } @@ -43,11 +46,13 @@ public class NfcOperationsParcel implements Parcelable { public static NfcOperationsParcel createNfcSignOperation( byte[] inputHash, int signAlgo, Date signatureTime) { - return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, inputHash, signAlgo, signatureTime); + return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, + new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime); } public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) { - return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, inputHash, 0, null); + return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, + new byte[][] { inputHash }, null, null); } @Override @@ -58,10 +63,10 @@ public class NfcOperationsParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType.ordinal()); - dest.writeInt(mInputHash.length); - for (int i = 0; i < mInputHash.length; i++) { - dest.writeByteArray(mInputHash[i]); - dest.writeInt(mSignAlgo[i]); + dest.writeInt(mInputHashes.length); + for (int i = 0; i < mInputHashes.length; i++) { + dest.writeByteArray(mInputHashes[i]); + dest.writeInt(mSignAlgos[i]); } if (mSignatureTime != null) { dest.writeInt(1); @@ -82,4 +87,50 @@ public class NfcOperationsParcel implements Parcelable { } }; + public static class NfcSignOperationsBuilder { + Date mSignatureTime; + ArrayList mSignAlgos = new ArrayList<>(); + ArrayList mInputHashes = new ArrayList<>(); + + public NfcSignOperationsBuilder(Date signatureTime) { + mSignatureTime = signatureTime; + } + + public NfcOperationsParcel build() { + byte[][] inputHashes = new byte[mInputHashes.size()][]; + mInputHashes.toArray(inputHashes); + int[] signAlgos = new int[mSignAlgos.size()]; + for (int i = 0; i < mSignAlgos.size(); i++) { + signAlgos[i] = mSignAlgos.get(i); + } + + return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, + inputHashes, signAlgos, mSignatureTime); + } + + public void addHash(byte[] hash, int algo) { + mInputHashes.add(hash); + mSignAlgos.add(algo); + } + + public void addAll(NfcOperationsParcel input) { + if (!mSignatureTime.equals(input.mSignatureTime)) { + throw new AssertionError("input times must match, this is a programming error!"); + } + if (input.mType != NfcOperationType.NFC_SIGN) { + throw new AssertionError("operation types must match, this is a progrmming error!"); + } + + Collections.addAll(mInputHashes, input.mInputHashes); + for (int signAlgo : input.mSignAlgos) { + mSignAlgos.add(signAlgo); + } + } + + public boolean isEmpty() { + return mInputHashes.isEmpty(); + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 049c6976d..acb6b0b85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -56,7 +56,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; @@ -66,14 +66,11 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Date; -public class CertifyKeyFragment extends LoaderFragment +public class CertifyKeyFragment extends CryptoOperationFragment implements LoaderManager.LoaderCallbacks { - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; - private CheckBox mUploadKeyCheckbox; ListView mUserIds; @@ -102,9 +99,6 @@ public class CertifyKeyFragment extends LoaderFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // Start out with a progress indicator. - setContentShown(false); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); if (mPubMasterKeyIds == null) { Log.e(Constants.TAG, "List of key ids to certify missing!"); @@ -114,6 +108,7 @@ public class CertifyKeyFragment extends LoaderFragment mPassthroughMessenger = getActivity().getIntent().getParcelableExtra( KeychainIntentService.EXTRA_MESSENGER); + mPassthroughMessenger = null; // TODO remove, development hack // preselect certify key id if given long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); @@ -143,9 +138,7 @@ public class CertifyKeyFragment extends LoaderFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - - View view = inflater.inflate(R.layout.certify_key_fragment, getContainer()); + View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); @@ -173,7 +166,7 @@ public class CertifyKeyFragment extends LoaderFragment Notify.showNotify(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR); } else { - initiateCertifying(); + cryptoOperation(); } } }); @@ -183,7 +176,7 @@ public class CertifyKeyFragment extends LoaderFragment mUploadKeyCheckbox.setChecked(false); } - return root; + return view; } @Override @@ -307,7 +300,6 @@ public class CertifyKeyFragment extends LoaderFragment } mUserIdsAdapter.swapCursor(matrix); - setContentShown(true, isResumed()); } @Override @@ -315,50 +307,17 @@ public class CertifyKeyFragment extends LoaderFragment mUserIdsAdapter.swapCursor(null); } - /** - * handles the UI bits of the signing process on the UI thread - */ - private void initiateCertifying() { - // get the user's passphrase for this key (if required) - String passphrase; - try { - passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), mSignMasterKeyId, mSignMasterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - Log.e(Constants.TAG, "Key not found!", e); - getActivity().finish(); - return; - } - if (passphrase == null) { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSignMasterKeyId); - startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); - // bail out; need to wait until the user has entered the passphrase before trying again - } else { - startCertifying(); - } + protected void cryptoOperation() { + cryptoOperation((CryptoInputParcel) null); } @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PASSPHRASE: { - if (resultCode == Activity.RESULT_OK && data != null) { - String passphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); - startCertifying(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } + protected void cryptoOperation(String passphrase) { + cryptoOperation((CryptoInputParcel) null); } - /** - * kicks off the actual signing process on a background thread - */ - private void startCertifying() { + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { // Bail out if there is not at least one user id selected ArrayList certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { @@ -370,7 +329,7 @@ public class CertifyKeyFragment extends LoaderFragment Bundle data = new Bundle(); { // fill values for this action - CertifyActionsParcel parcel = new CertifyActionsParcel(new Date(), mSignMasterKeyId); + CertifyActionsParcel parcel = new CertifyActionsParcel(cryptoInput, mSignMasterKeyId); parcel.mCertifyActions.addAll(certifyActions); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); @@ -390,14 +349,21 @@ public class CertifyKeyFragment extends LoaderFragment } else { // Message is received after signing is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), - getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + getActivity(), getString(R.string.progress_certifying), + ProgressDialog.STYLE_SPINNER, true) { public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first + // handle messages by KeychainIntentCryptoServiceHandler first super.handleMessage(message); + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + if (message.arg1 == MessageStatus.OKAY.ordinal()) { Bundle data = message.getData(); + CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java new file mode 100644 index 000000000..a1d70b011 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -0,0 +1,89 @@ +package org.sufficientlysecure.keychain.ui; + + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.support.v4.app.Fragment; + +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.InputPendingResult; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; + + +public abstract class CryptoOperationFragment extends Fragment { + + public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; + public static final int REQUEST_CODE_NFC = 0x00008002; + + private void startPassphraseDialog(long subkeyId) { + Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + } + + private void initiateNfcInput(NfcOperationsParcel nfcOps) { + Intent intent = new Intent(getActivity(), NfcOperationActivity.class); + intent.putExtra(NfcOperationActivity.EXTRA_PIN, "123456"); + intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps); + startActivityForResult(intent, REQUEST_CODE_NFC); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: { + if (resultCode == Activity.RESULT_OK && data != null) { + String passphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); + cryptoOperation(passphrase); + } + return; + } + + case REQUEST_CODE_NFC: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); + cryptoOperation(cryptoInput); + return; + } + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + public boolean handlePendingMessage(Message message) { + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + Bundle data = message.getData(); + + InputPendingResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + + if (result != null && result.isPending()) { + if (result.isPassphrasePending()) { + startPassphraseDialog(result.getPassphraseKeyId()); + return true; + } else if (result.isNfcPending()) { + NfcOperationsParcel requiredInput = result.getNfcOperationsParcel(); + initiateNfcInput(requiredInput); + return true; + } else { + throw new RuntimeException("Unhandled pending result!"); + } + } + } + + return false; + } + + protected abstract void cryptoOperation(CryptoInputParcel cryptoInput); + protected abstract void cryptoOperation(String passphrase); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index c1403d748..f226e71c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -182,17 +182,17 @@ public class NfcOperationActivity extends BaseActivity { case NFC_DECRYPT: - for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { - byte[] hash = mNfcOperations.mInputHash[i]; + for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) { + byte[] hash = mNfcOperations.mInputHashes[i]; byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); resultData.addCryptoData(hash, decryptedSessionKey); } break; case NFC_SIGN: - for (int i = 0; i < mNfcOperations.mInputHash.length; i++) { - byte[] hash = mNfcOperations.mInputHash[i]; - int algo = mNfcOperations.mSignAlgo[i]; + for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) { + byte[] hash = mNfcOperations.mInputHashes[i]; + int algo = mNfcOperations.mSignAlgos[i]; byte[] signedHash = nfcCalculateSignature(hash, algo); resultData.addCryptoData(hash, signedHash); } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f1c14bd32..2eb167c8c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1099,9 +1099,9 @@ "Master key not found!" "No keys certified!" "Error unlocking master key!" - "Certification with NFC is not (yet) supported!" "Certifying keyrings" "Fetching certifying master key" + "Returning for NFC input" "Saving certified key %s" "Saving keyrings" "Unlocking master key" From 25d89b5550b7fd699988954b07cad61bee9a8ba5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 19 Mar 2015 14:21:30 +0100 Subject: [PATCH 04/80] generalize NfcOperationParcel to RequiredInputParcel, including passphrases --- .../keychain/operations/CertifyOperation.java | 17 ++-- .../operations/results/CertifyResult.java | 4 +- .../results/InputPendingResult.java | 8 +- .../operations/results/SignEncryptResult.java | 2 - .../keychain/pgp/PgpCertifyOperation.java | 11 ++- .../service/input/CryptoInputParcel.java | 19 ++++- ...nsParcel.java => RequiredInputParcel.java} | 84 ++++++++++++------- .../keychain/ui/CertifyKeyFragment.java | 11 +-- .../keychain/ui/CryptoOperationFragment.java | 52 ++++++------ .../keychain/ui/EncryptActivity.java | 8 +- .../keychain/ui/NfcOperationActivity.java | 8 +- .../keychain/ui/PassphraseDialogActivity.java | 18 +++- 12 files changed, 147 insertions(+), 95 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/{NfcOperationsParcel.java => RequiredInputParcel.java} (53%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 140e03764..cc4ca88cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -28,7 +28,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation; import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; @@ -40,8 +39,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -78,8 +77,13 @@ public class CertifyOperation extends BaseOperation { log.add(LogType.MSG_CRT_UNLOCK, 1); certificationKey = secretKeyRing.getSecretKey(); + if (!parcel.mCryptoInput.hasPassphrase()) { + return new CertifyResult(log, RequiredInputParcel.createRequiredPassphrase( + certificationKey.getKeyId(), null)); + } + // certification is always with the master key id, so use that one - String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); + String passphrase = parcel.mCryptoInput.getPassphrase(); if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); @@ -91,9 +95,6 @@ public class CertifyOperation extends BaseOperation { } catch (NotFoundException e) { log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); - } catch (NoSecretKeyException e) { - log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2); - return new CertifyResult(CertifyResult.RESULT_ERROR, log); } ArrayList certifiedKeys = new ArrayList<>(); @@ -133,7 +134,7 @@ public class CertifyOperation extends BaseOperation { continue; } if (result.nfcInputRequired()) { - NfcOperationsParcel requiredInput = result.getRequiredInput(); + RequiredInputParcel requiredInput = result.getRequiredInput(); allRequiredInput.addAll(requiredInput); continue; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index 33591fa03..582ed2fc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java @@ -23,7 +23,7 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -38,7 +38,7 @@ public class CertifyResult extends InputPendingResult { super(result, log); } - public CertifyResult(OperationLog log, NfcOperationsParcel requiredInput) { + public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) { super(log, requiredInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java index b681aba60..07d42c456 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; public class InputPendingResult extends OperationResult { @@ -14,7 +14,7 @@ public class InputPendingResult extends OperationResult { public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16; public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32; - final NfcOperationsParcel mRequiredInput; + final RequiredInputParcel mRequiredInput; final Long mKeyIdPassphraseNeeded; public InputPendingResult(int result, OperationLog log) { @@ -23,7 +23,7 @@ public class InputPendingResult extends OperationResult { mKeyIdPassphraseNeeded = null; } - public InputPendingResult(OperationLog log, NfcOperationsParcel requiredInput) { + public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) { super(RESULT_PENDING_NFC, log); mRequiredInput = requiredInput; mKeyIdPassphraseNeeded = null; @@ -65,7 +65,7 @@ public class InputPendingResult extends OperationResult { return (mResult & RESULT_PENDING_PASSPHRASE) == RESULT_PENDING_PASSPHRASE; } - public NfcOperationsParcel getNfcOperationsParcel() { + public RequiredInputParcel getRequiredInputParcel() { return mRequiredInput; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index 5a0a51ee5..87483ade9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -21,8 +21,6 @@ import android.os.Parcel; import java.util.ArrayList; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; - public class SignEncryptResult extends OperationResult { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index 3c9daadbb..ff162775a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -3,7 +3,6 @@ package org.sufficientlysecure.keychain.pgp; import java.nio.ByteBuffer; import java.util.Date; -import java.util.HashMap; import java.util.Map; import org.spongycastle.openpgp.PGPException; @@ -19,8 +18,8 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -110,7 +109,7 @@ public class PgpCertifyOperation { public static class PgpCertifyResult { - final NfcOperationsParcel mRequiredInput; + final RequiredInputParcel mRequiredInput; final UncachedKeyRing mCertifiedRing; PgpCertifyResult() { @@ -118,7 +117,7 @@ public class PgpCertifyOperation { mCertifiedRing = null; } - PgpCertifyResult(NfcOperationsParcel requiredInput) { + PgpCertifyResult(RequiredInputParcel requiredInput) { mRequiredInput = requiredInput; mCertifiedRing = null; } @@ -140,7 +139,7 @@ public class PgpCertifyOperation { return mCertifiedRing; } - public NfcOperationsParcel getRequiredInput() { + public RequiredInputParcel getRequiredInput() { return mRequiredInput; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index e02eda4b3..7cc5069c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -15,18 +15,26 @@ import android.os.Parcelable; */ public class CryptoInputParcel implements Parcelable { - Date mSignatureTime; + final Date mSignatureTime; + final String mPassphrase; // this map contains both decrypted session keys and signed hashes to be // used in the crypto operation described by this parcel. private HashMap mCryptoData = new HashMap<>(); + public CryptoInputParcel(Date signatureTime, String passphrase) { + mSignatureTime = signatureTime == null ? new Date() : signatureTime; + mPassphrase = passphrase; + } + public CryptoInputParcel(Date signatureTime) { mSignatureTime = signatureTime == null ? new Date() : signatureTime; + mPassphrase = null; } protected CryptoInputParcel(Parcel source) { mSignatureTime = new Date(source.readLong()); + mPassphrase = source.readString(); { int count = source.readInt(); @@ -48,6 +56,7 @@ public class CryptoInputParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mSignatureTime.getTime()); + dest.writeString(mPassphrase); dest.writeInt(mCryptoData.size()); for (HashMap.Entry entry : mCryptoData.entrySet()) { @@ -68,6 +77,14 @@ public class CryptoInputParcel implements Parcelable { return mSignatureTime; } + public boolean hasPassphrase() { + return mPassphrase != null; + } + + public String getPassphrase() { + return mPassphrase; + } + public static final Creator CREATOR = new Creator() { public CryptoInputParcel createFromParcel(final Parcel source) { return new CryptoInputParcel(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java similarity index 53% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index b9ee9e6ca..a4df604be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/NfcOperationsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -8,29 +8,31 @@ import android.os.Parcel; import android.os.Parcelable; -public class NfcOperationsParcel implements Parcelable { +public class RequiredInputParcel implements Parcelable { - public enum NfcOperationType { - NFC_SIGN, NFC_DECRYPT + public enum RequiredInputType { + PASSPHRASE, NFC_SIGN, NFC_DECRYPT } public Date mSignatureTime; - public final NfcOperationType mType; + public final RequiredInputType mType; public final byte[][] mInputHashes; public final int[] mSignAlgos; + private Long mSubKeyId; - private NfcOperationsParcel(NfcOperationType type, byte[][] inputHashes, - int[] signAlgos, Date signatureTime) { + private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, + int[] signAlgos, Date signatureTime, Long keyId) { mType = type; mInputHashes = inputHashes; mSignAlgos = signAlgos; mSignatureTime = signatureTime; + mSubKeyId = keyId; } - public NfcOperationsParcel(Parcel source) { - mType = NfcOperationType.values()[source.readInt()]; + public RequiredInputParcel(Parcel source) { + mType = RequiredInputType.values()[source.readInt()]; - { + if (source.readInt() != 0) { int count = source.readInt(); mInputHashes = new byte[count][]; mSignAlgos = new int[count]; @@ -38,21 +40,34 @@ public class NfcOperationsParcel implements Parcelable { mInputHashes[i] = source.createByteArray(); mSignAlgos[i] = source.readInt(); } + } else { + mInputHashes = null; + mSignAlgos = null; } mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null; + mSubKeyId = source.readInt() != 0 ? source.readLong() : null; } - public static NfcOperationsParcel createNfcSignOperation( + public long getSubKeyId() { + return mSubKeyId; + } + + public static RequiredInputParcel createNfcSignOperation( byte[] inputHash, int signAlgo, Date signatureTime) { - return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, - new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime); + return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, null); } - public static NfcOperationsParcel createNfcDecryptOperation(byte[] inputHash) { - return new NfcOperationsParcel(NfcOperationType.NFC_DECRYPT, - new byte[][] { inputHash }, null, null); + public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash) { + return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, + new byte[][] { inputHash }, null, null, null); + } + + public static RequiredInputParcel createRequiredPassphrase(long keyId, Date signatureTime) { + return new RequiredInputParcel(RequiredInputType.PASSPHRASE, + null, null, signatureTime, keyId); } @Override @@ -63,10 +78,15 @@ public class NfcOperationsParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType.ordinal()); - dest.writeInt(mInputHashes.length); - for (int i = 0; i < mInputHashes.length; i++) { - dest.writeByteArray(mInputHashes[i]); - dest.writeInt(mSignAlgos[i]); + if (mInputHashes != null) { + dest.writeInt(1); + dest.writeInt(mInputHashes.length); + for (int i = 0; i < mInputHashes.length; i++) { + dest.writeByteArray(mInputHashes[i]); + dest.writeInt(mSignAlgos[i]); + } + } else { + dest.writeInt(0); } if (mSignatureTime != null) { dest.writeInt(1); @@ -74,16 +94,22 @@ public class NfcOperationsParcel implements Parcelable { } else { dest.writeInt(0); } + if (mSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSubKeyId); + } else { + dest.writeInt(0); + } } - public static final Creator CREATOR = new Creator() { - public NfcOperationsParcel createFromParcel(final Parcel source) { - return new NfcOperationsParcel(source); + public static final Creator CREATOR = new Creator() { + public RequiredInputParcel createFromParcel(final Parcel source) { + return new RequiredInputParcel(source); } - public NfcOperationsParcel[] newArray(final int size) { - return new NfcOperationsParcel[size]; + public RequiredInputParcel[] newArray(final int size) { + return new RequiredInputParcel[size]; } }; @@ -96,7 +122,7 @@ public class NfcOperationsParcel implements Parcelable { mSignatureTime = signatureTime; } - public NfcOperationsParcel build() { + public RequiredInputParcel build() { byte[][] inputHashes = new byte[mInputHashes.size()][]; mInputHashes.toArray(inputHashes); int[] signAlgos = new int[mSignAlgos.size()]; @@ -104,8 +130,8 @@ public class NfcOperationsParcel implements Parcelable { signAlgos[i] = mSignAlgos.get(i); } - return new NfcOperationsParcel(NfcOperationType.NFC_SIGN, - inputHashes, signAlgos, mSignatureTime); + return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + inputHashes, signAlgos, mSignatureTime, null); } public void addHash(byte[] hash, int algo) { @@ -113,11 +139,11 @@ public class NfcOperationsParcel implements Parcelable { mSignAlgos.add(algo); } - public void addAll(NfcOperationsParcel input) { + public void addAll(RequiredInputParcel input) { if (!mSignatureTime.equals(input.mSignatureTime)) { throw new AssertionError("input times must match, this is a programming error!"); } - if (input.mType != NfcOperationType.NFC_SIGN) { + if (input.mType != RequiredInputType.NFC_SIGN) { throw new AssertionError("operation types must match, this is a progrmming error!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index acb6b0b85..711e2f2e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -166,7 +166,7 @@ public class CertifyKeyFragment extends CryptoOperationFragment Notify.showNotify(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR); } else { - cryptoOperation(); + cryptoOperation(null); } } }); @@ -307,15 +307,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment mUserIdsAdapter.swapCursor(null); } - protected void cryptoOperation() { - cryptoOperation((CryptoInputParcel) null); - } - - @Override - protected void cryptoOperation(String passphrase) { - cryptoOperation((CryptoInputParcel) null); - } - @Override protected void cryptoOperation(CryptoInputParcel cryptoInput) { // Bail out if there is not at least one user id selected diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java index a1d70b011..585a9433a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -11,7 +11,7 @@ import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; public abstract class CryptoOperationFragment extends Fragment { @@ -19,17 +19,28 @@ public abstract class CryptoOperationFragment extends Fragment { public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; public static final int REQUEST_CODE_NFC = 0x00008002; - private void startPassphraseDialog(long subkeyId) { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId); - startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); - } + private void initiateInputActivity(RequiredInputParcel requiredInput) { + + switch (requiredInput.mType) { + case NFC_DECRYPT: + case NFC_SIGN: { + Intent intent = new Intent(getActivity(), NfcOperationActivity.class); + intent.putExtra(NfcOperationActivity.EXTRA_PIN, "123456"); + intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); + startActivityForResult(intent, REQUEST_CODE_NFC); + return; + } + + case PASSPHRASE: { + Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + return; + } + } + + throw new RuntimeException("Unhandled pending result!"); - private void initiateNfcInput(NfcOperationsParcel nfcOps) { - Intent intent = new Intent(getActivity(), NfcOperationActivity.class); - intent.putExtra(NfcOperationActivity.EXTRA_PIN, "123456"); - intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps); - startActivityForResult(intent, REQUEST_CODE_NFC); } @Override @@ -37,8 +48,9 @@ public abstract class CryptoOperationFragment extends Fragment { switch (requestCode) { case REQUEST_CODE_PASSPHRASE: { if (resultCode == Activity.RESULT_OK && data != null) { - String passphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); - cryptoOperation(passphrase); + CryptoInputParcel cryptoInput = + data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); + cryptoOperation(cryptoInput); } return; } @@ -67,16 +79,9 @@ public abstract class CryptoOperationFragment extends Fragment { InputPendingResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); if (result != null && result.isPending()) { - if (result.isPassphrasePending()) { - startPassphraseDialog(result.getPassphraseKeyId()); - return true; - } else if (result.isNfcPending()) { - NfcOperationsParcel requiredInput = result.getNfcOperationsParcel(); - initiateNfcInput(requiredInput); - return true; - } else { - throw new RuntimeException("Unhandled pending result!"); - } + RequiredInputParcel requiredInput = result.getRequiredInputParcel(); + initiateInputActivity(requiredInput); + return true; } } @@ -84,6 +89,5 @@ public abstract class CryptoOperationFragment extends Fragment { } protected abstract void cryptoOperation(CryptoInputParcel cryptoInput); - protected abstract void cryptoOperation(String passphrase); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 75f22f01c..f9b8e2ab6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -32,7 +32,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; public abstract class EncryptActivity extends BaseActivity { @@ -62,11 +62,11 @@ public abstract class EncryptActivity extends BaseActivity { startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); } - protected void startNfcSign(long keyId, String pin, NfcOperationsParcel nfcOps) { + protected void startNfcSign(long keyId, String pin, RequiredInputParcel nfcOps) { Intent intent = new Intent(this, NfcOperationActivity.class); intent.putExtra(NfcOperationActivity.EXTRA_PIN, pin); - intent.putExtra(NfcOperationActivity.EXTRA_NFC_OPS, nfcOps); + intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, nfcOps); // TODO respect keyid(?) startActivityForResult(intent, REQUEST_CODE_NFC); @@ -144,7 +144,7 @@ public abstract class EncryptActivity extends BaseActivity { } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == PgpSignEncryptResult.RESULT_PENDING_NFC) { - NfcOperationsParcel parcel = NfcOperationsParcel.createNfcSignOperation( + RequiredInputParcel parcel = RequiredInputParcel.createNfcSignOperation( pgpResult.getNfcHash(), pgpResult.getNfcAlgo(), input.getSignatureTime()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index f226e71c0..6a83c2361 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -23,7 +23,7 @@ import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.NfcOperationsParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; @@ -41,7 +41,7 @@ import java.nio.ByteBuffer; public class NfcOperationActivity extends BaseActivity { public static final String EXTRA_PIN = "pin"; - public static final String EXTRA_NFC_OPS = "nfc_operations"; + public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String RESULT_DATA = "result_data"; @@ -52,7 +52,7 @@ public class NfcOperationActivity extends BaseActivity { private String mPin; - NfcOperationsParcel mNfcOperations; + RequiredInputParcel mNfcOperations; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,7 +69,7 @@ public class NfcOperationActivity extends BaseActivity { Bundle data = intent.getExtras(); - mNfcOperations = data.getParcelable(EXTRA_NFC_OPS); + mNfcOperations = data.getParcelable(EXTRA_REQUIRED_INPUT); mPin = data.getString(EXTRA_PIN); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 48509710a..ca65f47cd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -41,6 +41,7 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import junit.framework.Assert; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; @@ -53,6 +54,9 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; @@ -63,7 +67,9 @@ import org.sufficientlysecure.keychain.util.Preferences; */ public class PassphraseDialogActivity extends FragmentActivity { public static final String MESSAGE_DATA_PASSPHRASE = "passphrase"; + public static final String RESULT_DATA = "result_data"; + public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_SUBKEY_ID = "secret_key_id"; // special extra for OpenPgpService @@ -86,7 +92,16 @@ public class PassphraseDialogActivity extends FragmentActivity { // this activity itself has no content view (see manifest) - long keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0); + long keyId; + if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) { + keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0); + } else { + RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT); + if (requiredInput.mType != RequiredInputType.PASSPHRASE) { + throw new AssertionError("Wrong required input type for PassphraseDialogActivity!"); + } + keyId = requiredInput.getSubKeyId(); + } Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_DATA); @@ -410,6 +425,7 @@ public class PassphraseDialogActivity extends FragmentActivity { // also return passphrase back to activity Intent returnIntent = new Intent(); returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase); + returnIntent.putExtra(RESULT_DATA, new CryptoInputParcel(null, passphrase)); getActivity().setResult(RESULT_OK, returnIntent); } From 3b04636f5daf3d171449296a5d9a67440abfbf75 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 02:27:05 +0100 Subject: [PATCH 05/80] support yubikeys in (some) edit key operations --- .../keychain/operations/CertifyOperation.java | 6 +- .../keychain/operations/EditKeyOperation.java | 11 +- .../results/InputPendingResult.java | 33 +- .../operations/results/OperationResult.java | 3 +- .../operations/results/PgpEditKeyResult.java | 9 +- .../keychain/pgp/CanonicalizedSecretKey.java | 8 +- .../keychain/pgp/PgpKeyOperation.java | 310 ++++++++++++------ .../service/CertifyActionsParcel.java | 4 - .../service/KeychainIntentService.java | 6 +- .../service/input/CryptoInputParcel.java | 19 +- .../service/input/RequiredInputParcel.java | 4 + .../keychain/ui/CryptoOperationFragment.java | 12 +- .../keychain/ui/EditKeyFragment.java | 86 ++--- .../keychain/ui/EncryptActivity.java | 6 +- .../keychain/ui/NfcOperationActivity.java | 10 +- .../dialog/SetPassphraseDialogFragment.java | 12 +- OpenKeychain/src/main/res/values/strings.xml | 3 +- 17 files changed, 295 insertions(+), 247 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index cc4ca88cb..bcdbf5f67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -83,7 +83,7 @@ public class CertifyOperation extends BaseOperation { } // certification is always with the master key id, so use that one - String passphrase = parcel.mCryptoInput.getPassphrase(); + char[] passphrase = parcel.mCryptoInput.getPassphrase(); if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); @@ -103,7 +103,7 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; - NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.getSignatureTime()); + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.mCryptoInput.getSignatureTime()); // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { @@ -127,7 +127,7 @@ public class CertifyOperation extends BaseOperation { PgpCertifyOperation op = new PgpCertifyOperation(); PgpCertifyResult result = op.certify(certificationKey, publicRing, - log, 2, action, parcel.getSignatureData(), parcel.getSignatureTime()); + log, 2, action, parcel.getSignatureData(), parcel.mCryptoInput.getSignatureTime()); if (!result.success()) { certifyError += 1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index bcd842dd0..a03664091 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -21,6 +21,7 @@ import android.content.Context; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -55,7 +57,7 @@ public class EditKeyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public EditKeyResult execute(SaveKeyringParcel saveParcel, String passphrase) { + public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); log.add(LogType.MSG_ED, 0); @@ -69,7 +71,7 @@ public class EditKeyOperation extends BaseOperation { PgpEditKeyResult modifyResult; { PgpKeyOperation keyOperations = - new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled); + new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled, cryptoInput); // If a key id is specified, fetch and edit if (saveParcel.mMasterKeyId != null) { @@ -80,7 +82,10 @@ public class EditKeyOperation extends BaseOperation { CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); + modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel); + if (modifyResult.isPending()) { + return modifyResult; + } } catch (NotFoundException e) { log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java index 07d42c456..e0ba28fbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -11,66 +11,35 @@ public class InputPendingResult extends OperationResult { // the fourth bit indicates a "data pending" result! (it's also a form of non-success) public static final int RESULT_PENDING = RESULT_ERROR + 8; - public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16; - public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32; - final RequiredInputParcel mRequiredInput; - final Long mKeyIdPassphraseNeeded; public InputPendingResult(int result, OperationLog log) { super(result, log); mRequiredInput = null; - mKeyIdPassphraseNeeded = null; } public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) { - super(RESULT_PENDING_NFC, log); + super(RESULT_PENDING, log); mRequiredInput = requiredInput; - mKeyIdPassphraseNeeded = null; - } - - public InputPendingResult(OperationLog log, long keyIdPassphraseNeeded) { - super(RESULT_PENDING_PASSPHRASE, log); - mRequiredInput = null; - mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; } public InputPendingResult(Parcel source) { super(source); mRequiredInput = source.readParcelable(getClass().getClassLoader()); - mKeyIdPassphraseNeeded = source.readInt() != 0 ? source.readLong() : null; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(mRequiredInput, 0); - if (mKeyIdPassphraseNeeded != null) { - dest.writeInt(1); - dest.writeLong(mKeyIdPassphraseNeeded); - } else { - dest.writeInt(0); - } } public boolean isPending() { return (mResult & RESULT_PENDING) == RESULT_PENDING; } - public boolean isNfcPending() { - return (mResult & RESULT_PENDING_NFC) == RESULT_PENDING_NFC; - } - - public boolean isPassphrasePending() { - return (mResult & RESULT_PENDING_PASSPHRASE) == RESULT_PENDING_PASSPHRASE; - } - public RequiredInputParcel getRequiredInputParcel() { return mRequiredInput; } - public long getPassphraseKeyId() { - return mKeyIdPassphraseNeeded; - } - } 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 989fb395e..25abab25b 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 @@ -512,6 +512,7 @@ public abstract class OperationResult implements Parcelable { // secret key modify MSG_MF (LogLevel.START, R.string.msg_mr), + MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert), MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial), MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode), MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint), @@ -529,6 +530,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary), MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig), MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), + MSG_MF_INPUT_REQUIRED (LogLevel.OK, R.string.msg_mf_input_required), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), @@ -596,7 +598,6 @@ public abstract class OperationResult implements Parcelable { MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success), // messages used in UI code - MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert), MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy), MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java index 611353ac9..38edbf6ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java @@ -22,8 +22,10 @@ import android.os.Parcel; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -public class PgpEditKeyResult extends OperationResult { + +public class PgpEditKeyResult extends InputPendingResult { private transient UncachedKeyRing mRing; public final long mRingMasterKeyId; @@ -35,6 +37,11 @@ public class PgpEditKeyResult extends OperationResult { mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; } + public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) { + super(log, requiredInput); + mRingMasterKeyId = Constants.key.none; + } + public UncachedKeyRing getRing() { return mRing; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index c4d0d488e..36ab9dc1d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -149,10 +149,14 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } + public boolean unlock(String passphrase) throws PgpGeneralException { + return unlock(passphrase.toCharArray()); + } + /** * Returns true on right passphrase */ - public boolean unlock(String passphrase) throws PgpGeneralException { + public boolean unlock(char[] passphrase) throws PgpGeneralException { // handle keys on OpenPGP cards like they were unlocked if (mSecretKey.getS2K() != null && mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K @@ -164,7 +168,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // try to extract keys using the passphrase try { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase); mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor); mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED; } catch (PGPException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 8fb5392e3..fb9edb1cd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.pgp; -import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.S2K; import org.spongycastle.bcpg.sig.Features; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.spec.ElGamalParameterSpec; @@ -36,6 +36,7 @@ import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; import org.spongycastle.openpgp.operator.PGPDigestCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; @@ -43,6 +44,8 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBu import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -54,6 +57,9 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -85,9 +91,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * This indicator may be null. */ public class PgpKeyOperation { + private Stack mProgress; private AtomicBoolean mCancelled; + NfcSignOperationsBuilder mNfcSignOps; + private CryptoInputParcel mCryptoInput; + public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { @@ -96,9 +106,11 @@ public class PgpKeyOperation { } } - public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled) { + public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled, CryptoInputParcel cryptoInput) { this(progress); mCancelled = cancelled; + mNfcSignOps = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); + mCryptoInput = cryptoInput; } private boolean checkCancelled() { @@ -316,7 +328,8 @@ public class PgpKeyOperation { masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); subProgressPush(50, 100); - return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, "", log); + CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), ""); + return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, log); } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -347,8 +360,8 @@ public class PgpKeyOperation { * namely stripping of subkeys and changing the protection mode of dummy keys. * */ - public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, - String passphrase) { + public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, + SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); int indent = 0; @@ -386,11 +399,16 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } - // If we have no passphrase, only allow restricted operation - if (passphrase == null) { + if (saveParcel.isRestrictedOnly()) { return internalRestricted(sKR, saveParcel, log); } + // Do we require a passphrase? If so, pass it along + if (!isDivertToCard(masterSecretKey) && !mCryptoInput.hasPassphrase()) { + return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredPassphrase( + masterSecretKey.getKeyID(), mCryptoInput.getSignatureTime())); + } + // read masterKeyFlags, and use the same as before. // since this is the master key, this contains at least CERTIFY_OTHER PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); @@ -398,13 +416,13 @@ public class PgpKeyOperation { Date expiryTime = wsKR.getPublicKey().getExpiryTime(); long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; - return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log); + return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, log); } private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags, long masterKeyExpiry, - SaveKeyringParcel saveParcel, String passphrase, + SaveKeyringParcel saveParcel, OperationLog log) { int indent = 1; @@ -413,18 +431,25 @@ public class PgpKeyOperation { PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); - // 1. Unlock private key - progress(R.string.progress_modify_unlock, 10); - log.add(LogType.MSG_MF_UNLOCK, indent); PGPPrivateKey masterPrivateKey; - { - try { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); - } catch (PGPException e) { - log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + + if (isDivertToCard(masterSecretKey)) { + masterPrivateKey = null; + log.add(LogType.MSG_MF_DIVERT, indent); + } else { + + // 1. Unlock private key + progress(R.string.progress_modify_unlock, 10); + log.add(LogType.MSG_MF_UNLOCK, indent); + { + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mCryptoInput.getPassphrase()); + masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } } } @@ -479,9 +504,16 @@ public class PgpKeyOperation { boolean isPrimary = saveParcel.mChangePrimaryUserId != null && userId.equals(saveParcel.mChangePrimaryUserId); // generate and add new certificate - PGPSignature cert = generateUserIdSignature(masterPrivateKey, - masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry); - modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + try { + PGPSignature cert = generateUserIdSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, + isPrimary, masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -508,9 +540,15 @@ public class PgpKeyOperation { PGPUserAttributeSubpacketVector vector = attribute.getVector(); // generate and add new certificate - PGPSignature cert = generateUserAttributeSignature(masterPrivateKey, - masterPublicKey, vector); - modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); + try { + PGPSignature cert = generateUserAttributeSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, vector); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -538,9 +576,15 @@ public class PgpKeyOperation { // a duplicate revocation will be removed during canonicalization, so no need to // take care of that here. - PGPSignature cert = generateRevocationSignature(masterPrivateKey, - masterPublicKey, userId); - modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + try { + PGPSignature cert = generateRevocationSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -610,11 +654,18 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent); modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey, userId, currentCert); - PGPSignature newCert = generateUserIdSignature( - masterPrivateKey, masterPublicKey, userId, false, - masterKeyFlags, masterKeyExpiry); - modifiedPublicKey = PGPPublicKey.addCertification( - modifiedPublicKey, userId, newCert); + try { + PGPSignature newCert = generateUserIdSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, false, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } + continue; } @@ -626,11 +677,17 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_PRIMARY_NEW, indent); modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey, userId, currentCert); - PGPSignature newCert = generateUserIdSignature( - masterPrivateKey, masterPublicKey, userId, true, - masterKeyFlags, masterKeyExpiry); - modifiedPublicKey = PGPPublicKey.addCertification( - modifiedPublicKey, userId, newCert); + try { + PGPSignature newCert = generateUserIdSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, true, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } ok = true; } @@ -717,7 +774,8 @@ public class PgpKeyOperation { } PGPPublicKey pKey = - updateMasterCertificates(masterPrivateKey, masterPublicKey, + updateMasterCertificates( + masterSecretKey, masterPrivateKey, masterPublicKey, flags, expiry, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself @@ -755,9 +813,16 @@ public class PgpKeyOperation { pKey = PGPPublicKey.removeCertification(pKey, sig); } + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + mCryptoInput.getPassphrase()); + PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); + PGPSignature sig = generateSubkeyBindingSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry); + // generate and add new signature - PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, - sKey, pKey, flags, expiry, passphrase); pKey = PGPPublicKey.addCertification(pKey, sig); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); } @@ -781,10 +846,17 @@ public class PgpKeyOperation { PGPPublicKey pKey = sKey.getPublicKey(); // generate and add new signature - PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey); + try { + PGPSignature sig = generateRevocationSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPublicKey, masterPrivateKey, pKey); - pKey = PGPPublicKey.addCertification(pKey, sig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); + pKey = PGPPublicKey.addCertification(pKey, sig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -827,19 +899,29 @@ public class PgpKeyOperation { // add subkey binding signature (making this a sub rather than master key) PGPPublicKey pKey = keyPair.getPublicKey(); - PGPSignature cert = generateSubkeyBindingSignature( - masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, - add.mFlags, add.mExpiry); - pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); + try { + PGPSignature cert = generateSubkeyBindingSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, + add.mFlags, add.mExpiry); + pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } PGPSecretKey sKey; { + char[] passphrase = mCryptoInput.getPassphrase(); + if (passphrase == null) { + passphrase = new char[] { }; + } // Build key encrypter and decrypter based on passphrase PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder() .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase); PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); @@ -867,7 +949,7 @@ public class PgpKeyOperation { indent += 1; sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - passphrase, saveParcel.mNewUnlock, log, indent); + mCryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -891,6 +973,12 @@ public class PgpKeyOperation { } progress(R.string.progress_done, 100); + + if (!mNfcSignOps.isEmpty()) { + log.add(LogType.MSG_MF_INPUT_REQUIRED, indent); + return new PgpEditKeyResult(log, mNfcSignOps.build()); + } + log.add(LogType.MSG_MF_SUCCESS, indent); return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); @@ -967,7 +1055,7 @@ public class PgpKeyOperation { PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, - String passphrase, + char[] passphrase, ChangeUnlockParcel newUnlock, OperationLog log, int indent) throws PGPException { @@ -1051,14 +1139,14 @@ public class PgpKeyOperation { private static PGPSecretKeyRing applyNewPassphrase( PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, - String passphrase, + char[] passphrase, String newPassphrase, OperationLog log, int indent) throws PGPException { PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build() .get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase); // Build key encryptor based on new passphrase PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, @@ -1115,8 +1203,9 @@ public class PgpKeyOperation { } /** Update all (non-revoked) uid signatures with new flags and expiry time. */ - private static PGPPublicKey updateMasterCertificates( - PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, + private PGPPublicKey updateMasterCertificates( + PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey, + PGPPublicKey masterPublicKey, int flags, long expiry, int indent, OperationLog log) throws PGPException, IOException, SignatureException { @@ -1172,10 +1261,16 @@ public class PgpKeyOperation { currentCert.getHashedSubPackets().isPrimaryUserID(); modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey, userId, currentCert); - PGPSignature newCert = generateUserIdSignature( - masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); - modifiedPublicKey = PGPPublicKey.addCertification( - modifiedPublicKey, userId, newCert); + try { + PGPSignature newCert = generateUserIdSignature( + getSignatureGenerator(masterSecretKey, mCryptoInput), + mCryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } ok = true; } @@ -1190,15 +1285,37 @@ public class PgpKeyOperation { } - private static PGPSignature generateUserIdSignature( + private static PGPSignatureGenerator getSignatureGenerator( + PGPSecretKey secretKey, CryptoInputParcel cryptoInput) { + + PGPContentSignerBuilder builder; + + S2K s2k = secretKey.getS2K(); + if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K + && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) { + // use synchronous "NFC based" SignerBuilder + builder = new NfcSyncPGPContentSignerBuilder( + secretKey.getPublicKey().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO, + secretKey.getKeyID(), cryptoInput.getCryptoData()) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + } else { + // content signer based on signing key algorithm and chosen hash algorithm + builder = new JcaPGPContentSignerBuilder( + secretKey.getPublicKey().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + } + + return new PGPSignatureGenerator(builder); + + } + + private PGPSignature generateUserIdSignature( + PGPSignatureGenerator sGen, Date creationTime, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags, long expiry) throws IOException, PGPException, SignatureException { - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); { @@ -1222,7 +1339,7 @@ public class PgpKeyOperation { hashedPacketsGen.setPrimaryUserID(false, primary); /* critical subpackets: we consider those important for a modern pgp implementation */ - hashedPacketsGen.setSignatureCreationTime(true, new Date()); + hashedPacketsGen.setSignatureCreationTime(true, creationTime); // Request that senders add the MDC to the message (authenticate unsigned messages) hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION); hashedPacketsGen.setKeyFlags(true, flags); @@ -1238,19 +1355,15 @@ public class PgpKeyOperation { } private static PGPSignature generateUserAttributeSignature( + PGPSignatureGenerator sGen, Date creationTime, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, PGPUserAttributeSubpacketVector vector) throws IOException, PGPException, SignatureException { - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); { /* critical subpackets: we consider those important for a modern pgp implementation */ - hashedPacketsGen.setSignatureCreationTime(true, new Date()); + hashedPacketsGen.setSignatureCreationTime(true, creationTime); } sGen.setHashedSubpackets(hashedPacketsGen.generate()); @@ -1259,29 +1372,24 @@ public class PgpKeyOperation { } private static PGPSignature generateRevocationSignature( + PGPSignatureGenerator sGen, Date creationTime, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) + throws IOException, PGPException, SignatureException { - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(true, new Date()); + subHashedPacketsGen.setSignatureCreationTime(true, creationTime); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); return sGen.generateCertification(userId, pKey); } private static PGPSignature generateRevocationSignature( + PGPSignatureGenerator sGen, Date creationTime, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey) throws IOException, PGPException, SignatureException { - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(true, new Date()); + subHashedPacketsGen.setSignatureCreationTime(true, creationTime); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); // Generate key revocation or subkey revocation, depending on master/subkey-ness if (masterPublicKey.getKeyID() == pKey.getKeyID()) { @@ -1293,26 +1401,12 @@ public class PgpKeyOperation { } } - private static PGPSignature generateSubkeyBindingSignature( - PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, - PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase) - throws IOException, PGPException, SignatureException { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.toCharArray()); - PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); - return generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, subPrivateKey, - pKey, flags, expiry); - } - static PGPSignature generateSubkeyBindingSignature( + PGPSignatureGenerator sGen, Date creationTime, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry) throws IOException, PGPException, SignatureException { - // date for signing - Date creationTime = new Date(); - PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); // If this key can sign, we need a primary key binding signature @@ -1323,10 +1417,10 @@ public class PgpKeyOperation { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); - sGen.setHashedSubpackets(subHashedPacketsGen.generate()); - PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey); + PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder); + subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); + subSigGen.setHashedSubpackets(subHashedPacketsGen.generate()); + PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey); unhashedPacketsGen.setEmbeddedSignature(true, certification); } @@ -1341,10 +1435,6 @@ public class PgpKeyOperation { } } - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey); sGen.setHashedSubpackets(hashedPacketsGen.generate()); sGen.setUnhashedSubpackets(unhashedPacketsGen.generate()); @@ -1371,4 +1461,10 @@ public class PgpKeyOperation { return flags; } + private static boolean isDivertToCard(PGPSecretKey secretKey) { + S2K s2k = secretKey.getS2K(); + return s2k.getType() == S2K.GNU_DUMMY_S2K + && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index 63a7bf371..405a6a24b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -76,10 +76,6 @@ public class CertifyActionsParcel implements Parcelable { return mCryptoInput.getCryptoData(); } - public Date getSignatureTime() { - return mCryptoInput.getSignatureTime(); - } - public static final Creator CREATOR = new Creator() { public CertifyActionsParcel createFromParcel(final Parcel source) { return new CertifyActionsParcel(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index bf6a62782..6535cb404 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -61,6 +61,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -160,6 +161,7 @@ public class KeychainIntentService extends IntentService implements Progressable // save keyring public static final String EDIT_KEYRING_PARCEL = "save_parcel"; public static final String EDIT_KEYRING_PASSPHRASE = "passphrase"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; // delete keyring(s) public static final String DELETE_KEY_LIST = "delete_list"; @@ -469,11 +471,11 @@ public class KeychainIntentService extends IntentService implements Progressable // Input SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE); + CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); // Operation EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled); - EditKeyResult result = op.execute(saveParcel, passphrase); + OperationResult result = op.execute(saveParcel, cryptoInput); // Result sendMessageToHandler(MessageStatus.OKAY, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 7cc5069c8..836568533 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -81,8 +81,8 @@ public class CryptoInputParcel implements Parcelable { return mPassphrase != null; } - public String getPassphrase() { - return mPassphrase; + public char[] getPassphrase() { + return mPassphrase == null ? null : mPassphrase.toCharArray(); } public static final Creator CREATOR = new Creator() { @@ -95,4 +95,19 @@ public class CryptoInputParcel implements Parcelable { } }; + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("CryptoInput: { "); + b.append(mSignatureTime).append(" "); + if (mPassphrase != null) { + b.append("passphrase"); + } + if (mCryptoData != null) { + b.append(mCryptoData.size()); + b.append(" hashes "); + } + b.append("}"); + return b.toString(); + } } 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 a4df604be..3d91812eb 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 @@ -15,9 +15,13 @@ public class RequiredInputParcel implements Parcelable { } public Date mSignatureTime; + public final RequiredInputType mType; + + public String mNfcPin = "123456"; public final byte[][] mInputHashes; public final int[] mSignAlgos; + private Long mSubKeyId; private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java index 585a9433a..6b67d1db8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -9,6 +9,7 @@ import android.support.v4.app.Fragment; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -25,7 +26,6 @@ public abstract class CryptoOperationFragment extends Fragment { case NFC_DECRYPT: case NFC_SIGN: { Intent intent = new Intent(getActivity(), NfcOperationActivity.class); - intent.putExtra(NfcOperationActivity.EXTRA_PIN, "123456"); intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); startActivityForResult(intent, REQUEST_CODE_NFC); return; @@ -76,10 +76,14 @@ public abstract class CryptoOperationFragment extends Fragment { if (message.arg1 == MessageStatus.OKAY.ordinal()) { Bundle data = message.getData(); - InputPendingResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + OperationResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + if (result == null || ! (result instanceof InputPendingResult)) { + return false; + } - if (result != null && result.isPending()) { - RequiredInputParcel requiredInput = result.getRequiredInputParcel(); + InputPendingResult pendingResult = (InputPendingResult) result; + if (pendingResult.isPending()) { + RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); initiateInputActivity(requiredInput); return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 8d16fe47e..9f2fa54f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui; +import java.util.Date; + import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; @@ -51,10 +53,10 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; @@ -68,14 +70,12 @@ import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; -public class EditKeyFragment extends LoaderFragment implements +public class EditKeyFragment extends CryptoOperationFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; - private ListView mUserIdsList; private ListView mSubkeysList; private ListView mUserIdsAddedList; @@ -100,7 +100,6 @@ public class EditKeyFragment extends LoaderFragment implements private SaveKeyringParcel mSaveKeyringParcel; private String mPrimaryUserId; - private String mCurrentPassphrase; /** * Creates new instance of this fragment @@ -129,8 +128,7 @@ public class EditKeyFragment extends LoaderFragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.edit_key_fragment, getContainer()); + View view = inflater.inflate(R.layout.edit_key_fragment, null); mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids); mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys); @@ -140,7 +138,7 @@ public class EditKeyFragment extends LoaderFragment implements mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id); mAddSubkey = view.findViewById(R.id.edit_key_action_add_key); - return root; + return view; } @Override @@ -155,7 +153,7 @@ public class EditKeyFragment extends LoaderFragment implements if (mDataUri == null) { returnKeyringParcel(); } else { - saveInDatabase(mCurrentPassphrase); + cryptoOperation(new CryptoInputParcel(new Date())); } } }, new OnClickListener() { @@ -185,18 +183,12 @@ public class EditKeyFragment extends LoaderFragment implements private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) { mSaveKeyringParcel = saveKeyringParcel; mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId; - if (saveKeyringParcel.mNewUnlock != null) { - mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase; - } mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); - - // show directly - setContentShown(true); } private void loadData(Uri dataUri) { @@ -216,9 +208,6 @@ public class EditKeyFragment extends LoaderFragment implements case GNU_DUMMY: finishWithError(LogType.MSG_EK_ERROR_DUMMY); return; - case DIVERT_TO_CARD: - finishWithError(LogType.MSG_EK_ERROR_DIVERT); - break; } mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); @@ -229,24 +218,10 @@ public class EditKeyFragment extends LoaderFragment implements return; } - try { - mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), - mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); - return; - } - - if (mCurrentPassphrase == null) { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSaveKeyringParcel.mMasterKeyId); - startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); - } else { - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); - getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this); - } + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); + getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel); mUserIdsList.setAdapter(mUserIdsAdapter); @@ -262,28 +237,6 @@ public class EditKeyFragment extends LoaderFragment implements mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PASSPHRASE: { - if (resultCode == Activity.RESULT_OK && data != null) { - mCurrentPassphrase = data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); - getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this); - } else { - getActivity().finish(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - private void initView() { mChangePassphrase.setOnClickListener(new View.OnClickListener() { @Override @@ -322,7 +275,6 @@ public class EditKeyFragment extends LoaderFragment implements } public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); switch (id) { case LOADER_ID_USER_IDS: { @@ -355,7 +307,6 @@ public class EditKeyFragment extends LoaderFragment implements break; } - setContentShown(true); } /** @@ -397,7 +348,7 @@ public class EditKeyFragment extends LoaderFragment implements Messenger messenger = new Messenger(returnHandler); SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance( - messenger, mCurrentPassphrase, R.string.title_change_passphrase); + messenger, R.string.title_change_passphrase); setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog"); } @@ -593,8 +544,11 @@ public class EditKeyFragment extends LoaderFragment implements getActivity().finish(); } - private void saveInDatabase(String passphrase) { - Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString()); + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + + Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput); + Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel); KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( getActivity(), @@ -605,6 +559,10 @@ public class EditKeyFragment extends LoaderFragment implements // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); + if (handlePendingMessage(message)) { + return; + } + if (message.arg1 == MessageStatus.OKAY.ordinal()) { // get returned data bundle @@ -640,7 +598,7 @@ public class EditKeyFragment extends LoaderFragment implements // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index f9b8e2ab6..66d1ad5a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -62,10 +62,9 @@ public abstract class EncryptActivity extends BaseActivity { startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); } - protected void startNfcSign(long keyId, String pin, RequiredInputParcel nfcOps) { + protected void startNfcSign(long keyId, RequiredInputParcel nfcOps) { Intent intent = new Intent(this, NfcOperationActivity.class); - intent.putExtra(NfcOperationActivity.EXTRA_PIN, pin); intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, nfcOps); // TODO respect keyid(?) @@ -148,8 +147,7 @@ public abstract class EncryptActivity extends BaseActivity { pgpResult.getNfcHash(), pgpResult.getNfcAlgo(), input.getSignatureTime()); - startNfcSign(pgpResult.getNfcKeyId(), - pgpResult.getNfcPassphrase(), parcel); + startNfcSign(pgpResult.getNfcKeyId(), parcel); } else { throw new RuntimeException("Unhandled pending result!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index 6a83c2361..69467daac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -40,7 +40,6 @@ import java.nio.ByteBuffer; @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) public class NfcOperationActivity extends BaseActivity { - public static final String EXTRA_PIN = "pin"; public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String RESULT_DATA = "result_data"; @@ -50,8 +49,6 @@ public class NfcOperationActivity extends BaseActivity { private NfcAdapter mNfcAdapter; private IsoDep mIsoDep; - private String mPin; - RequiredInputParcel mNfcOperations; @Override @@ -70,7 +67,6 @@ public class NfcOperationActivity extends BaseActivity { Bundle data = intent.getExtras(); mNfcOperations = data.getParcelable(EXTRA_REQUIRED_INPUT); - mPin = data.getString(EXTRA_PIN); } @@ -161,14 +157,16 @@ public class NfcOperationActivity extends BaseActivity { return; } + String pin = mNfcOperations.mNfcPin; + // Command APDU for VERIFY command (page 32) String login = "00" // CLA + "20" // INS + "00" // P1 + "82" // P2 (PW1) - + String.format("%02x", mPin.length()) // Lc - + Hex.toHexString(mPin.getBytes()); + + String.format("%02x", pin.length()) // Lc + + Hex.toHexString(pin.getBytes()); if ( ! card(login).equals(accepted)) { // login toast("Wrong PIN!"); setResult(RESULT_CANCELED); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index b34dc2edc..dc8d8f303 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -49,7 +49,6 @@ import org.sufficientlysecure.keychain.util.Log; public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_TITLE = "title"; - private static final String ARG_OLD_PASSPHRASE = "old_passphrase"; public static final int MESSAGE_OKAY = 1; @@ -67,12 +66,11 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi * @param messenger to communicate back after setting the passphrase * @return */ - public static SetPassphraseDialogFragment newInstance(Messenger messenger, String oldPassphrase, int title) { + public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) { SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment(); Bundle args = new Bundle(); args.putInt(ARG_TITLE, title); args.putParcelable(ARG_MESSENGER, messenger); - args.putString(ARG_OLD_PASSPHRASE, oldPassphrase); frag.setArguments(args); @@ -88,7 +86,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi int title = getArguments().getInt(ARG_TITLE); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - String oldPassphrase = getArguments().getString(ARG_OLD_PASSPHRASE); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -102,13 +99,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again); mNoPassphraseCheckBox = (CheckBox) view.findViewById(R.id.passphrase_no_passphrase); - - if (TextUtils.isEmpty(oldPassphrase)) { - mNoPassphraseCheckBox.setChecked(true); - mPassphraseEditText.setEnabled(false); - mPassphraseAgainEditText.setEnabled(false); - } - mNoPassphraseCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 2eb167c8c..e8bcbcc31 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -897,6 +897,7 @@ "Modifying keyring %s" + "Will divert to card/nfc for crypto operations" "The serial number of a divert-to-card key must be 16 bytes! This is a programming error, please file a bug report!" "Encoding exception!" "Actual key fingerprint does not match the expected one!" @@ -911,6 +912,7 @@ "Fatal error decrypting master key! This is likely a programming error, please file a bug report!" "Internal OpenPGP error!" "Signature exception!" + "Diverting to card/nfc for crypto operations" "Modifying master certifications" "Adding empty notation packet" "Adding PIN notation packet" @@ -987,7 +989,6 @@ "Key successfully promoted" - "Editing of NFC keys is not (yet) supported!" "Cannot edit keyring with stripped master key!" "Key not found!" From 6cf966b63fd32f27b021684950b1d342506d1d38 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 14:10:00 +0100 Subject: [PATCH 06/80] re-inline cryptoInput variable --- .../keychain/pgp/PgpKeyOperation.java | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 092fd9d48..0bae93055 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -97,7 +97,6 @@ public class PgpKeyOperation { private AtomicBoolean mCancelled; NfcSignOperationsBuilder mNfcSignOps; - private CryptoInputParcel mCryptoInput; public PgpKeyOperation(Progressable progress) { super(); @@ -111,7 +110,6 @@ public class PgpKeyOperation { this(progress); mCancelled = cancelled; mNfcSignOps = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); - mCryptoInput = cryptoInput; } private boolean checkCancelled() { @@ -329,8 +327,8 @@ public class PgpKeyOperation { masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); subProgressPush(50, 100); - mCryptoInput = new CryptoInputParcel(new Date(), new Passphrase("")); - return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, log); + CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase("")); + return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log); } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -362,6 +360,7 @@ public class PgpKeyOperation { * */ public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, + CryptoInputParcel cryptoInput, SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); @@ -405,9 +404,9 @@ public class PgpKeyOperation { } // Do we require a passphrase? If so, pass it along - if (!isDivertToCard(masterSecretKey) && !mCryptoInput.hasPassphrase()) { + if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) { return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredPassphrase( - masterSecretKey.getKeyID(), mCryptoInput.getSignatureTime())); + masterSecretKey.getKeyID(), cryptoInput.getSignatureTime())); } // read masterKeyFlags, and use the same as before. @@ -417,12 +416,13 @@ public class PgpKeyOperation { Date expiryTime = wsKR.getPublicKey().getExpiryTime(); long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; - return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, log); + return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log); } private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags, long masterKeyExpiry, + CryptoInputParcel cryptoInput, SaveKeyringParcel saveParcel, OperationLog log) { @@ -445,7 +445,7 @@ public class PgpKeyOperation { { try { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mCryptoInput.getPassphrase().getCharArray()); + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(cryptoInput.getPassphrase().getCharArray()); masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); } catch (PGPException e) { log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); @@ -507,8 +507,8 @@ public class PgpKeyOperation { // generate and add new certificate try { PGPSignature cert = generateUserIdSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); @@ -543,8 +543,8 @@ public class PgpKeyOperation { // generate and add new certificate try { PGPSignature cert = generateUserAttributeSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, vector); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); } catch (NfcInteractionNeeded e) { @@ -579,8 +579,8 @@ public class PgpKeyOperation { // take care of that here. try { PGPSignature cert = generateRevocationSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); } catch (NfcInteractionNeeded e) { @@ -657,8 +657,8 @@ public class PgpKeyOperation { modifiedPublicKey, userId, currentCert); try { PGPSignature newCert = generateUserIdSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags, masterKeyExpiry); modifiedPublicKey = PGPPublicKey.addCertification( @@ -680,8 +680,8 @@ public class PgpKeyOperation { modifiedPublicKey, userId, currentCert); try { PGPSignature newCert = generateUserIdSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags, masterKeyExpiry); modifiedPublicKey = PGPPublicKey.addCertification( @@ -777,7 +777,7 @@ public class PgpKeyOperation { PGPPublicKey pKey = updateMasterCertificates( masterSecretKey, masterPrivateKey, masterPublicKey, - flags, expiry, indent, log); + flags, expiry, cryptoInput, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -816,11 +816,11 @@ public class PgpKeyOperation { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - mCryptoInput.getPassphrase().getCharArray()); + cryptoInput.getPassphrase().getCharArray()); PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); PGPSignature sig = generateSubkeyBindingSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry); // generate and add new signature @@ -849,8 +849,8 @@ public class PgpKeyOperation { // generate and add new signature try { PGPSignature sig = generateRevocationSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey, pKey); pKey = PGPPublicKey.addCertification(pKey, sig); @@ -902,8 +902,8 @@ public class PgpKeyOperation { PGPPublicKey pKey = keyPair.getPublicKey(); try { PGPSignature cert = generateSubkeyBindingSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, add.mFlags, add.mExpiry); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); @@ -919,7 +919,7 @@ public class PgpKeyOperation { PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - mCryptoInput.getPassphrase().getCharArray()); + cryptoInput.getPassphrase().getCharArray()); PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); @@ -947,7 +947,7 @@ public class PgpKeyOperation { indent += 1; sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - mCryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); + cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -1149,7 +1149,7 @@ public class PgpKeyOperation { PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); // noinspection unchecked for (PGPSecretKey sKey : new IterableIterator(sKR.getSecretKeys())) { @@ -1203,7 +1203,9 @@ public class PgpKeyOperation { private PGPPublicKey updateMasterCertificates( PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, - int flags, long expiry, int indent, OperationLog log) + int flags, long expiry, + CryptoInputParcel cryptoInput, + int indent, OperationLog log) throws PGPException, IOException, SignatureException { // keep track if we actually changed one @@ -1260,8 +1262,8 @@ public class PgpKeyOperation { modifiedPublicKey, userId, currentCert); try { PGPSignature newCert = generateUserIdSignature( - getSignatureGenerator(masterSecretKey, mCryptoInput), - mCryptoInput.getSignatureTime(), + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.getSignatureTime(), masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey, userId, newCert); From 879efc2c703d55cf3a50dc3c427129555ad58004 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 14:21:55 +0100 Subject: [PATCH 07/80] fix unit tests (syntax) --- .../keychain/pgp/PgpKeyOperationTest.java | 38 ++++++++++--------- .../keychain/operations/EditKeyOperation.java | 4 +- .../pgp/PgpSignEncryptInputParcel.java | 2 +- .../service/input/CryptoInputParcel.java | 10 +++++ 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 144501c89..2ecc304e7 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -49,6 +49,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.support.KeyringBuilder; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; @@ -74,6 +75,7 @@ public class PgpKeyOperationTest { static UncachedKeyRing staticRing; final static Passphrase passphrase = TestingUtils.genPassphrase(); + final static CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), passphrase); UncachedKeyRing ring; PgpKeyOperation op; @@ -302,7 +304,7 @@ public class PgpKeyOperationTest { } assertModifyFailure("keyring modification with bad passphrase should fail", - ring, parcel, badphrase, LogType.MSG_MF_UNLOCK_ERROR); + ring, parcel, new CryptoInputParcel(badphrase), LogType.MSG_MF_UNLOCK_ERROR); } } @@ -646,7 +648,7 @@ public class PgpKeyOperationTest { parcel.mRevokeSubKeys.add(123L); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); - UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing(); + UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, cryptoInput, parcel).getRing(); Assert.assertNull("revoking a nonexistent subkey should fail", otherModified); @@ -975,7 +977,7 @@ public class PgpKeyOperationTest { // applying the same modification AGAIN should not add more certifications but drop those // as duplicates - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, passphrase, true, false); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, cryptoInput, true, false); Assert.assertEquals("duplicate modification: one extra packet in original", 1, onlyA.size()); Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size()); @@ -1039,8 +1041,7 @@ public class PgpKeyOperationTest { // change passphrase to empty parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); // note that canonicalization here necessarily strips the empty notation packet - UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, - passphrase); + UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -1052,8 +1053,9 @@ public class PgpKeyOperationTest { // modify keyring, change to non-empty passphrase Passphrase otherPassphrase = TestingUtils.genPassphrase(true); + CryptoInputParcel otherCryptoInput = new CryptoInputParcel(otherPassphrase); parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase); - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, new Passphrase()); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(new Date())); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -1086,7 +1088,7 @@ public class PgpKeyOperationTest { // we should still be able to modify it (and change its passphrase) without errors PgpKeyOperation op = new PgpKeyOperation(null); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, otherCryptoInput, parcel); Assert.assertTrue("key modification must succeed", result.success()); Assert.assertFalse("log must not contain a warning", result.getLog().containsWarnings()); @@ -1102,7 +1104,7 @@ public class PgpKeyOperationTest { PgpKeyOperation op = new PgpKeyOperation(null); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase2); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(otherPassphrase2), parcel); Assert.assertTrue("key modification must succeed", result.success()); Assert.assertTrue("log must contain a failed passphrase change warning", result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL)); @@ -1140,7 +1142,7 @@ public class PgpKeyOperationTest { { parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("phrayse"), null); - applyModificationWithChecks(parcel, modified, onlyA, onlyB, pin, true, false); + applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(pin), true, false); Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)", 4, onlyA.size()); @@ -1157,7 +1159,7 @@ public class PgpKeyOperationTest { parcel.mAddUserIds.add("discord"); PgpKeyOperation op = new PgpKeyOperation(null); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, null); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Date()), parcel); Assert.assertFalse("non-restricted operations should fail without passphrase", result.success()); } @@ -1165,15 +1167,15 @@ public class PgpKeyOperationTest { UncachedKeyRing ring, ArrayList onlyA, ArrayList onlyB) { - return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true); + return applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput, true, true); } private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel, UncachedKeyRing ring, ArrayList onlyA, ArrayList onlyB, - Passphrase passphrase) { - return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true); + CryptoInputParcel cryptoInput) { + return applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput, true, true); } // applies a parcel modification while running some integrity checks @@ -1181,7 +1183,7 @@ public class PgpKeyOperationTest { UncachedKeyRing ring, ArrayList onlyA, ArrayList onlyB, - Passphrase passphrase, + CryptoInputParcel cryptoInput, boolean canonicalize, boolean constantCanonicalize) { @@ -1191,7 +1193,7 @@ public class PgpKeyOperationTest { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); PgpKeyOperation op = new PgpKeyOperation(null); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); Assert.assertTrue("key modification must succeed", result.success()); UncachedKeyRing rawModified = result.getRing(); Assert.assertNotNull("key modification must not return null", rawModified); @@ -1258,11 +1260,11 @@ public class PgpKeyOperationTest { } private void assertModifyFailure(String reason, UncachedKeyRing ring, - SaveKeyringParcel parcel, Passphrase passphrase, LogType expected) + SaveKeyringParcel parcel, CryptoInputParcel cryptoInput, LogType expected) throws Exception { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); Assert.assertFalse(reason, result.success()); Assert.assertNull(reason, result.getRing()); @@ -1276,7 +1278,7 @@ public class PgpKeyOperationTest { throws Exception { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); - PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); Assert.assertFalse(reason, result.success()); Assert.assertNull(reason, result.getRing()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 28ef7582b..4072d91c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -72,7 +72,7 @@ public class EditKeyOperation extends BaseOperation { PgpEditKeyResult modifyResult; { PgpKeyOperation keyOperations = - new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled, cryptoInput); + new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled); // If a key id is specified, fetch and edit if (saveParcel.mMasterKeyId != null) { @@ -83,7 +83,7 @@ public class EditKeyOperation extends BaseOperation { CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel); + modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel); if (modifyResult.isPending()) { return modifyResult; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java index 022dc4d32..d5f3cf964 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -49,7 +49,7 @@ public class PgpSignEncryptInputParcel implements Parcelable { protected boolean mCleartextSignature; protected boolean mDetachedSignature = false; protected boolean mHiddenRecipients = false; - protected CryptoInputParcel mCryptoInput = new CryptoInputParcel(null); + protected CryptoInputParcel mCryptoInput = new CryptoInputParcel(); public PgpSignEncryptInputParcel() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index d77bbe7e2..6edc48b09 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -24,11 +24,21 @@ public class CryptoInputParcel implements Parcelable { // used in the crypto operation described by this parcel. private HashMap mCryptoData = new HashMap<>(); + public CryptoInputParcel() { + mSignatureTime = new Date(); + mPassphrase = null; + } + public CryptoInputParcel(Date signatureTime, Passphrase passphrase) { mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = passphrase; } + public CryptoInputParcel(Passphrase passphrase) { + mSignatureTime = new Date(); + mPassphrase = null; + } + public CryptoInputParcel(Date signatureTime) { mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = null; From 3fce6d8a12884519fa77e1500e8f8441b3aa43cd Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 14:22:07 +0100 Subject: [PATCH 08/80] inline mNfcSignOps variable --- .../keychain/pgp/PgpKeyOperation.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 0bae93055..51f8460a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -36,7 +36,6 @@ import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.PGPDataDecryptor; import org.spongycastle.openpgp.operator.PGPDigestCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; @@ -96,8 +95,6 @@ public class PgpKeyOperation { private Stack mProgress; private AtomicBoolean mCancelled; - NfcSignOperationsBuilder mNfcSignOps; - public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { @@ -106,10 +103,9 @@ public class PgpKeyOperation { } } - public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled, CryptoInputParcel cryptoInput) { + public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled) { this(progress); mCancelled = cancelled; - mNfcSignOps = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); } private boolean checkCancelled() { @@ -428,6 +424,8 @@ public class PgpKeyOperation { int indent = 1; + NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); + progress(R.string.progress_modify, 0); PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); @@ -474,7 +472,7 @@ public class PgpKeyOperation { String userId = saveParcel.mAddUserIds.get(i); log.add(LogType.MSG_MF_UID_ADD, indent, userId); - if (userId.equals("")) { + if ("".equals(userId)) { log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } @@ -513,7 +511,7 @@ public class PgpKeyOperation { isPrimary, masterKeyFlags, masterKeyExpiry); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } } subProgressPop(); @@ -548,7 +546,7 @@ public class PgpKeyOperation { masterPrivateKey, masterPublicKey, vector); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } } subProgressPop(); @@ -584,7 +582,7 @@ public class PgpKeyOperation { masterPrivateKey, masterPublicKey, userId); modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } } subProgressPop(); @@ -664,7 +662,7 @@ public class PgpKeyOperation { modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey, userId, newCert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } continue; @@ -687,7 +685,7 @@ public class PgpKeyOperation { modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey, userId, newCert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } ok = true; } @@ -777,7 +775,7 @@ public class PgpKeyOperation { PGPPublicKey pKey = updateMasterCertificates( masterSecretKey, masterPrivateKey, masterPublicKey, - flags, expiry, cryptoInput, indent, log); + flags, expiry, cryptoInput, nfcSignOps, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -856,7 +854,7 @@ public class PgpKeyOperation { pKey = PGPPublicKey.addCertification(pKey, sig); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } } subProgressPop(); @@ -908,7 +906,7 @@ public class PgpKeyOperation { add.mFlags, add.mExpiry); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } PGPSecretKey sKey; { @@ -972,9 +970,9 @@ public class PgpKeyOperation { progress(R.string.progress_done, 100); - if (!mNfcSignOps.isEmpty()) { + if (!nfcSignOps.isEmpty()) { log.add(LogType.MSG_MF_INPUT_REQUIRED, indent); - return new PgpEditKeyResult(log, mNfcSignOps.build()); + return new PgpEditKeyResult(log, nfcSignOps.build()); } log.add(LogType.MSG_MF_SUCCESS, indent); @@ -1205,6 +1203,7 @@ public class PgpKeyOperation { PGPPublicKey masterPublicKey, int flags, long expiry, CryptoInputParcel cryptoInput, + NfcSignOperationsBuilder nfcSignOps, int indent, OperationLog log) throws PGPException, IOException, SignatureException { @@ -1268,7 +1267,7 @@ public class PgpKeyOperation { modifiedPublicKey = PGPPublicKey.addCertification( modifiedPublicKey, userId, newCert); } catch (NfcInteractionNeeded e) { - mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); } ok = true; From e00ce86de911e5b3f9aa7f5d8f1cb40e310e95e3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 14:57:38 +0100 Subject: [PATCH 09/80] fix more unit tests (syntax) --- .../operations/CertifyOperationTest.java | 51 +++++-------------- .../keychain/pgp/PgpEncryptDecryptTest.java | 10 ++-- .../pgp/UncachedKeyringCanonicalizeTest.java | 4 ++ .../pgp/UncachedKeyringMergeTest.java | 23 ++++++--- .../keychain/operations/CertifyOperation.java | 11 ++-- .../keychain/pgp/CanonicalizedSecretKey.java | 5 ++ .../keychain/pgp/PgpKeyOperation.java | 2 +- .../service/CertifyActionsParcel.java | 10 +--- .../service/KeychainIntentService.java | 3 +- .../keychain/ui/CertifyKeyFragment.java | 3 +- 10 files changed, 56 insertions(+), 66 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java index 7c4b2e91e..130b86908 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyActio import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -152,8 +153,8 @@ public class CertifyOperationTest { @Test public void testCertifyId() throws Exception { - CertifyOperation op = operationWithFakePassphraseCache( - mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); + CertifyOperation op = new CertifyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null, null); { CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) @@ -165,7 +166,7 @@ public class CertifyOperationTest { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), mStaticRing2.getPublicKey().getUnorderedUserIds())); - CertifyResult result = op.certify(actions, null); + CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertTrue("certification must succeed", result.success()); @@ -180,8 +181,8 @@ public class CertifyOperationTest { @Test public void testCertifyAttribute() throws Exception { - CertifyOperation op = operationWithFakePassphraseCache( - mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); + CertifyOperation op = new CertifyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null, null); { CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) @@ -193,7 +194,7 @@ public class CertifyOperationTest { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), null, mStaticRing2.getPublicKey().getUnorderedUserAttributes())); - CertifyResult result = op.certify(actions, null); + CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertTrue("certification must succeed", result.success()); @@ -209,14 +210,14 @@ public class CertifyOperationTest { @Test public void testCertifySelf() throws Exception { - CertifyOperation op = operationWithFakePassphraseCache( - mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); + CertifyOperation op = new CertifyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null, null); CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), mStaticRing2.getPublicKey().getUnorderedUserIds())); - CertifyResult result = op.certify(actions, null); + CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertFalse("certification with itself must fail!", result.success()); Assert.assertTrue("error msg must be about self certification", @@ -226,7 +227,8 @@ public class CertifyOperationTest { @Test public void testCertifyNonexistent() throws Exception { - CertifyOperation op = operationWithFakePassphraseCache(null, null, mKeyPhrase1); + CertifyOperation op = new CertifyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null, null); { CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); @@ -234,7 +236,7 @@ public class CertifyOperationTest { uids.add("nonexistent"); actions.add(new CertifyAction(1234L, uids)); - CertifyResult result = op.certify(actions, null); + CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertFalse("certification of nonexistent key must fail", result.success()); Assert.assertTrue("must contain error msg about not found", @@ -246,7 +248,7 @@ public class CertifyOperationTest { actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), mStaticRing2.getPublicKey().getUnorderedUserIds())); - CertifyResult result = op.certify(actions, null); + CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null); Assert.assertFalse("certification of nonexistent key must fail", result.success()); Assert.assertTrue("must contain error msg about not found", @@ -255,29 +257,4 @@ public class CertifyOperationTest { } - private CertifyOperation operationWithFakePassphraseCache( - final Long checkMasterKeyId, final Long checkSubKeyId, final Passphrase passphrase) { - - return new CertifyOperation(Robolectric.application, - new ProviderHelper(Robolectric.application), - null, null) { - @Override - public Passphrase getCachedPassphrase(long masterKeyId, long subKeyId) - throws NoSecretKeyException { - if (checkMasterKeyId != null) { - Assert.assertEquals("requested passphrase should be for expected master key id", - (long) checkMasterKeyId, masterKeyId); - } - if (checkSubKeyId != null) { - Assert.assertEquals("requested passphrase should be for expected sub key id", - (long) checkSubKeyId, subKeyId); - } - if (passphrase == null) { - return null; - } - return passphrase; - } - }; - } - } diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 6b7e20b04..5a90af2dc 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -138,7 +138,7 @@ public class PgpEncryptDecryptTest { InputData data = new InputData(in, in.available()); - PgpSignEncryptInputParcel b = new setSignatureTimestamp(); + PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel(); b.setSymmetricPassphrase(mPassphrase); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); @@ -222,7 +222,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInputParcel b = new setSignatureTimestamp(); + PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel(); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); @@ -303,7 +303,7 @@ public class PgpEncryptDecryptTest { InputData data = new InputData(in, in.available()); - PgpSignEncryptInputParcel b = new setSignatureTimestamp(); + PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel(); b.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId(), mStaticRing2.getMasterKeyId() @@ -395,7 +395,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInputParcel b = new setSignatureTimestamp(); + PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel(); b.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId(), @@ -477,7 +477,7 @@ public class PgpEncryptDecryptTest { new ProviderHelper(Robolectric.application), null); InputData data = new InputData(in, in.available()); - PgpSignEncryptInputParcel b = new setSignatureTimestamp(); + PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel(); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java index bfe34b14b..2b184c075 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java @@ -59,6 +59,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.util.Passphrase; @@ -549,7 +550,10 @@ public class UncachedKeyringCanonicalizeTest { CanonicalizedSecretKey masterSecretKey = canonicalized.getSecretKey(); masterSecretKey.unlock(new Passphrase()); PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); + CryptoInputParcel cryptoInput = new CryptoInputParcel(); PGPSignature cert = PgpKeyOperation.generateSubkeyBindingSignature( + PgpKeyOperation.getSignatureGenerator(masterSecretKey.getSecretKey(), cryptoInput), + cryptoInput.getSignatureTime(), masterPublicKey, masterSecretKey.getPrivateKey(), masterSecretKey.getPrivateKey(), masterPublicKey, masterSecretKey.getKeyUsage(), 0); PGPPublicKey subPubKey = PGPPublicKey.addSubkeyBindingCertification(masterPublicKey, cert); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 712f0563d..732c13f62 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -35,9 +35,12 @@ import org.spongycastle.util.Strings; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.util.Passphrase; @@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; import java.security.Security; import java.util.ArrayList; +import java.util.Date; import java.util.Iterator; import java.util.Random; @@ -186,11 +190,11 @@ public class UncachedKeyringMergeTest { parcel.reset(); parcel.mAddUserIds.add("flim"); - modifiedA = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); + modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); parcel.reset(); parcel.mAddUserIds.add("flam"); - modifiedB = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); + modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); } { // merge A into base @@ -227,8 +231,8 @@ public class UncachedKeyringMergeTest { parcel.reset(); parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); - modifiedA = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); - modifiedB = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); + modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2); subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2); @@ -269,7 +273,7 @@ public class UncachedKeyringMergeTest { parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1)); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( ringA.getEncoded(), false, 0); - modified = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); + modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); } { @@ -295,8 +299,13 @@ public class UncachedKeyringMergeTest { CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing( ringB.getEncoded(), false, 0).getSecretKey(); secretKey.unlock(new Passphrase()); + PgpCertifyOperation op = new PgpCertifyOperation(); + CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds()); // sign all user ids - modified = secretKey.certifyUserIds(publicRing, publicRing.getPublicKey().getUnorderedUserIds(), null, null); + PgpCertifyResult result = op.certify(secretKey, publicRing, new OperationLog(), 0, action, null, new Date()); + Assert.assertTrue("certification must succeed", result.success()); + Assert.assertNotNull("certification must yield result", result.getCertifiedRing()); + modified = result.getCertifiedRing(); } { @@ -363,7 +372,7 @@ public class UncachedKeyringMergeTest { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( ringA.getEncoded(), false, 0); - modified = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); + modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); } { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index ad2b297dd..7c299a6bd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -63,7 +64,7 @@ public class CertifyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public CertifyResult certify(CertifyActionsParcel parcel, String keyServerUri) { + public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) { OperationLog log = new OperationLog(); log.add(LogType.MSG_CRT, 0); @@ -78,13 +79,13 @@ public class CertifyOperation extends BaseOperation { log.add(LogType.MSG_CRT_UNLOCK, 1); certificationKey = secretKeyRing.getSecretKey(); - if (!parcel.mCryptoInput.hasPassphrase()) { + if (!cryptoInput.hasPassphrase()) { return new CertifyResult(log, RequiredInputParcel.createRequiredPassphrase( certificationKey.getKeyId(), null)); } // certification is always with the master key id, so use that one - Passphrase passphrase = parcel.mCryptoInput.getPassphrase(); + Passphrase passphrase = cryptoInput.getPassphrase(); if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); @@ -104,7 +105,7 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; - NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.mCryptoInput.getSignatureTime()); + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { @@ -128,7 +129,7 @@ public class CertifyOperation extends BaseOperation { PgpCertifyOperation op = new PgpCertifyOperation(); PgpCertifyResult result = op.certify(certificationKey, publicRing, - log, 2, action, parcel.getSignatureData(), parcel.mCryptoInput.getSignatureTime()); + log, 2, action, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime()); if (!result.success()) { certifyError += 1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 715d5af30..21b6e551b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -292,4 +292,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return mPrivateKey; } + // HACK, for TESTING ONLY!! + PGPSecretKey getSecretKey() { + return mSecretKey; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 51f8460a4..f739cfb69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -1283,7 +1283,7 @@ public class PgpKeyOperation { } - private static PGPSignatureGenerator getSignatureGenerator( + static PGPSignatureGenerator getSignatureGenerator( PGPSecretKey secretKey, CryptoInputParcel cryptoInput) { PGPContentSignerBuilder builder; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index 405a6a24b..8721f4c0c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -42,17 +42,14 @@ public class CertifyActionsParcel implements Parcelable { public CertifyLevel mLevel; public ArrayList mCertifyActions = new ArrayList<>(); - public CryptoInputParcel mCryptoInput; - public CertifyActionsParcel(CryptoInputParcel cryptoInput, long masterKeyId) { + public CertifyActionsParcel(long masterKeyId) { mMasterKeyId = masterKeyId; - mCryptoInput = cryptoInput != null ? cryptoInput : new CryptoInputParcel(new Date()); mLevel = CertifyLevel.DEFAULT; } public CertifyActionsParcel(Parcel source) { mMasterKeyId = source.readLong(); - mCryptoInput = source.readParcelable(CertifyActionsParcel.class.getClassLoader()); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; @@ -66,16 +63,11 @@ public class CertifyActionsParcel implements Parcelable { @Override public void writeToParcel(Parcel destination, int flags) { destination.writeLong(mMasterKeyId); - destination.writeParcelable(mCryptoInput, 0); destination.writeInt(mLevel.ordinal()); destination.writeSerializable(mCertifyActions); } - public Map getSignatureData() { - return mCryptoInput.getCryptoData(); - } - public static final Creator CREATOR = new Creator() { public CertifyActionsParcel createFromParcel(final Parcel source) { return new CertifyActionsParcel(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index cd3e06705..ed6453e9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -254,11 +254,12 @@ public class KeychainIntentService extends IntentService implements Progressable // Input CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); + CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); String keyServerUri = data.getString(UPLOAD_KEY_SERVER); // Operation CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); - CertifyResult result = op.certify(parcel, keyServerUri); + CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri); // Result sendMessageToHandler(MessageStatus.OKAY, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index a5f818734..a42c52e63 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -321,9 +321,10 @@ public class CertifyKeyFragment extends CryptoOperationFragment Bundle data = new Bundle(); { // fill values for this action - CertifyActionsParcel parcel = new CertifyActionsParcel(cryptoInput, mSignMasterKeyId); + CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); parcel.mCertifyActions.addAll(certifyActions); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); if (mUploadKeyCheckbox.isChecked()) { String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); From 3e51da3afa542c62b82bbcf9a953cdcd379950a2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 18:45:00 +0100 Subject: [PATCH 10/80] fix unit tests (for real) --- .../keychain/pgp/PgpKeyOperationTest.java | 21 ++++++++++++------- .../pgp/UncachedKeyringMergeTest.java | 12 +++++------ .../operations/results/OperationResult.java | 4 +++- .../keychain/pgp/PgpKeyOperation.java | 12 +++++++++-- .../keychain/service/SaveKeyringParcel.java | 2 +- .../service/input/CryptoInputParcel.java | 2 +- OpenKeychain/src/main/res/values/strings.xml | 6 ++++-- 7 files changed, 39 insertions(+), 20 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 2ecc304e7..e6ada202b 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -75,7 +75,6 @@ public class PgpKeyOperationTest { static UncachedKeyRing staticRing; final static Passphrase passphrase = TestingUtils.genPassphrase(); - final static CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), passphrase); UncachedKeyRing ring; PgpKeyOperation op; @@ -83,6 +82,8 @@ public class PgpKeyOperationTest { ArrayList onlyA = new ArrayList(); ArrayList onlyB = new ArrayList(); + static CryptoInputParcel cryptoInput; + @BeforeClass public static void setUpOnce() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); @@ -110,6 +111,9 @@ public class PgpKeyOperationTest { // we sleep here for a second, to make sure all new certificates have different timestamps Thread.sleep(1000); + + cryptoInput = new CryptoInputParcel(new Date(), passphrase); + } @Before public void setUp() throws Exception { @@ -302,6 +306,7 @@ public class PgpKeyOperationTest { if (badphrase.equals(passphrase)) { badphrase = new Passphrase("a"); } + parcel.mAddUserIds.add("allure"); assertModifyFailure("keyring modification with bad passphrase should fail", ring, parcel, new CryptoInputParcel(badphrase), LogType.MSG_MF_UNLOCK_ERROR); @@ -659,7 +664,8 @@ public class PgpKeyOperationTest { parcel.reset(); parcel.mRevokeSubKeys.add(keyId); - modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB); + modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, + new CryptoInputParcel(new Date(), passphrase)); Assert.assertEquals("no extra packets in original", 0, onlyA.size()); Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size()); @@ -779,7 +785,7 @@ public class PgpKeyOperationTest { { // we should be able to change the stripped/divert status of subkeys without passphrase parcel.reset(); parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null)); - modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null); + modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, new CryptoInputParcel()); Assert.assertEquals("one extra packet in modified", 1, onlyB.size()); Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket(); Assert.assertEquals("new packet should have GNU_DUMMY S2K type", @@ -795,7 +801,7 @@ public class PgpKeyOperationTest { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; parcel.mChangeSubKeys.add(new SubkeyChange(keyId, false, serial)); - modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null); + modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, new CryptoInputParcel()); Assert.assertEquals("one extra packet in modified", 1, onlyB.size()); Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket(); Assert.assertEquals("new packet should have GNU_DUMMY S2K type", @@ -972,12 +978,12 @@ public class PgpKeyOperationTest { Assert.assertEquals("signature type must be positive certification", PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType()); - // make sure packets can be distinguished by timestamp Thread.sleep(1000); // applying the same modification AGAIN should not add more certifications but drop those // as duplicates - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, cryptoInput, true, false); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, + new CryptoInputParcel(new Date(), passphrase), true, false); Assert.assertEquals("duplicate modification: one extra packet in original", 1, onlyA.size()); Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size()); @@ -1055,7 +1061,8 @@ public class PgpKeyOperationTest { Passphrase otherPassphrase = TestingUtils.genPassphrase(true); CryptoInputParcel otherCryptoInput = new CryptoInputParcel(otherPassphrase); parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase); - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(new Date())); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, + new CryptoInputParcel(new Date(), new Passphrase())); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 732c13f62..755a0b00d 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -190,11 +190,11 @@ public class UncachedKeyringMergeTest { parcel.reset(); parcel.mAddUserIds.add("flim"); - modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); parcel.reset(); parcel.mAddUserIds.add("flam"); - modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); } { // merge A into base @@ -231,8 +231,8 @@ public class UncachedKeyringMergeTest { parcel.reset(); parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); - modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); - modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); + modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2); subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2); @@ -273,7 +273,7 @@ public class UncachedKeyringMergeTest { parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1)); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( ringA.getEncoded(), false, 0); - modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); } { @@ -372,7 +372,7 @@ public class UncachedKeyringMergeTest { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( ringA.getEncoded(), false, 0); - modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(), parcel).getRing(); + modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing(); } { 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 25abab25b..4646aef8a 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 @@ -530,7 +530,6 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary), MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig), MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), - MSG_MF_INPUT_REQUIRED (LogLevel.OK, R.string.msg_mf_input_required), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), @@ -540,6 +539,9 @@ public abstract class OperationResult implements Parcelable { MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail), MSG_MF_PRIMARY_REPLACE_OLD (LogLevel.DEBUG, R.string.msg_mf_primary_replace_old), MSG_MF_PRIMARY_NEW (LogLevel.DEBUG, R.string.msg_mf_primary_new), + MSG_MF_RESTRICTED_MODE (LogLevel.INFO, R.string.msg_mf_restricted_mode), + MSG_MF_REQUIRE_DIVERT (LogLevel.OK, R.string.msg_mf_require_divert), + MSG_MF_REQUIRE_PASSPHRASE (LogLevel.OK, R.string.msg_mf_require_passphrase), MSG_MF_SUBKEY_CHANGE (LogLevel.INFO, R.string.msg_mf_subkey_change), MSG_MF_SUBKEY_NEW_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id), MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index f739cfb69..454a35cd6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -395,12 +395,14 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } - if (saveParcel.isRestrictedOnly()) { + if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) { + log.add(LogType.MSG_MF_RESTRICTED_MODE, indent); return internalRestricted(sKR, saveParcel, log); } // Do we require a passphrase? If so, pass it along if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) { + log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredPassphrase( masterSecretKey.getKeyID(), cryptoInput.getSignatureTime())); } @@ -971,7 +973,7 @@ public class PgpKeyOperation { progress(R.string.progress_done, 100); if (!nfcSignOps.isEmpty()) { - log.add(LogType.MSG_MF_INPUT_REQUIRED, indent); + log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent); return new PgpEditKeyResult(log, nfcSignOps.build()); } @@ -1459,6 +1461,12 @@ public class PgpKeyOperation { return flags; } + private static boolean isDummy(PGPSecretKey secretKey) { + S2K s2k = secretKey.getS2K(); + return s2k.getType() == S2K.GNU_DUMMY_S2K + && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY; + } + private static boolean isDivertToCard(PGPSecretKey secretKey) { S2K s2k = secretKey.getS2K(); return s2k.getType() == S2K.GNU_DUMMY_S2K diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 9fd278c13..88f258c58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -85,7 +85,7 @@ public class SaveKeyringParcel implements Parcelable { /** Returns true iff this parcel does not contain any operations which require a passphrase. */ public boolean isRestrictedOnly() { if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty() - || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty() + || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty() || !mRevokeSubKeys.isEmpty()) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 6edc48b09..21aacd1f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -36,7 +36,7 @@ public class CryptoInputParcel implements Parcelable { public CryptoInputParcel(Passphrase passphrase) { mSignatureTime = new Date(); - mPassphrase = null; + mPassphrase = passphrase; } public CryptoInputParcel(Date signatureTime) { diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index a7e9cdf7e..76514575c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -913,7 +913,7 @@ "Fatal error decrypting master key! This is likely a programming error, please file a bug report!" "Internal OpenPGP error!" "Signature exception!" - "Diverting to card/nfc for crypto operations" + "Tried to operate on missing subkey %s!" "Modifying master certifications" "Adding empty notation packet" "Adding PIN notation packet" @@ -923,8 +923,10 @@ "Passphrase for subkey could not be changed! (Does it have a different one from the other keys?)" "Replacing certificate of previous primary user ID" "Generating new certificate for new primary user ID" + "Changing to restricted operation mode" "Modifying subkey %s" - "Tried to operate on missing subkey %s!" + "Diverting to card/nfc for crypto operations" + "Passphrase required for operations" "Adding new subkey of type %s" "New subkey ID: %s" "Expiry date cannot be in the past!" From 88ca41d55586e5084cc3f177eb12617aa640ae1d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 20 Mar 2015 18:55:16 +0100 Subject: [PATCH 11/80] add edit key unit test for no-op --- .../keychain/pgp/PgpKeyOperationTest.java | 6 ++++++ .../keychain/operations/results/OperationResult.java | 1 + .../sufficientlysecure/keychain/pgp/PgpKeyOperation.java | 5 +++++ .../keychain/service/SaveKeyringParcel.java | 4 ++++ OpenKeychain/src/main/res/values/strings.xml | 1 + 5 files changed, 17 insertions(+) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index e6ada202b..54ccccc3d 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -312,6 +312,12 @@ public class PgpKeyOperationTest { ring, parcel, new CryptoInputParcel(badphrase), LogType.MSG_MF_UNLOCK_ERROR); } + { + parcel.reset(); + assertModifyFailure("no-op should fail", + ring, parcel, cryptoInput, LogType.MSG_MF_ERROR_NOOP); + } + } @Test 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 4646aef8a..033039df6 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 @@ -522,6 +522,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify), MSG_MF_ERROR_NOEXIST_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_noexist_primary), MSG_MF_ERROR_NOEXIST_REVOKE (LogLevel.ERROR, R.string.msg_mf_error_noexist_revoke), + MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop), MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry), MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master), MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 454a35cd6..f4eccf298 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -395,6 +395,11 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } + if (saveParcel.isEmpty()) { + log.add(LogType.MSG_MF_ERROR_NOOP, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) { log.add(LogType.MSG_MF_RESTRICTED_MODE, indent); return internalRestricted(sKR, saveParcel, log); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 88f258c58..2e0524141 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -82,6 +82,10 @@ public class SaveKeyringParcel implements Parcelable { mRevokeSubKeys = new ArrayList<>(); } + public boolean isEmpty() { + return isRestrictedOnly() && mChangeSubKeys.isEmpty(); + } + /** Returns true iff this parcel does not contain any operations which require a passphrase. */ public boolean isRestrictedOnly() { if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty() diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 76514575c..6c8ccd340 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -910,6 +910,7 @@ "Tried to execute restricted operation without passphrase! This is a programming error, please file a bug report!" "Revoked user IDs cannot be primary!" "Expiry time cannot be "same as before" on subkey creation. This is a programming error, please file a bug report!" + "Nothing to do!" "Fatal error decrypting master key! This is likely a programming error, please file a bug report!" "Internal OpenPGP error!" "Signature exception!" From 93c7eb72fbbf93938043566dfc1707b6714f325b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 21 Mar 2015 15:16:32 +0100 Subject: [PATCH 12/80] more data in RequiredInputParcel, OperationResult notifications - pass both masterkeyid and subkeyid though RequiredInputParcel parcel - fix numeric vales in OperationResult.createNotify() --- .../keychain/operations/CertifyOperation.java | 6 +- .../operations/results/EditKeyResult.java | 9 ++- .../operations/results/OperationResult.java | 22 ++++--- .../keychain/pgp/PgpCertifyOperation.java | 3 +- .../keychain/pgp/PgpKeyOperation.java | 7 ++- .../service/input/RequiredInputParcel.java | 39 +++++++++--- .../keychain/ui/NfcOperationActivity.java | 60 +++++++++++++++---- 7 files changed, 110 insertions(+), 36 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 7c299a6bd..eb2a3d33f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -81,7 +81,7 @@ public class CertifyOperation extends BaseOperation { if (!cryptoInput.hasPassphrase()) { return new CertifyResult(log, RequiredInputParcel.createRequiredPassphrase( - certificationKey.getKeyId(), null)); + certificationKey.getKeyId(), certificationKey.getKeyId(), null)); } // certification is always with the master key id, so use that one @@ -105,7 +105,9 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; - NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder( + cryptoInput.getSignatureTime(), certificationKey.getKeyId(), + certificationKey.getKeyId()); // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java index abcf575af..842b75c3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java @@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult { public EditKeyResult(Parcel source) { super(source); - mMasterKeyId = source.readLong(); + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mMasterKeyId); + if (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } else { + dest.writeInt(0); + } } public static Creator CREATOR = new Creator() { 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 033039df6..d6e71046d 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 @@ -250,12 +250,20 @@ public abstract class OperationResult implements Parcelable { public Showable createNotify(final Activity activity) { - Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast()); - Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType); - Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId()); - // Take the last message as string - int msgId = mLog.getLast().mType.getMsgId(); + String logText; + + LogEntryParcel entryParcel = mLog.getLast(); + // special case: first parameter may be a quantity + if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0 + && entryParcel.mParameters[0] instanceof Integer) { + logText = activity.getResources().getQuantityString(entryParcel.mType.getMsgId(), + (Integer) entryParcel.mParameters[0], + entryParcel.mParameters); + } else { + logText = activity.getString(entryParcel.mType.getMsgId(), + entryParcel.mParameters); + } Style style; @@ -273,10 +281,10 @@ public abstract class OperationResult implements Parcelable { } if (getLog() == null || getLog().isEmpty()) { - return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style); + return Notify.createNotify(activity, logText, Notify.LENGTH_LONG, style); } - return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style, + return Notify.createNotify(activity, logText, Notify.LENGTH_LONG, style, new ActionListener() { @Override public void onAction() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index ff162775a..90ec3053f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -58,7 +58,8 @@ public class PgpCertifyOperation { // get the master subkey (which we certify for) PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey(); - NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp); + NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp, + publicKey.getKeyID(), publicKey.getKeyID()); try { if (action.mUserIds != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index f4eccf298..f73bada06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -409,7 +409,8 @@ public class PgpKeyOperation { if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) { log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredPassphrase( - masterSecretKey.getKeyID(), cryptoInput.getSignatureTime())); + masterSecretKey.getKeyID(), masterSecretKey.getKeyID(), + cryptoInput.getSignatureTime())); } // read masterKeyFlags, and use the same as before. @@ -431,7 +432,9 @@ public class PgpKeyOperation { int indent = 1; - NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(cryptoInput.getSignatureTime()); + NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder( + cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(), + masterSecretKey.getKeyID()); progress(R.string.progress_modify, 0); 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 3d91812eb..d8d87114e 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 @@ -18,19 +18,20 @@ public class RequiredInputParcel implements Parcelable { public final RequiredInputType mType; - public String mNfcPin = "123456"; public final byte[][] mInputHashes; public final int[] mSignAlgos; + private Long mMasterKeyId; private Long mSubKeyId; private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, - int[] signAlgos, Date signatureTime, Long keyId) { + int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { mType = type; mInputHashes = inputHashes; mSignAlgos = signAlgos; mSignatureTime = signatureTime; - mSubKeyId = keyId; + mMasterKeyId = masterKeyId; + mSubKeyId = subKeyId; } public RequiredInputParcel(Parcel source) { @@ -50,6 +51,7 @@ public class RequiredInputParcel implements Parcelable { } mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null; + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; mSubKeyId = source.readInt() != 0 ? source.readLong() : null; } @@ -61,19 +63,28 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createNfcSignOperation( byte[] inputHash, int signAlgo, Date signatureTime) { return new RequiredInputParcel(RequiredInputType.NFC_SIGN, - new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, null); + new byte[][] { inputHash }, new int[] { signAlgo }, + signatureTime, null, null); } public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash) { return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, - new byte[][] { inputHash }, null, null, null); + new byte[][] { inputHash }, null, null, null, null); } - public static RequiredInputParcel createRequiredPassphrase(long keyId, Date signatureTime) { + public static RequiredInputParcel createRequiredPassphrase( + long masterKeyId, long subKeyId, Date signatureTime) { return new RequiredInputParcel(RequiredInputType.PASSPHRASE, - null, null, signatureTime, keyId); + null, null, signatureTime, masterKeyId, subKeyId); } + public static RequiredInputParcel createRequiredPassphrase( + RequiredInputParcel req) { + return new RequiredInputParcel(RequiredInputType.PASSPHRASE, + null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId); + } + + @Override public int describeContents() { return 0; @@ -98,6 +109,12 @@ public class RequiredInputParcel implements Parcelable { } else { dest.writeInt(0); } + if (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } else { + dest.writeInt(0); + } if (mSubKeyId != null) { dest.writeInt(1); dest.writeLong(mSubKeyId); @@ -121,9 +138,13 @@ public class RequiredInputParcel implements Parcelable { Date mSignatureTime; ArrayList mSignAlgos = new ArrayList<>(); ArrayList mInputHashes = new ArrayList<>(); + long mMasterKeyId; + long mSubKeyId; - public NfcSignOperationsBuilder(Date signatureTime) { + public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) { mSignatureTime = signatureTime; + mMasterKeyId = masterKeyId; + mSubKeyId = subKeyId; } public RequiredInputParcel build() { @@ -135,7 +156,7 @@ public class RequiredInputParcel implements Parcelable { } return new RequiredInputParcel(RequiredInputType.NFC_SIGN, - inputHashes, signAlgos, mSignatureTime, null); + inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId); } public void addHash(byte[] hash, int algo) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index 69467daac..68eee2513 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -26,6 +26,8 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.nio.ByteBuffer; @@ -40,6 +42,8 @@ import java.nio.ByteBuffer; @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) public class NfcOperationActivity extends BaseActivity { + public static final int REQUEST_CODE_PASSPHRASE = 1; + public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String RESULT_DATA = "result_data"; @@ -49,7 +53,8 @@ public class NfcOperationActivity extends BaseActivity { private NfcAdapter mNfcAdapter; private IsoDep mIsoDep; - RequiredInputParcel mNfcOperations; + RequiredInputParcel mRequiredInput; + private Passphrase mPin; @Override protected void onCreate(Bundle savedInstanceState) { @@ -66,8 +71,38 @@ public class NfcOperationActivity extends BaseActivity { Bundle data = intent.getExtras(); - mNfcOperations = data.getParcelable(EXTRA_REQUIRED_INPUT); + mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); + obtainPassphrase(); + + } + + private void obtainPassphrase() { + + Preferences prefs = Preferences.getPreferences(this); + if (prefs.useDefaultYubikeyPin()) { + mPin = new Passphrase("123456"); + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: + CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); + mPin = input.getPassphrase(); + break; + + default: + super.onActivityResult(requestCode, resultCode, data); + } } @Override @@ -157,7 +192,7 @@ public class NfcOperationActivity extends BaseActivity { return; } - String pin = mNfcOperations.mNfcPin; + byte[] pin = new String(mPin.getCharArray()).getBytes(); // Command APDU for VERIFY command (page 32) String login = @@ -165,8 +200,8 @@ public class NfcOperationActivity extends BaseActivity { + "20" // INS + "00" // P1 + "82" // P2 (PW1) - + String.format("%02x", pin.length()) // Lc - + Hex.toHexString(pin.getBytes()); + + String.format("%02x", pin.length) // Lc + + Hex.toHexString(pin); if ( ! card(login).equals(accepted)) { // login toast("Wrong PIN!"); setResult(RESULT_CANCELED); @@ -174,23 +209,22 @@ public class NfcOperationActivity extends BaseActivity { return; } - CryptoInputParcel resultData = new CryptoInputParcel(mNfcOperations.mSignatureTime); + CryptoInputParcel resultData = new CryptoInputParcel(mRequiredInput.mSignatureTime); - switch (mNfcOperations.mType) { + switch (mRequiredInput.mType) { case NFC_DECRYPT: - - for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) { - byte[] hash = mNfcOperations.mInputHashes[i]; + for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { + byte[] hash = mRequiredInput.mInputHashes[i]; byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); resultData.addCryptoData(hash, decryptedSessionKey); } break; case NFC_SIGN: - for (int i = 0; i < mNfcOperations.mInputHashes.length; i++) { - byte[] hash = mNfcOperations.mInputHashes[i]; - int algo = mNfcOperations.mSignAlgos[i]; + for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { + byte[] hash = mRequiredInput.mInputHashes[i]; + int algo = mRequiredInput.mSignAlgos[i]; byte[] signedHash = nfcCalculateSignature(hash, algo); resultData.addCryptoData(hash, signedHash); } From 147003123fffc84b1d658f78d0a888479ce4ff35 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 21 Mar 2015 17:13:31 +0100 Subject: [PATCH 13/80] first steps toward yubikey activity - move BaseActivity into new package - extract BaseNfcActivity from NfcOperationsActivity --- .../remote/ui/AccountSettingsActivity.java | 2 +- .../remote/ui/AppSettingsActivity.java | 4 +- .../remote/ui/RemoteServiceActivity.java | 2 +- .../remote/ui/SelectSignKeyIdActivity.java | 2 +- .../ui/CertifyFingerprintActivity.java | 1 + .../keychain/ui/CertifyKeyActivity.java | 2 + .../keychain/ui/CreateKeyActivity.java | 8 +- .../keychain/ui/CreateKeyStartFragment.java | 10 +- .../keychain/ui/CreateKeyYubiFragment.java | 90 ++++ .../keychain/ui/DecryptFilesActivity.java | 2 +- .../keychain/ui/DecryptTextActivity.java | 1 + .../keychain/ui/EditKeyActivity.java | 1 + .../keychain/ui/EncryptActivity.java | 1 + .../keychain/ui/HelpActivity.java | 2 + .../keychain/ui/ImportKeysActivity.java | 1 + .../keychain/ui/LogDisplayActivity.java | 2 + .../keychain/ui/NfcActivity.java | 1 + .../keychain/ui/NfcIntentActivity.java | 1 + .../keychain/ui/NfcOperationActivity.java | 446 +---------------- .../keychain/ui/QrCodeViewActivity.java | 1 + .../keychain/ui/SafeSlingerActivity.java | 1 + .../ui/SettingsKeyServerActivity.java | 1 + .../keychain/ui/UploadKeyActivity.java | 1 + .../keychain/ui/ViewCertActivity.java | 1 + .../keychain/ui/ViewKeyActivity.java | 1 + .../keychain/ui/ViewKeyAdvActivity.java | 1 + .../keychain/ui/{ => base}/BaseActivity.java | 6 +- .../keychain/ui/base/BaseNfcActivity.java | 448 ++++++++++++++++++ .../res/layout/create_key_start_fragment.xml | 34 +- .../res/layout/create_yubikey_fragment.xml | 83 ++++ 30 files changed, 689 insertions(+), 468 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/{ => base}/BaseActivity.java (96%) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java create mode 100644 OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 507d4dea5..e19757d65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class AccountSettingsActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 407480c98..2b71d6dc1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -40,9 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.ui.BaseActivity; -import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index e8c3e4511..f312c0d44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 98a44466d..cb9f46f7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index b7c80c1ed..016ab5f3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -23,6 +23,7 @@ import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class CertifyFingerprintActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 1fb88b182..3845e07cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + /** * Signs the specified public key with the specified secret master key 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 ab76f693e..4ae901c6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,12 +17,18 @@ package org.sufficientlysecure.keychain.ui; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.view.View; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import java.util.ArrayList; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 180a52a1c..d23d598fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment { mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); -// mYubiKey = view.findViewById(R.id.create_key_yubikey_button); + mYubiKey = view.findViewById(R.id.create_key_yubikey_button); mCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { @@ -95,6 +95,14 @@ public class CreateKeyStartFragment extends Fragment { } }); + mYubiKey.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CreateKeyYubiFragment frag = new CreateKeyYubiFragment(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + }); + mImportKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java new file mode 100644 index 000000000..665c68d65 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java @@ -0,0 +1,90 @@ +/* + * 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 android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.widget.NameEditText; + + +public class CreateKeyYubiFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + NameEditText mNameEdit; + View mBackButton; + View mNextButton; + + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().length() == 0) { + editText.setError(context.getString(R.string.create_key_empty)); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void nextClicked() { + if (isEditTextNotEmpty(getActivity(), mNameEdit)) { + // save state + mCreateKeyActivity.mName = mNameEdit.getText().toString(); + + CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index 162b10eca..dce2386b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -21,12 +21,12 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.PersistableBundle; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class DecryptFilesActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java index bc2ec014a..728e3ba41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 6dc2994cf..b607ba9f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -23,6 +23,7 @@ import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class EditKeyActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index b87dec8fc..5254af19f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -33,6 +33,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Passphrase; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index cd6cdf4d6..4aa706f57 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -26,6 +26,8 @@ import com.astuetz.PagerSlidingTabStrip; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + public class HelpActivity extends BaseActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 9143609ad..9a7c405d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java index 0de7bb391..df325d31d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -22,6 +22,8 @@ import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + public class LogDisplayActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java index 7311f4879..57acf3e93 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java @@ -22,6 +22,7 @@ import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java index 0ccb206d1..a1affbc39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java @@ -21,6 +21,7 @@ import android.widget.Toast; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index 68eee2513..549d9ece7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -7,31 +7,19 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.PendingIntent; import android.content.Intent; -import android.content.IntentFilter; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.tech.IsoDep; import android.os.Build; import android.os.Bundle; import android.view.WindowManager; -import android.widget.Toast; -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; -import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; -import java.nio.ByteBuffer; - /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant @@ -40,21 +28,13 @@ import java.nio.ByteBuffer; * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1) -public class NfcOperationActivity extends BaseActivity { - - public static final int REQUEST_CODE_PASSPHRASE = 1; +public class NfcOperationActivity extends BaseNfcActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String RESULT_DATA = "result_data"; - private static final int TIMEOUT = 100000; - - private NfcAdapter mNfcAdapter; - private IsoDep mIsoDep; - RequiredInputParcel mRequiredInput; - private Passphrase mPin; @Override protected void onCreate(Bundle savedInstanceState) { @@ -64,45 +44,12 @@ public class NfcOperationActivity extends BaseActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Intent intent = getIntent(); - String action = intent.getAction(); - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { - throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); - } - Bundle data = intent.getExtras(); mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); - obtainPassphrase(); - - } - - private void obtainPassphrase() { - - Preferences prefs = Preferences.getPreferences(this); - if (prefs.useDefaultYubikeyPin()) { - mPin = new Passphrase("123456"); - return; - } - - Intent intent = new Intent(this, PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, - RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); - startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PASSPHRASE: - CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); - mPin = input.getPassphrase(); - break; - - default: - super.onActivityResult(requestCode, resultCode, data); - } + // obtain passphrase for this subkey + obtainYubikeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); } @Override @@ -110,104 +57,8 @@ public class NfcOperationActivity extends BaseActivity { setContentView(R.layout.nfc_activity); } - /** - * Called when the system is about to start resuming a previous activity, - * disables NFC Foreground Dispatch - */ - public void onPause() { - super.onPause(); - Log.d(Constants.TAG, "NfcOperationActivity.onPause"); - - disableNfcForegroundDispatch(); - } - - /** - * Called when the activity will start interacting with the user, - * enables NFC Foreground Dispatch - */ - public void onResume() { - super.onResume(); - Log.d(Constants.TAG, "NfcOperationActivity.onResume"); - - enableNfcForegroundDispatch(); - } - - /** - * This activity is started as a singleTop activity. - * All new NFC Intents which are delivered to this activity are handled here - */ - public void onNewIntent(Intent intent) { - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { - try { - handleNdefDiscoveredIntent(intent); - } catch (IOException e) { - Log.e(Constants.TAG, "Connection error!", e); - toast("Connection Error: " + e.getMessage()); - setResult(RESULT_CANCELED); - finish(); - } - } - } - - /** Handle NFC communication and return a result. - * - * This method is called by onNewIntent above upon discovery of an NFC tag. - * It handles initialization and login to the application, subsequently - * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then - * finishes the activity with an appropiate result. - * - * On general communication, see also - * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - * - * References to pages are generally related to the OpenPGP Application - * on ISO SmartCard Systems specification. - * - */ - private void handleNdefDiscoveredIntent(Intent intent) throws IOException { - - Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - - // Connect to the detected tag, setting a couple of settings - mIsoDep = IsoDep.get(detectedTag); - mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation - mIsoDep.connect(); - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - - // Command APDU (page 51) for SELECT FILE command (page 29) - String opening = - "00" // CLA - + "A4" // INS - + "04" // P1 - + "00" // P2 - + "06" // Lc (number of bytes) - + "D27600012401" // Data (6 bytes) - + "00"; // Le - if ( ! card(opening).equals(accepted)) { // activate connection - toast("Opening Error!"); - setResult(RESULT_CANCELED); - finish(); - return; - } - - byte[] pin = new String(mPin.getCharArray()).getBytes(); - - // Command APDU for VERIFY command (page 32) - String login = - "00" // CLA - + "20" // INS - + "00" // P1 - + "82" // P2 (PW1) - + String.format("%02x", pin.length) // Lc - + Hex.toHexString(pin); - if ( ! card(login).equals(accepted)) { // login - toast("Wrong PIN!"); - setResult(RESULT_CANCELED); - finish(); - return; - } + @Override + protected void onNfcPerform() throws IOException { CryptoInputParcel resultData = new CryptoInputParcel(mRequiredInput.mSignatureTime); @@ -233,292 +84,9 @@ public class NfcOperationActivity extends BaseActivity { // give data through for new service call Intent result = new Intent(); - result.putExtra(RESULT_DATA, resultData); + result.putExtra(NfcOperationActivity.RESULT_DATA, resultData); setResult(RESULT_OK, result); finish(); } - - /** - * Gets the user ID - * - * @return the user id as "name " - * @throws java.io.IOException - */ - public String getUserId() throws IOException { - String info = "00CA006500"; - String data = "00CA005E00"; - return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">"; - } - - /** Return the key id from application specific data stored on tag, or null - * if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The long key id of the requested key, or null if not found. - */ - public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException { - byte[] fp = nfcGetFingerprint(isoDep, idx); - if (fp == null) { - return null; - } - ByteBuffer buf = ByteBuffer.wrap(fp); - // skip first 12 bytes of the fingerprint - buf.position(12); - // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) - return buf.getLong(); - } - - /** Return fingerprints of all keys from application specific data stored - * on tag, or null if data not available. - * - * @return The fingerprints of all subkeys in a contiguous byte array. - */ - public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException { - String data = "00CA006E00"; - byte[] buf = isoDep.transceive(Hex.decode(data)); - - Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); - Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); - - Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); - if (fptlv == null) { - return null; - } - - return fptlv.mV; - } - - /** Return the fingerprint from application specific data stored on tag, or - * null if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The fingerprint of the requested key, or null if not found. - */ - public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException { - byte[] data = nfcGetFingerprints(isoDep); - - // return the master key fingerprint - ByteBuffer fpbuf = ByteBuffer.wrap(data); - byte[] fp = new byte[20]; - fpbuf.position(idx*20); - fpbuf.get(fp, 0, 20); - - return fp; - } - - /** - * Calls to calculate the signature and returns the MPI value - * - * @param hash the hash for signing - * @return a big integer representing the MPI for the given hash - * @throws java.io.IOException - */ - public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { - - // dsi, including Lc - String dsi; - - Log.i(Constants.TAG, "Hash: " + hashAlgo); - switch (hashAlgo) { - case HashAlgorithmTags.SHA1: - if (hash.length != 20) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!"); - } - dsi = "23" // Lc - + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes - + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes - + "0605" + "2B0E03021A" // OID of SHA1 - + "0500" // TLV coding of ZERO - + "0414" + getHex(hash); // 0x14 are 20 hash bytes - break; - case HashAlgorithmTags.RIPEMD160: - if (hash.length != 20) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!"); - } - dsi = "233021300906052B2403020105000414" + getHex(hash); - break; - case HashAlgorithmTags.SHA224: - if (hash.length != 28) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!"); - } - dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); - break; - case HashAlgorithmTags.SHA256: - if (hash.length != 32) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!"); - } - dsi = "333031300D060960864801650304020105000420" + getHex(hash); - break; - case HashAlgorithmTags.SHA384: - if (hash.length != 48) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!"); - } - dsi = "433041300D060960864801650304020205000430" + getHex(hash); - break; - case HashAlgorithmTags.SHA512: - if (hash.length != 64) { - throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!"); - } - dsi = "533051300D060960864801650304020305000440" + getHex(hash); - break; - default: - throw new RuntimeException("Not supported hash algo!"); - } - - // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) - String apdu = - "002A9E9A" // CLA, INS, P1, P2 - + dsi // digital signature input - + "00"; // Le - - String response = card(apdu); - - // split up response into signature and status - String status = response.substring(response.length()-4); - String signature = response.substring(0, response.length() - 4); - - // while we are getting 0x61 status codes, retrieve more data - while (status.substring(0, 2).equals("61")) { - Log.d(Constants.TAG, "requesting more data, status " + status); - // Send GET RESPONSE command - response = card("00C00000" + status.substring(2)); - status = response.substring(response.length()-4); - signature += response.substring(0, response.length()-4); - } - - Log.d(Constants.TAG, "final response:" + status); - - if ( ! status.equals("9000")) { - toast("Bad NFC response code: " + status); - return null; - } - - // Make sure the signature we received is actually the expected number of bytes long! - if (signature.length() != 256 && signature.length() != 512) { - toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); - return null; - } - - return Hex.decode(signature); - } - - /** - * Calls to calculate the signature and returns the MPI value - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - * @throws java.io.IOException - */ - public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { - String firstApdu = "102a8086fe"; - String secondApdu = "002a808603"; - String le = "00"; - - byte[] one = new byte[254]; - // leave out first byte: - System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); - - byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; - for (int i = 0; i < two.length; i++) { - two[i] = encryptedSessionKey[i + one.length + 1]; - } - - String first = card(firstApdu + getHex(one)); - String second = card(secondApdu + getHex(two) + le); - - String decryptedSessionKey = getDataField(second); - - Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); - - return Hex.decode(decryptedSessionKey); - } - - /** - * Prints a message to the screen - * - * @param text the text which should be contained within the toast - */ - private void toast(String text) { - Toast.makeText(this, text, Toast.LENGTH_LONG).show(); - } - - /** - * Receive new NFC Intents to this activity only by enabling foreground dispatch. - * This can only be done in onResume! - */ - public void enableNfcForegroundDispatch() { - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - Intent nfcI = new Intent(this, NfcOperationActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); - IntentFilter[] writeTagFilters = new IntentFilter[]{ - new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) - }; - - // https://code.google.com/p/android/issues/detail?id=62918 - // maybe mNfcAdapter.enableReaderMode(); ? - try { - mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); - } catch (IllegalStateException e) { - Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e); - } - Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); - } - - /** - * Disable foreground dispatch in onPause! - */ - public void disableNfcForegroundDispatch() { - mNfcAdapter.disableForegroundDispatch(this); - Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); - } - - /** - * Gets the name of the user out of the raw card output regarding card holder related data - * - * @param name the raw card holder related data from the card - * @return the name given in this data - */ - public String getName(String name) { - String slength; - int ilength; - name = name.substring(6); - slength = name.substring(0, 2); - ilength = Integer.parseInt(slength, 16) * 2; - name = name.substring(2, ilength + 2); - name = (new String(Hex.decode(name))).replace('<', ' '); - return (name); - } - - /** - * Reduces the raw data from the card by four characters - * - * @param output the raw data from the card - * @return the data field of that data - */ - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - - /** - * Communicates with the OpenPgpCard via the APDU - * - * @param hex the hexadecimal APDU - * @return The answer from the card - * @throws java.io.IOException throws an exception if something goes wrong - */ - public String card(String hex) throws IOException { - return getHex(mIsoDep.transceive(Hex.decode(hex))); - } - - /** - * Converts a byte array into an hex string - * - * @param raw the byte array representation - * @return the hexadecimal string representation - */ - public static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java index 43af07bbe..d4858ee5d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index 863aef65f..a41416a47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index 080dc2495..9f2e46b38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.widget.Editor; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 4fb2074a5..ad13e390d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index a80503591..8d876ba69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 484956e6a..fc30eafcc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -66,6 +66,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index f17d6e0fd..9e8a12c8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java similarity index 96% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 41fa50705..07d2ef8c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.ui; +package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.os.Bundle; @@ -63,8 +63,8 @@ public abstract class BaseActivity extends ActionBarActivity { * Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines * see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs */ - protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, - View.OnClickListener cancelOnClickListener) { + public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, + View.OnClickListener cancelOnClickListener) { setActionBarIcon(R.drawable.ic_close_white_24dp); // Inflate the custom action bar view diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java new file mode 100644 index 000000000..0a7b7611b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -0,0 +1,448 @@ +package org.sufficientlysecure.keychain.ui.base; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.widget.Toast; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.Preferences; + + +public abstract class BaseNfcActivity extends BaseActivity { + + public static final int REQUEST_CODE_PASSPHRASE = 1; + + protected Passphrase mPin; + private NfcAdapter mNfcAdapter; + private IsoDep mIsoDep; + + private static final int TIMEOUT = 100000; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); + } + + } + + /** + * This activity is started as a singleTop activity. + * All new NFC Intents which are delivered to this activity are handled here + */ + @Override + public void onNewIntent(Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + handleNdefDiscoveredIntent(intent); + } catch (IOException e) { + Log.e(Constants.TAG, "Connection error!", e); + toast("Connection Error: " + e.getMessage()); + setResult(RESULT_CANCELED); + finish(); + } + } + } + + /** + * Called when the system is about to start resuming a previous activity, + * disables NFC Foreground Dispatch + */ + public void onPause() { + super.onPause(); + Log.d(Constants.TAG, "NfcOperationActivity.onPause"); + + disableNfcForegroundDispatch(); + } + + /** + * Called when the activity will start interacting with the user, + * enables NFC Foreground Dispatch + */ + public void onResume() { + super.onResume(); + Log.d(Constants.TAG, "NfcOperationActivity.onResume"); + + enableNfcForegroundDispatch(); + } + + protected void obtainYubikeyPin(RequiredInputParcel requiredInput) { + + Preferences prefs = Preferences.getPreferences(this); + if (prefs.useDefaultYubikeyPin()) { + mPin = new Passphrase("123456"); + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(requiredInput)); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + + } + + protected void setYubikeyPin(Passphrase pin) { + mPin = pin; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: + CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); + mPin = input.getPassphrase(); + break; + + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** Handle NFC communication and return a result. + * + * This method is called by onNewIntent above upon discovery of an NFC tag. + * It handles initialization and login to the application, subsequently + * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then + * finishes the activity with an appropiate result. + * + * On general communication, see also + * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx + * + * References to pages are generally related to the OpenPGP Application + * on ISO SmartCard Systems specification. + * + */ + protected void handleNdefDiscoveredIntent(Intent intent) throws IOException { + + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + // Connect to the detected tag, setting a couple of settings + mIsoDep = IsoDep.get(detectedTag); + mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation + mIsoDep.connect(); + + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. + // See specification, page 51 + String accepted = "9000"; + + // Command APDU (page 51) for SELECT FILE command (page 29) + String opening = + "00" // CLA + + "A4" // INS + + "04" // P1 + + "00" // P2 + + "06" // Lc (number of bytes) + + "D27600012401" // Data (6 bytes) + + "00"; // Le + if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection + toast("Opening Error!"); + setResult(RESULT_CANCELED); + finish(); + return; + } + + if (mPin != null) { + + byte[] pin = new String(mPin.getCharArray()).getBytes(); + + // Command APDU for VERIFY command (page 32) + String login = + "00" // CLA + + "20" // INS + + "00" // P1 + + "82" // P2 (PW1) + + String.format("%02x", pin.length) // Lc + + Hex.toHexString(pin); + if (!nfcCommunicate(login).equals(accepted)) { // login + toast("Wrong PIN!"); + setResult(RESULT_CANCELED); + finish(); + return; + } + + } + + onNfcPerform(); + + mIsoDep.close(); + mIsoDep = null; + + } + + protected abstract void onNfcPerform() throws IOException; + + /** Return the key id from application specific data stored on tag, or null + * if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The long key id of the requested key, or null if not found. + */ + public Long nfcGetKeyId(int idx) throws IOException { + byte[] fp = nfcGetFingerprint(idx); + if (fp == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(fp); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); + } + + /** Return fingerprints of all keys from application specific data stored + * on tag, or null if data not available. + * + * @return The fingerprints of all subkeys in a contiguous byte array. + */ + public byte[] nfcGetFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mIsoDep.transceive(Hex.decode(data)); + + Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); + Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); + + Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); + if (fptlv == null) { + return null; + } + + return fptlv.mV; + } + + /** Return the fingerprint from application specific data stored on tag, or + * null if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The fingerprint of the requested key, or null if not found. + */ + public byte[] nfcGetFingerprint(int idx) throws IOException { + byte[] data = nfcGetFingerprints(); + + // return the master key fingerprint + ByteBuffer fpbuf = ByteBuffer.wrap(data); + byte[] fp = new byte[20]; + fpbuf.position(idx*20); + fpbuf.get(fp, 0, 20); + + return fp; + } + + + public String nfcGetUserId() throws IOException { + String info = "00CA006500"; + String data = "00CA005E00"; + return nfcGetHolderName(nfcCommunicate(info)) + " <" + (new String(Hex.decode(nfcGetDataField( + nfcCommunicate(data))))) + ">"; + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + */ + public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { + + // dsi, including Lc + String dsi; + + Log.i(Constants.TAG, "Hash: " + hashAlgo); + switch (hashAlgo) { + case HashAlgorithmTags.SHA1: + if (hash.length != 20) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!"); + } + dsi = "23" // Lc + + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes + + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes + + "0605" + "2B0E03021A" // OID of SHA1 + + "0500" // TLV coding of ZERO + + "0414" + getHex(hash); // 0x14 are 20 hash bytes + break; + case HashAlgorithmTags.RIPEMD160: + if (hash.length != 20) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!"); + } + dsi = "233021300906052B2403020105000414" + getHex(hash); + break; + case HashAlgorithmTags.SHA224: + if (hash.length != 28) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!"); + } + dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); + break; + case HashAlgorithmTags.SHA256: + if (hash.length != 32) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!"); + } + dsi = "333031300D060960864801650304020105000420" + getHex(hash); + break; + case HashAlgorithmTags.SHA384: + if (hash.length != 48) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!"); + } + dsi = "433041300D060960864801650304020205000430" + getHex(hash); + break; + case HashAlgorithmTags.SHA512: + if (hash.length != 64) { + throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!"); + } + dsi = "533051300D060960864801650304020305000440" + getHex(hash); + break; + default: + throw new RuntimeException("Not supported hash algo!"); + } + + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) + String apdu = + "002A9E9A" // CLA, INS, P1, P2 + + dsi // digital signature input + + "00"; // Le + + String response = nfcCommunicate(apdu); + + // split up response into signature and status + String status = response.substring(response.length()-4); + String signature = response.substring(0, response.length() - 4); + + // while we are getting 0x61 status codes, retrieve more data + while (status.substring(0, 2).equals("61")) { + Log.d(Constants.TAG, "requesting more data, status " + status); + // Send GET RESPONSE command + response = nfcCommunicate("00C00000" + status.substring(2)); + status = response.substring(response.length()-4); + signature += response.substring(0, response.length()-4); + } + + Log.d(Constants.TAG, "final response:" + status); + + if ( ! "9000".equals(status)) { + toast("Bad NFC response code: " + status); + return null; + } + + // Make sure the signature we received is actually the expected number of bytes long! + if (signature.length() != 256 && signature.length() != 512) { + toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); + return null; + } + + return Hex.decode(signature); + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { + String firstApdu = "102a8086fe"; + String secondApdu = "002a808603"; + String le = "00"; + + byte[] one = new byte[254]; + // leave out first byte: + System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); + + byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; + for (int i = 0; i < two.length; i++) { + two[i] = encryptedSessionKey[i + one.length + 1]; + } + + String first = nfcCommunicate(firstApdu + getHex(one)); + String second = nfcCommunicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = nfcGetDataField(second); + + Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + + return Hex.decode(decryptedSessionKey); + } + + /** + * Prints a message to the screen + * + * @param text the text which should be contained within the toast + */ + protected void toast(String text) { + Toast.makeText(this, text, Toast.LENGTH_LONG).show(); + } + + /** + * Receive new NFC Intents to this activity only by enabling foreground dispatch. + * This can only be done in onResume! + */ + public void enableNfcForegroundDispatch() { + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + Intent nfcI = new Intent(this, NfcOperationActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); + IntentFilter[] writeTagFilters = new IntentFilter[]{ + new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + }; + + // https://code.google.com/p/android/issues/detail?id=62918 + // maybe mNfcAdapter.enableReaderMode(); ? + try { + mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); + } catch (IllegalStateException e) { + Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e); + } + Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); + } + + /** + * Disable foreground dispatch in onPause! + */ + public void disableNfcForegroundDispatch() { + mNfcAdapter.disableForegroundDispatch(this); + Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); + } + + public String nfcGetHolderName(String name) { + String slength; + int ilength; + name = name.substring(6); + slength = name.substring(0, 2); + ilength = Integer.parseInt(slength, 16) * 2; + name = name.substring(2, ilength + 2); + name = (new String(Hex.decode(name))).replace('<', ' '); + return (name); + } + + private String nfcGetDataField(String output) { + return output.substring(0, output.length() - 4); + } + + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoDep.transceive(Hex.decode(apdu))); + } + + public static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + +} diff --git a/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml index 79ffe58b1..2db147475 100644 --- a/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml @@ -50,22 +50,22 @@ android:clickable="true" style="?android:attr/borderlessButtonStyle" /> - - - - - - - - - - - - - - - - + - \ No newline at end of file + diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml new file mode 100644 index 000000000..d8c95c658 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1ad3635d139ea5033b06e5cdd87a7b2eab5f2e75 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 21 Mar 2015 19:52:10 +0100 Subject: [PATCH 14/80] work on ad-hoc yubikey import support --- OpenKeychain/src/main/AndroidManifest.xml | 2 + .../operations/results/PromoteKeyResult.java | 9 +- .../service/KeychainIntentService.java | 2 +- .../keychain/ui/CreateKeyActivity.java | 22 +- .../keychain/ui/CreateKeyYubiFragment.java | 261 +++++++++++++++++- .../keychain/ui/base/BaseNfcActivity.java | 7 +- .../res/layout/create_yubikey_fragment.xml | 101 +++++-- 7 files changed, 366 insertions(+), 38 deletions(-) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index d983498cc..fd999b0fb 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -90,6 +90,8 @@ android:name=".ui.CreateKeyActivity" android:windowSoftInputMode="adjustResize" android:label="@string/title_manage_my_keys" + android:launchMode="singleTop" + android:allowTaskReparenting="true" android:parentActivityName=".ui.MainActivity"> CREATOR = new Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index ed6453e9d..5a9c146f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -487,7 +487,7 @@ public class KeychainIntentService extends IntentService implements Progressable case ACTION_PROMOTE_KEYRING: { // Input - long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID); + long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); // Operation PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); 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 4ae901c6c..9919e2aab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,23 +17,18 @@ package org.sufficientlysecure.keychain.ui; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.IntentFilter; -import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.util.Passphrase; +import java.io.IOException; import java.util.ArrayList; -public class CreateKeyActivity extends BaseActivity { +public class CreateKeyActivity extends BaseNfcActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; @@ -85,6 +80,13 @@ public class CreateKeyActivity extends BaseActivity { } } + @Override + protected void onNfcPerform() throws IOException { + if (mCurrentFragment instanceof NfcListenerFragment) { + ((NfcListenerFragment) mCurrentFragment).onNfcPerform(); + } + } + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -135,4 +137,8 @@ public class CreateKeyActivity extends BaseActivity { getSupportFragmentManager().executePendingTransactions(); } + interface NfcListenerFragment { + public void onNfcPerform() throws IOException; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java index 665c68d65..483a5f4e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java @@ -17,26 +17,77 @@ package org.sufficientlysecure.keychain.ui; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; + import android.app.Activity; +import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import android.widget.TextView; +import android.widget.ViewAnimator; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +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.ui.widget.NameEditText; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiFragment extends Fragment { +public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragment, + LoaderManager.LoaderCallbacks { CreateKeyActivity mCreateKeyActivity; NameEditText mNameEdit; View mBackButton; View mNextButton; + private TextView mUnknownFingerprint; + + public static final String ARGS_MASTER_KEY_ID = "master_key_id"; + private byte[] mScannedFingerprint; + private long mScannedMasterKeyId; + private ViewAnimator mAnimator; + private TextView mFingerprint; + private TextView mUserId; + + private YubiImportState mState = YubiImportState.SCAN; + + enum YubiImportState { + SCAN, // waiting for scan + UNKNOWN, // scanned unknown key (ready to import) + BAD_FINGERPRINT, // scanned key, bad fingerprint + IMPORTED, // imported key (ready to promote) + } private static boolean isEditTextNotEmpty(Context context, EditText editText) { boolean output = true; @@ -55,6 +106,16 @@ public class CreateKeyYubiFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false); + mAnimator = (ViewAnimator) view.findViewById(R.id.create_yubikey_animator); + + mUnknownFingerprint = (TextView) view.findViewById(R.id.create_yubikey_unknown_fp); + + mFingerprint = (TextView) view.findViewById(R.id.create_yubikey_fingerprint); + mUserId = (TextView) view.findViewById(R.id.create_yubikey_user_id); + + 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) { @@ -77,14 +138,200 @@ public class CreateKeyYubiFragment extends Fragment { mCreateKeyActivity = (CreateKeyActivity) getActivity(); } - private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mNameEdit)) { - // save state - mCreateKeyActivity.mName = mNameEdit.getText().toString(); + @Override + public void onNfcPerform() throws IOException { - CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + mScannedFingerprint = mCreateKeyActivity.nfcGetFingerprint(0); + mScannedMasterKeyId = getKeyIdFromFingerprint(mScannedFingerprint); + + getLoaderManager().initLoader(0, null, this); + + } + + // These are the rows that we will retrieve. + static final String[] UNIFIED_PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, + }; + + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_USER_ID = 2; + static final int INDEX_IS_REVOKED = 3; + static final int INDEX_IS_EXPIRED = 4; + static final int INDEX_HAS_ANY_SECRET = 5; + static final int INDEX_FINGERPRINT = 6; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( + KeyRings.buildUnifiedKeyRingUri(mScannedMasterKeyId) + ); + return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (data.moveToFirst()) { + + byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); + if (!Arrays.equals(fingerprint, mScannedFingerprint)) { + mState = YubiImportState.BAD_FINGERPRINT; + Notify.create(getActivity(), "Fingerprint mismatch!", Style.ERROR); + return; + } + + showKey(data); + + } else { + showUnknownKey(); } } + public void showUnknownKey() { + String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); + mUnknownFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); + + mAnimator.setDisplayedChild(1); + mState = YubiImportState.UNKNOWN; + } + + public void showKey(Cursor data) { + String userId = data.getString(INDEX_USER_ID); + boolean hasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + + String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); + mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); + + mUserId.setText(userId); + + mAnimator.setDisplayedChild(2); + mState = YubiImportState.IMPORTED; + } + + + private void nextClicked() { + + switch (mState) { + case UNKNOWN: + importKey(); + break; + case IMPORTED: + promoteKey(); + break; + } + + } + + public void promoteKey() { + + // Message is received after decrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + PromoteKeyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + result.createNotify(getActivity()).show(); + } + + } + }; + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + + intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING); + + Bundle data = new Bundle(); + data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mScannedMasterKeyId); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // start service with intent + getActivity().startService(intent); + + } + + public void importKey() { + + // Message is received after decrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + ImportKeyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + result.createNotify(getActivity()).show(); + } + + } + }; + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); + + String hexFp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); + ArrayList keyList = new ArrayList<>(); + keyList.add(new ParcelableKeyRing(hexFp, null, null)); + data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList); + + { + Preferences prefs = Preferences.getPreferences(getActivity()); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + } + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // start service with intent + getActivity().startService(intent); + + } + + + @Override + public void onLoaderReset(Loader loader) { + + } + + static long getKeyIdFromFingerprint(byte[] fingerprint) { + ByteBuffer buf = ByteBuffer.wrap(fingerprint); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index 0a7b7611b..ca1d79155 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -18,7 +18,6 @@ import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.NfcOperationActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; @@ -72,7 +71,7 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void onPause() { super.onPause(); - Log.d(Constants.TAG, "NfcOperationActivity.onPause"); + Log.d(Constants.TAG, "BaseNfcActivity.onPause"); disableNfcForegroundDispatch(); } @@ -83,7 +82,7 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void onResume() { super.onResume(); - Log.d(Constants.TAG, "NfcOperationActivity.onResume"); + Log.d(Constants.TAG, "BaseNfcActivity.onResume"); enableNfcForegroundDispatch(); } @@ -397,7 +396,7 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void enableNfcForegroundDispatch() { mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - Intent nfcI = new Intent(this, NfcOperationActivity.class) + Intent nfcI = new Intent(this, getClass()) .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); IntentFilter[] writeTagFilters = new IntentFilter[]{ diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml index d8c95c658..ec2505706 100644 --- a/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml @@ -9,30 +9,97 @@ android:fillViewport="true" android:layout_above="@+id/create_key_buttons"> - + android:id="@+id/create_yubikey_animator" + > - + android:orientation="vertical"> - + + + + + + + android:orientation="vertical"> - + + + + + + + + + + + + + + + + + @@ -79,5 +146,7 @@ android:gravity="right|center_vertical" android:clickable="true" style="?android:attr/borderlessButtonStyle" /> + + \ No newline at end of file From 04c7639a5a9b4f85122b7d5f299ba09131ce3036 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 21 Mar 2015 22:18:58 +0100 Subject: [PATCH 15/80] split up wait/action yubikey fragments --- Graphics/drawables/yubi_icon.svg | 32 +++++ Graphics/update-drawables.sh | 4 +- .../operations/PromoteKeyOperation.java | 2 +- .../pgp/CanonicalizedPublicKeyRing.java | 6 +- .../keychain/ui/CreateKeyActivity.java | 6 + .../keychain/ui/CreateKeyStartFragment.java | 2 +- .../keychain/ui/CreateKeyYubiFragment.java | 109 +++++++----------- .../ui/CreateKeyYubiWaitFragment.java | 88 ++++++++++++++ .../src/main/res/drawable-hdpi/yubi_icon.png | Bin 0 -> 2517 bytes .../main/res/drawable-hdpi/yubi_icon_24dp.png | Bin 0 -> 1203 bytes .../src/main/res/drawable-mdpi/yubi_icon.png | Bin 0 -> 1715 bytes .../main/res/drawable-mdpi/yubi_icon_24dp.png | Bin 0 -> 753 bytes .../src/main/res/drawable-xhdpi/yubi_icon.png | Bin 0 -> 4078 bytes .../res/drawable-xhdpi/yubi_icon_24dp.png | Bin 0 -> 1715 bytes .../main/res/drawable-xxhdpi/yubi_icon.png | Bin 0 -> 5808 bytes .../res/drawable-xxhdpi/yubi_icon_24dp.png | Bin 0 -> 2854 bytes .../res/drawable-xxxhdpi/yubi_icon_24dp.png | Bin 0 -> 4078 bytes .../res/layout/create_yubikey_fragment.xml | 24 +--- .../layout/create_yubikey_wait_fragment.xml | 83 +++++++++++++ glyph_authlite.png | Bin 0 -> 3869 bytes 20 files changed, 266 insertions(+), 90 deletions(-) create mode 100644 Graphics/drawables/yubi_icon.svg create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/yubi_icon.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png create mode 100644 OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml create mode 100644 glyph_authlite.png diff --git a/Graphics/drawables/yubi_icon.svg b/Graphics/drawables/yubi_icon.svg new file mode 100644 index 000000000..027257550 --- /dev/null +++ b/Graphics/drawables/yubi_icon.svg @@ -0,0 +1,32 @@ + + + + + + + + image/svg+xml + + + + + + + + diff --git a/Graphics/update-drawables.sh b/Graphics/update-drawables.sh index 8da894725..61dc51099 100755 --- a/Graphics/update-drawables.sh +++ b/Graphics/update-drawables.sh @@ -22,7 +22,7 @@ SRC_DIR=./drawables/ #inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg -for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" +for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" do echo $NAME inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" @@ -60,4 +60,4 @@ for NAME in "first_time_1" do echo $NAME inkscape -w 512 -h 512 -e "$DRAWABLE_DIR/$NAME.png" "$SRC_DIR/$NAME.svg" -done \ No newline at end of file +done 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 fd86d4b92..46db30ad0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -74,7 +74,7 @@ public class PromoteKeyOperation extends BaseOperation { mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); // create divert-to-card secret key from public key - promotedRing = pubRing.createDummySecretRing(); + promotedRing = pubRing.createDummySecretRing(true); } catch (PgpKeyNotFoundException e) { log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); 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 c2506685d..fa5b0785e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -97,10 +97,12 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { } /** Create a dummy secret ring from this key */ - public UncachedKeyRing createDummySecretRing () { + public UncachedKeyRing createDummySecretRing (boolean divertToCard) { PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), - S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY); + divertToCard + ? S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD + : S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY); return new UncachedKeyRing(secRing); } 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 9919e2aab..5c2fcde82 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -84,7 +84,13 @@ public class CreateKeyActivity extends BaseNfcActivity { protected void onNfcPerform() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).onNfcPerform(); + return; } + + byte[] scannedFingerprint = nfcGetFingerprint(0); + Fragment frag = CreateKeyYubiFragment.createInstance(scannedFingerprint); + loadFragment(frag, FragAction.TO_RIGHT); + } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index d23d598fa..3f56949f5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -98,7 +98,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiFragment frag = new CreateKeyYubiFragment(); + CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java index 483a5f4e5..63549c3d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java @@ -23,8 +23,6 @@ import java.util.ArrayList; import java.util.Arrays; import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -38,11 +36,9 @@ import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; import android.widget.TextView; import android.widget.ViewAnimator; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; @@ -51,57 +47,59 @@ import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; 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.ui.widget.NameEditText; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragment, - LoaderManager.LoaderCallbacks { +public class CreateKeyYubiFragment extends Fragment + implements LoaderManager.LoaderCallbacks { + + private static final String ARG_FINGERPRINT = "fingerprint"; CreateKeyActivity mCreateKeyActivity; - NameEditText mNameEdit; - View mBackButton; - View mNextButton; - private TextView mUnknownFingerprint; - public static final String ARGS_MASTER_KEY_ID = "master_key_id"; private byte[] mScannedFingerprint; private long mScannedMasterKeyId; + private ViewAnimator mAnimator; + private TextView mUnknownFingerprint; private TextView mFingerprint; private TextView mUserId; - private YubiImportState mState = YubiImportState.SCAN; + private YubiImportState mState; + + public static Fragment createInstance(byte[] scannedFingerprint) { + Bundle args = new Bundle(); + args.putByteArray(ARG_FINGERPRINT, scannedFingerprint); + + CreateKeyYubiFragment frag = new CreateKeyYubiFragment(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mScannedFingerprint = getArguments().getByteArray(ARG_FINGERPRINT); + mScannedMasterKeyId = getKeyIdFromFingerprint(mScannedFingerprint); + + getLoaderManager().initLoader(0, null, this); + } enum YubiImportState { - SCAN, // waiting for scan UNKNOWN, // scanned unknown key (ready to import) BAD_FINGERPRINT, // scanned key, bad fingerprint IMPORTED, // imported key (ready to promote) } - private static boolean isEditTextNotEmpty(Context context, EditText editText) { - boolean output = true; - if (editText.getText().length() == 0) { - editText.setError(context.getString(R.string.create_key_empty)); - editText.requestFocus(); - output = false; - } else { - editText.setError(null); - } - - return output; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false); @@ -113,8 +111,8 @@ public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragme mFingerprint = (TextView) view.findViewById(R.id.create_yubikey_fingerprint); mUserId = (TextView) view.findViewById(R.id.create_yubikey_user_id); - mBackButton = view.findViewById(R.id.create_key_back_button); - mNextButton = view.findViewById(R.id.create_key_next_button); + View mBackButton = view.findViewById(R.id.create_key_back_button); + View mNextButton = view.findViewById(R.id.create_key_next_button); mBackButton.setOnClickListener(new View.OnClickListener() { @Override @@ -138,16 +136,6 @@ public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragme mCreateKeyActivity = (CreateKeyActivity) getActivity(); } - @Override - public void onNfcPerform() throws IOException { - - mScannedFingerprint = mCreateKeyActivity.nfcGetFingerprint(0); - mScannedMasterKeyId = getKeyIdFromFingerprint(mScannedFingerprint); - - getLoaderManager().initLoader(0, null, this); - - } - // These are the rows that we will retrieve. static final String[] UNIFIED_PROJECTION = new String[]{ KeychainContract.KeyRings._ID, @@ -185,35 +173,26 @@ public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragme return; } - showKey(data); + String userId = data.getString(INDEX_USER_ID); + boolean hasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + + String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); + mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); + + mUserId.setText(userId); + + mAnimator.setDisplayedChild(2); + mState = YubiImportState.IMPORTED; } else { - showUnknownKey(); + String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); + mUnknownFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); + + mAnimator.setDisplayedChild(1); + mState = YubiImportState.UNKNOWN; } } - public void showUnknownKey() { - String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); - mUnknownFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); - - mAnimator.setDisplayedChild(1); - mState = YubiImportState.UNKNOWN; - } - - public void showKey(Cursor data) { - String userId = data.getString(INDEX_USER_ID); - boolean hasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - - String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); - mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); - - mUserId.setText(userId); - - mAnimator.setDisplayedChild(2); - mState = YubiImportState.IMPORTED; - } - - private void nextClicked() { switch (mState) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java new file mode 100644 index 000000000..c338a0f1f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java @@ -0,0 +1,88 @@ +/* + * 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 java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +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.ui.widget.NameEditText; +import org.sufficientlysecure.keychain.util.Preferences; + + +public class CreateKeyYubiWaitFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + View mBackButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..428ad6fad6a98fd45754dd37e7472d0f61fca5bf GIT binary patch literal 2517 zcmV;`2`cu9P)EV!KR)T;s3dvGT4Dc~a zQlk_c97!lMwAI5NhK~h3mbGk3lI0^cVo+NpEgx%{6egLx6dC2AiKY`G0}PBXz|64w zRlqo4qqXk?dgn7|scJsV&oXBJ3QPl*0+ql* zd;bb>a;X_B2mrw6fi~bF;09yZ1G|7K;2NL>*aKWuYUa8X0ALWX4frQe4IBso;H~{* zV0cI*ehRo!GudSTfIh%WKm%|Qus6bdxc&(k2rRO89Z*(kM!O2Y!@xe^0^rRI%m?*l zU@&l-wVwg{m73YS004eqOk8bSx-|g+9%uwc+qhO>9WbcW4Ce%Zeerf+Ug+Hi%=G;u z0SGO?Bwzy247>xJR%)gb0RS9pO7B_VFniYvI1ji4_=qvrk_Ciz;5t)|{{`LyE+{qQ zNdR~f*a3XZ+C!1!6K1T|z(^t%w*wWXAKHMKrDi@Y0KnIb=*z8rF0kL;Z8n7%F!nI; z^@Mt15l{~NjYGst#xegXU^nnkWH)RDPPcY%`|{1k$k`;b4uM4|2Nqa+Atf}EQRaIA z%S~2~wReM&lT#f7);MEgY7V==V-^9JW9_As$V^5Vnr#G40RCdkUt{k^rxBMeNXea& z0|0?2jj;LK43vsu18^`LcFt8In?DaY37Np_?dyZALWkm&O@TM8aY@6cbFV71$Y3MjuITB>0I6her3$|MWV6| z$^Lj09%n0iQL<#Mjr}z68Q{1`zdr&qQO@CWz*O&jU4r@ZF0Y^j#wSr?BgvPF<^nR+ zCZyyBBiFeGWtGMw-7%7shy`^C@L?2Mw*%7)DFQ#VMHc{L4UF#C&K}qRTnVfQz1xhm zf3VJ@D6Xi90J|FH74Ek74TaPJ%WRRKBOhRY3^Tdb6~LvWl;djPl+eO`fNPNzc-MBq zHzISq1{@E}wf4=0vH~AM86R)k^AhBo%afN#Pwc z4CNpX0KcRga0O)iy=DpvDA(}txxNW$>r`UT2EF&2 zPz>-U+Kw&2g-BUtD;tq0HG1#=9r^7F;02)G0;yM-i%Nn;L9rXpIAfI;5^-n-s zr;_$?)O)`fp}Yn;B*&ome@B)%-$vfz;W59hw7=!nCt3y}iyc8Nh`h=topWUg z#&swePlvtt+mSpkMY25%f$~mrLvHHY7-C*W+juANVh(fHBSkx$%tvqO>Ycz-NM8-?b^we}2i#!`-i7qSzha2F z7%4mtd@sSgfvYBe0_4@rBF1w=I_M@F zvlZ!=<_rvGs|%45eyX4V81b`=xyON{fkz_5+yNYcqCJny-#1ap?M$R6>g~4;C=a_B zz(AOUlBlat5@T+Juq7zoA89~5i!91)>tBUZ>s?`fodG>R^scTb02m14kRQJuIWs}7 zKOm+R>4_OAzPJ?len>pJQd^LJGA%|VCKm+&1EJEsQ-P< zQ2;Oy2HAGpgG}KFbj(&L<{v;l$0lpnAQ21q*$t9C48!SzP?kN=<03+f&bm;O1It{8J`E9dsU2fu4XYH(UhOJ*k$6GDifT2YL zz=#-XU#$r^2W4}ToEn!BKnd_d(TD}BVNkFd=m=2tF4{aF&T1~!_L{Jq61)r z^djZA-*3!CL+Wt78Y#Yaje#TcT=ytC9{b%}5&%L3-H3elB`6PgYeZRj;CI&dK5PFz z&$X%z!0_CaB?Z6;n@sBNz+hx0f@`>IP)=b=i0IwGm>lNsfzBqQyXtxGi)aP15IF{! zrhVvy`e<|)(E;D4^I738;EaTG2g%Ycz;Erj9lM8Aa^^#X4kYz&H(OE8AX5y{1e}^+ z&Sqo2f>h@DHwXKan(-K-$D-uQcH8a^q))*gbZFAd6~rT7G*MWEj@v(Y$O4donF182iJhm^jC2DKGsu3FHYeS9XOsM2 z7w6n?bg}Y;n4g!SOi)|g*u4LF`cNv~`xnq<>qjC#J&zKivCISSj~x&$&bd)2RbByH f?7iQassH}~RCrB!*PSp?00000NkvXXu0mjf*l~(( literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb41223dd39c5ecd18b78b5c93a7e9bdb1af8bb GIT binary patch literal 1203 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 JPDHLkV1m6yDsTV* literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..753f226079aa4c7cdcc71e6e166c0780ddfbf87f GIT binary patch literal 753 zcmV#~=?88v}hS??2Tlf`k(3yHRHDE2}@P)X8 zrI>>?xQG8s+%$ZJ-*GS^hBsUTjKKvw!C8EaPtjSSsmGsUk=$Wzjdsx{v|((?Xe$$~)Eg<`G1l<*Y^Z2E#x0Tpo*ngK;WqoNxOiC%e#6N{DScOU091$P&;-~hKTXCRJ_8NcTn2IK0CeG9{O}K%*18PyZ z={kIXJs6HZaV#R{N5o=m#D}FGRqMcK5z$fo*h}mR$Xbk)J7qaW;B&NNRLP=lr|=Rh z`lYBwSBqS{ZL)0CJFM!D<=XvRs9V)b>;?c@BiH0!KZkeAUDa9Qm&g*cpdo;=X&!#S zq literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa799e74eaa845ee1736c7232b6786f8666fae8 GIT binary patch literal 4078 zcmV zJ9m3?A2R3EIdAIDOjp%!rn{=Tx}Rr^p;(9Kd82@_z<6WKsl0rvq$={^MDgMl*O1fatCeJW7DknLATDx~qxcZ~Z-pe69M zp3eZ9D9v!L`vmBev=OR306<@0iyCzjun(}&GqLsVM;)YxCC zG5-NH2IdEh@6!h0G`(*Guu=ItfXkCMMzsR~s0GYbBQ6J812aR6@6sJQKJ?K0eEvN^ zjiilIodB4qhTEjZoEXFSKD`1o0`>-GIsE5=I!PO*WRu00aiz-vK{z`txk*JK$t}?u`zAF+TtgDHr3rgYGQEwOo{=@qMZUuGHtB z32asVe}Fzo8>wUfAa&6F0%!*83G@T51^$Ah8*&BWUSx1S(s|BC;H0DtRVo1ZgY0IY zSI9MufeFaKoC^q3fjZi)eaSBFreQ#Ug+K!&O_2)- z8-PLDrt79I0`^VXfJFrW36eIe!Z$nox`-F#56IJibAeNU-vj>&81Y7+eTJ_kinaq6 zYTIXF-PAQyb2l~0__crqs_47G9?oyqI>s3qa{sN)HJcE3^`;yF@aZ5Z`f6iaz`OkjsE#1F#wpU*ACS4 zWd#DjAal$I0X2a~9sV4kVbaFU8UUn@$5-NXpf}dzFu+Sl|ND)ef0DuYZLz^wQ)EiA zP{#LZJ#eJ9cay{a5ZEVa1IGpc=~LIJ@XsTz=8}N2l|UC@vco$j0)X*)%_Kd)i^2n7 z^d%XoZH#hWnx&L@O4~KoZ&m0`h|hj^h_X)sgPrr`qzQt=2PbXlNC1GYSoiQ2B>dkVqGTn~mE7X+Rv>AKIzV%z9&{Vh{rG+VbwxJ5 zPkud8Uu^zrJr zY=e_Fc$%JqzK-Bm0LR2sawam!c0~H!M-g}RPGA@|E!_t@gO&?bDtJqfa+l@UI3$W@ zAeq$m*crR+grw4wA`f@~xIl$#jf@4S6YnsAAViVmq@@t1-a!Ve^BNf2^oJO z&;qHhex1x+O(l|vZLV?;Cw;EzDC#6Y8c{u1)skT_&L%=x|2IEkYMV<2cRAD=^Lak?}_xAk5!TJ zSF7475oM02Qbo_Oo88%GIGTdHf))MSS(M z9o{T*7#NXEtv@D=SKbpD+V`X86R}gTOMrHX0ic2(i>>|q5*hf)u`^+XqMNlnzuImE zd2|Cv-H-v6=y|soGWb#R;n-=;Wk83-08rtFIG^S+WFn>!5_lED_&bmkbw`JHbY9Ac zoB6RmtGDu2#gN61ntNa~!fBI?RR(|xe;PKmyF|Z)dZfCoxzaps!!I@2fHU!*2MFit z^X}I3S7XZa9y0aX8TcIg^>)(qmfA<|9nD3`+a~nkl<@T40)FU%k=LN zS<2-{%`LGD4OapOBnE&g(gUg2H;Aj+4+++Co#i?W$)gA>B|pl3hIB7;_~sF79)jV*`>P^N;!w*DiAY zZiaX_QAbyxuIG96kO`t}k4C`XwXOchSkMf3&GWprIXo}?$f1vIht02V#bz!qCSIQ2 zr!l0?Mcf+EW^-(ywGF5jacwi_y4u*O)?8>Ul7H&twEqrJyUM$uGzRe_*Bxkq7Lo{y zTlqE_`I7vF9M5EEvl(zZ8OjimqG1Cp(DP^V0Kgy%$$KJm4}O;5fkXjN#TsG*D1%IL z_y&kmgu6nopQ`7}DU2KXratd6q^x~)3|ZoWFw)_TN)!N9wjQZ|_HD{{y{0H?hs?hD z=V#<8&#BI{#sGr~V0;&ZUdV)y(UO$)$k$z#c%&9{O+#`sZLm>gI=VvdAFtzD9>!KW`(MKnq>Qp$uj_(MYZr@F z0FAW0$${UNl3~0`3;-3lH_~?+B%R$$`C%2)6-a4GJ-x0WvShY7sjD?xAaTdmkmnSN zD$z7Jsf$=#x~OCUP{9wx7U_Nq9IO0Mgy)aeAzjfWNOau~7yw+2tP9I^Ly&CgR+Y6S z;Z@!nfI~_a09Bv^wyW5N=2MUY+V85!nAy@y#6_+{=K1gyK&z4kK#korpom|e(~Hch zi>juTNH^z^y7SAzh6~>U=gnzCyM%z2f;eCj+OIQZ^qpAj%lcwl5aigbEqKor6Z$no# zB6UQ?m|*c~inenAHubTNELYki3Beab+E@y-E@=Q%i5AGDhcEaI%I{Bp`;vyyVkBK& zM(Qq&BXg9$CgIbei-DFU4S*_99{8Lms)4%dn^Y*{Z%4N>04{R)Q}dL+jBqDnA^P7# z5;t^=H_1(1`>{e(?2J^t);dZs05wxg5LvbOq2;`D01HEHw`K#blkaDeWhUAp6N7 z?Z1?`VY5_ZAhtYkBX)XlFxh8Qm!dVESg&OrMcQc#ac6@vR3vWH92DtBYQpYj?WVdD zU4s;)q58a&9Nzs!l>6N@GTfH9L332538||#ryzrK3sS2p!f1{@r<~OPZPud*vNlJu zy+Ijv0PPbuW*&;wB(?ZzK2ii$23#4@C2jBxjjeDq!15xe1F*$dTaZ$f2Kw76NS)EE z*kXXNJpK-JqwqZC$+UoR*7w&YZM*`4(1q0hWwQpjkie>YWZ&zk#NKy6|2*Uv?mTOo zc86|+y%p%X%K@Ou*Vnh-EmQdzi6JgV)_ukCkT&^UM3(KBV=F&4qYKZG@=E{Nx2N2b zw9$$L#Qx+zqqdcZm75{$x)J+0hd)z!pA$%JCvL1F12F{IJ;m>GHY2gZd&Hak-+}IV z9iy!-&X@NQS}8~BKe#(#r~8sNQWXJFk3faB@;f1WV@Fj}SEA|g3~jiztXC_q3AWT` zTH5=PHclxJQn{VUb9o%yiZjjq9Z9X&?v504`IXumfcCoq$PC0O*h1oTG#hA==Dy%R zG3`p~tk`*>Z6>XBsS&bI#PSFb76S)4*LPs&n8K~*elD`Tq!lhjLh?6rBS6@S{6CvU zL>G}QCqs7C(*}1Rfy48>+Q{03;SrZ@M3R8%jvdI}*>6O(6MM6cAKGDz*5R%u7w-N7VjRYpc}S(+lo*#+0K?-L zA9e!}`!L3=H^vMlx=X4+-u#NV&)wYx<#2eO*8}nWdpd02_@8Cf}_eV!Z8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11~5rPK~!jg&Dnd9msJ@D@Xx!b1uESXv=|~UV;9g+5gK*Kk_kg| zN=-;iO>IU^Vg@hMScq0wEKx4IEWQ2l zoLAoc@w>b~`7P_2IkUUxJ(uVEoagd=&dT##)sEBfTa0dJ-9Yv(6VR@Ei zCsbWKpsk|jzk>BR6&K-R^x!(|I+!<5Mfel24kzImrX zzUYr(9Da#4IQUHk0K>5o^YM6%@I_yP@wf`F;MkoFpso{VS(f34cp4953;tJU>L~mU z58)F0BFnNfD>+}MS_AFR#b2;zY3u`7r2b9x2{W69*KqlunGh!7)gF)>d67EUkInEQwtPzM`;b38Y_hb0Z1aJ$MAj6^f zH%64cUxBUocoP7*aQZ`pe>`pg!)#gjPl=HEX-vgDEUp2#OERx_;>8vqUc{03NixB4 z0|o#ileW*tb9fME;LkMxc1!mC;TFOdeFHv&tMOkP)oB1l7`*~dU@Z2>G1vo#WLb7D zWLegQ%kX_1ty&v~pC-^>#l2aUO|7`LavLpp&F9f;oA#c?g7o|S!f_YjgF>5CZlK(+ z#<VCwJ-wGTe+Y!v3e%fO`llFhp>keQ}C#(0c6&EK6&4OJJvB@AUoFq=dULJJ0hw zDyz9kdl+|%Oxmtii@p`t;Bz8xbc?)klZaT~5Scs|PJJ}iO5u8koEZBER(TyREFDy} z_X&Ev1{;$i4#0kbBW+Im&MK|x#$jpA+)hNHv4V_Ugmc>ju|SyG^EjxEhEB#Nd<;J? zt^2)Tvnz3JC-T4);mk9IL-$n>{vX0`dN85MHD8RUlGe-XmkB@lqwtTeHw+*hbXL0Z zX`=et(gMId!CDuzu+uIq7x|;Mu2`8sED_G#7JsUD*y+iZPZ3$UrwM?Wf_;9rw0?-F z-riob?>xby?yIxUs$|9whyrP7RRG)719_g`i5d7V-X$_=Pwl+a)(QgnTxo2Qh-!1{ z%-u`xZT7A;@>m4b0z-(sJAgsIKY+$0PBbXod&M%zA6 z=kReN+AI|8vP>hEirZO_1Dot|hp_WGErc(6k1)fv*ky+RBmoY<3;3R}@pYxc&cY4p zrnW2UMR}TwMAnLemwUIMH&b>PKoVq>sInH}2tmJdQL!B;oPLW4g*S@Vj6bEZvvF@5 z!uJY4SdH5{0w96dOS}gyNgxW1uEj?Mjre;S+bELLG?8_y3ZIMMF(LiFPLtfw7Jvj| zxNzJ@MP_}nq_w+67C#av;%mY`+7bQ*ybpgZjh){y013oU{7lpg6GRMeN62Q*J4rkY zY$}a!#mtTYNFcg!ZSsL*goE!WfG-KB?@hOOscpov57RpapakRs5#vu4bm5H(f0yv9 zK3rJZe}>>^xd;d+bre84c#OL1&|IoOeDOU z@d?%I<_&_#%})S^HQ0YjNimzH{LnD~>5$PPDs|%{B96BLFbjtZr#-aA{xeHHu>tSx zD1dax+eDsNo&;OgMEGX}n|)Hn(o#5fvHs4E14xJLD)`UpbQ9eT0Gue^q*iIcd206G z^tu8D0zm1Yxycuf!?*P>Q*RU$WHaVh+;1d)RNCXx0R@l_Jza#^!q)G@k3|i!GzqdA zV_NL

$J)@ar*RzyYMgCku+OLcCI(CaSoyIsFZz8mu28Ub&azoHT!So_9D3wE!_p zR9i2n2=Xue=|bVu`_@@|LHa&kytOXJkev#k0jQz)VggivcpitB=Db5N(g#yO=*7g> zzYivWl8|G?|Kth~n{i(Hc0(F_Isv(=-`c?zz64@-+Y`dY`qPp}Ys2lJ3tk7v7ep~s zn=@X)e%s9-Y=IkqDBes9;d?Nx?%cr^wh4$m#GC30e6rEJ{{y)#APO{!_ZI*F002ov JPDHLkV1m6yDsTV* literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f20f562ec5c98fbe0903e746536b0b52d63ab357 GIT binary patch literal 5808 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 literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa799e74eaa845ee1736c7232b6786f8666fae8 GIT binary patch literal 4078 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 - - - - - - - + android:layout_gravity="center" + android:indeterminate="true" + /> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/glyph_authlite.png b/glyph_authlite.png new file mode 100644 index 0000000000000000000000000000000000000000..1a40c4a2d5e57feb805a6e4ab2421447bce18d07 GIT binary patch literal 3869 zcmWkx2RPJ!947vpJ;KS6E<#o^A|p9_bSQg|Gc&SRo$QROh*Cy&xFk8_vJy#`(aBy% z_FhL;{J)-i?tag6&-eEopZEQ|@B2+OLTWS7^UzaJP%!E0Xxs%)E$~%^(12fCs%8v$ z&^YO9Yfzk?ee#;#y`Z3A?$gy!GYQR}%L+3x(Z(el^)R+ci;HFv?s53h5=-G+N&3Is zJ!FlI#q{vg?9LkFB7d3m_1W3{5(Oo%C;f?!PuD8!EMUi6Mj&)dZb!!yQX3bXJ4ZT- zd2^L+ERXSU8*RGq*77j-WPNS@_X3%-vBb&Q*<-9$f*-vPuk)D*pO~NINgO)ZnQluk z7R`XbSrHDNo`jM+St4R$*lVYgnRXWpEG)SF@x12d=4okZ3IX$sd`iI@kXBm$hqsYP zk$tF1H**Tr%#_QD=TO3EcDJyO+6OW?Rq1ZywLj=_BAA?9O;0} zQH;A=*51|@Q(7uwBAUTW_<7^eVc@6g>Vy56XKaXH71r`Tq-O8ZTihDqF?jzudRJH1 zz_rng`g#R%2?=a$ftq zGoGiJ%q52)zKM(b4;)7D$`xX*x3!?I)5-Ax2NVkXY+qSn)jB&jN33$zek1FFu(IN@ zkNhXs6!G_iR9(MozT3KVNertcrKzb&UKwI3zN>3sU=X*V zr>A#TO7Pww19D@s$rw98O-t*HLd8~8NT~iJzfe>9t60v~MPB;NhS*%IBQ4)*0Oz8l zq})F|{Ny*Ib@$#qI%a0Ae4~E)cb3(z?ccu#9BjK-;oQ$kJJW&jb-8UJk-0WjClKoZ zHcz1(d6I2->!9ean~U@XG#VY7kRb5lhi4pEQbR`v!oq(G1ptA-xn*1#PmgyDV}h*s zP3eS$glGf=1ZbFeG<0?8D9#HbH6T$~tXRN8&$r&*XSpg7%ZiPtAM9oyY|8#N%Ak)! zbCtus7HB3v7o;;cAFA`LG^H~zGvlhQtyNG`+WAY0q?&8fhD6oX)iI}=X+PyvD>HHO z@-jf9Z{;Y348oAFByH=1R=5&a_&dhOvpX=bfaU(O_@yw3a*C4rdTtgL7EnGNJ-y3t zIOXFj`Y$iVUb=Y`PtbZPX`93vJv?mDe7vpS)Cz&S_m{F68ygoD6-}+KrWgnbNJ(vC zdX=QN(!fwrN!!nY(J?WaHa6MZ(x@k$y}eL89*=;)ySlqIO}EKp4g@6ML^Q6d$}gj( z40&*H;IzLv{iick;13}~%xQC~)oJ9Di&KB8aY0^Q9@2XNi;G3?PtyXXbY))8R*5jH zBfPh&Npku55+3U?R_krrH-YmwFPf30{p{jWEoG&XdOYJaXgYEGW?tR2#c})&Y)lXG zczoRE%?8<7SFg%?^c8(AyvWCgVLbCOvXik)gtJWt*rwWL+cxb>hwDIxF&fq}E& zUzD3$z~V3X`-+MSih&~EzI{u9$*yn{AH{jZAo4HC%HkYbL8n?l z^}R+u73*mO0yPA$Ie~S_i$tmny*Rz%uM-nFRJ6=SR#pUnS*l=}-4_V_xnSbs#}pTp zL%%$xq+&%#d5_8_B_;9Dq%<@%_6LCHh zR+T-93JM>?cReNTo4fuU{kB%F?bz~$A@L?^sJ6l0|9tv$S=!- znQjeS>iZ8)Eo_*J`u>z}H+;zcMr33p`glwG;iE@OgB36d>)Lh#fj$nQR_)r&ebZkM zRBgN^(jIEySz+4{TvSrR&cziEw1!Abg~|JHb9HUBoIb6ok(-~Nw`qR(`dagTf!%cH zoIgTceQS4j;L%{dI&H1bgr2{@bXi&1>SzrE6bg-HC9jP^Ypl9!y~jlN9X&jBO-wla z{QQPTM)L2LDfrJmnf^jy(}L!+wzd`*7gq>b=RMk6YwPSRwfh28VC9?!kO?CrqvFL2 zgqWW|k(P2z9n(e(9s0>8F+e%GBY!C!(ajAMdO}a z73+7|`ldG=zfuFumXenKN+2ZF*VkL|jW&kKARzHfu#RNtwO`D;ue!&^U>L2Kx;l9T zMC;i_+l{a}dh2MQiD&5BND7OKr?j_gypg-Vr)SjF)m0y|ana7st`j9KX;Yt9caH%o z3A_L>?q=wwL&jPI9-o?<+XIFNawY>Zw>_pb!VZ9yFX^%az6i+3R1+G^vhKXSl>=st zWi`lAtU)3)4i1K2-M@G3C8$^5G2D@rf_)d6tPe{WAv?*<2it}xb0^gVdY&>5zrSU zCF{y8>7Zqk^>Ms9pp|i%iH4@;1)3By*bT4SX@ZJD%NpU~ibsDpD))%!k@bm&gu+4r z#1m0rt+j104-d<^pqe%*2?QrIYpZPg&FFw`A@Nt)J>jN=2cQXc3=B}9 zy8s+}8zf%^DGKmDV40(n6GB6SI`ZExbfa+xS;M?OQcVjwA(r*5gOSLl(5+~|%|4vR zJy{+me|#fyCkv4L9Wn26u3MVg$=D?&;kP8Rm9Vg|cq%Hu=Py7% z*$`5m{UT>BaYpAm1b=+?>?{gsF|fH2x3^0JXrRfG3EA1%??VQvKXcw6{^$sSTQme$ zIJQ!xXJl0Q&*=&3e(mnYq@*x1^C`xbm3jR4{cvwh9~ji{k~`{8wugmORa-`b3kwPY z<+h$bXPxiKIU*S0=4WPNii@v2IwW6L=B{{pQ6&lJ*5KevU>fLyIVdkLuc*Qe-$`X) z&4J&P?%%&}R%v%9Fc9cUd{>vjQ$D4VhHCn_=)Am3d+QTNqgj@KYJIU7l$C_1uhWmr9Z2mLZ6p?XGT2)mQOgY>b7J$+Jm!}p}kN&5apPyfubD0yt z%EqSV>RRHn{S<@o2Jo?5RL=7yHACQ;a`(BO@vFKX?{9HtuMS|LiHV8hO%bm^y6OXI z2b5=%Oh)}ky8;aEd1|Wh8p_T*OU8}a#l_{bu1N!Q(7WNT{+=$-%WvT5+fWOu#a%(BD)u_ZDxDLL7)&d0WRW3bYm^w9w~ z3PMvyM+eTG7>zZrWCwOsQQ_SbXu-eoE1wEr`|$5Xi0NS86;%n%(R;J^)b7*~{zwPfAWMu4P30EAR1oKjnzM_l~W%4;l-* z*CRqh^USb-`em;;7f6P;Lqionxh$-#F6*+>GV_+s_JXDq`)0M#eB_himc~S{g$2RO z2{g&hd^bLDiT(QZ>!51}V4&)QU?z{r#;}U>UQSMzE39hDH&0(mSo?#K^ey*Ur6`6z z*cJj?7s=2IUL965Gczl$Iu!-nQa3hE^%?HTyk2N#4>%)fUL+?bCWbyaaGUVuVPs7I zt~AQNnSS*i$QqSKW@Xs0@bJ4PCPi4u#?zCd#?URqHTzuE$e!UUXR%hI>nRThWBNOe z`R=TDFp{OE7e07wI zi)lHtPFg#V#H*5$C5CTrLEwuc)eB-7?)v)rXLtsFZ3B4(r06&5f zfsc#^;u$6ylRJmHzUj87z*LhUT2FgK7DlxVX6eqa%S= zKS7|PQ&Uq@ez?ynLW(hk9)UA0=9n`xGHN{8F0w?S1c57RL88tA3O;h>i+j-8sN7`4 zhROtM`I_7%IDupy3XXtT1DA*bBM~V%xjBFk2PfzBpFh#BUt7jW_gXV(VS%C?A0Klo z1&dd^^}6mZ4^Y$4NISJZ5#5mo_<)Wz8OyB=g>rCE=S5hZ*!~GyY3(msE>8!y7E?2L z%{#+5*4f$F2ivo{v;H@MTV>3bp8j^gDF&}{V_@PLc6f5$fkIkFM$DTrjuqtXfJ3s!4X2THQ$}u)-cxsb-w)meF9j3Z z5?IW~1|#WH%!ou{#8V?*J10MX zB{q!CIJdK$iT*Kl7sn|}AhHLNCL Date: Sun, 22 Mar 2015 02:36:10 +0100 Subject: [PATCH 16/80] move yubikey import into viewkeyfragment --- .../keychain/ui/CreateKeyActivity.java | 34 +++- .../keychain/ui/CreateKeyYubiFragment.java | 149 +----------------- .../keychain/ui/ViewKeyActivity.java | 83 +++++++++- .../keychain/ui/ViewKeyYubikeyFragment.java | 129 +++++++++++++++ .../keychain/ui/base/BaseNfcActivity.java | 11 +- .../keychain/ui/util/KeyFormattingUtils.java | 11 +- .../res/layout/create_yubikey_fragment.xml | 89 +++-------- .../src/main/res/layout/view_key_yubikey.xml | 98 ++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 2 + 9 files changed, 377 insertions(+), 229 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java create mode 100644 OpenKeychain/src/main/res/layout/view_key_yubikey.xml 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 5c2fcde82..b73d6f545 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,12 +17,21 @@ package org.sufficientlysecure.keychain.ui; +import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +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.util.Passphrase; import java.io.IOException; @@ -87,9 +96,28 @@ public class CreateKeyActivity extends BaseNfcActivity { return; } - byte[] scannedFingerprint = nfcGetFingerprint(0); - Fragment frag = CreateKeyYubiFragment.createInstance(scannedFingerprint); - loadFragment(frag, FragAction.TO_RIGHT); + byte[] scannedFingerprints = nfcGetFingerprints(); + + try { + long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); + ring.getMasterKeyId(); + + String userId = nfcGetUserId(); + byte[] nfcAid = nfcGetAid(); + + Intent intent = new Intent(this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); + startActivity(intent); + finish(); + + } catch (PgpKeyNotFoundException e) { + Fragment frag = CreateKeyYubiFragment.createInstance(scannedFingerprints); + loadFragment(frag, FragAction.TO_RIGHT); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java index 63549c3d6..a1668d22e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiFragment.java @@ -17,48 +17,33 @@ package org.sufficientlysecure.keychain.ui; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import android.app.Activity; import android.content.Intent; import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; 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.ui.widget.NameEditText; import org.sufficientlysecure.keychain.util.Preferences; - -public class CreateKeyYubiFragment extends Fragment - implements LoaderManager.LoaderCallbacks { +public class CreateKeyYubiFragment extends Fragment { private static final String ARG_FINGERPRINT = "fingerprint"; @@ -67,12 +52,7 @@ public class CreateKeyYubiFragment extends Fragment private byte[] mScannedFingerprint; private long mScannedMasterKeyId; - private ViewAnimator mAnimator; private TextView mUnknownFingerprint; - private TextView mFingerprint; - private TextView mUserId; - - private YubiImportState mState; public static Fragment createInstance(byte[] scannedFingerprint) { Bundle args = new Bundle(); @@ -91,26 +71,14 @@ public class CreateKeyYubiFragment extends Fragment mScannedFingerprint = getArguments().getByteArray(ARG_FINGERPRINT); mScannedMasterKeyId = getKeyIdFromFingerprint(mScannedFingerprint); - getLoaderManager().initLoader(0, null, this); - } - - enum YubiImportState { - UNKNOWN, // scanned unknown key (ready to import) - BAD_FINGERPRINT, // scanned key, bad fingerprint - IMPORTED, // imported key (ready to promote) } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false); - mAnimator = (ViewAnimator) view.findViewById(R.id.create_yubikey_animator); - mUnknownFingerprint = (TextView) view.findViewById(R.id.create_yubikey_unknown_fp); - mFingerprint = (TextView) view.findViewById(R.id.create_yubikey_fingerprint); - mUserId = (TextView) view.findViewById(R.id.create_yubikey_user_id); - View mBackButton = view.findViewById(R.id.create_key_back_button); View mNextButton = view.findViewById(R.id.create_key_next_button); @@ -136,115 +104,8 @@ public class CreateKeyYubiFragment extends Fragment mCreateKeyActivity = (CreateKeyActivity) getActivity(); } - // These are the rows that we will retrieve. - static final String[] UNIFIED_PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_HAS_ANY_SECRET = 5; - static final int INDEX_FINGERPRINT = 6; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - KeyRings.buildUnifiedKeyRingUri(mScannedMasterKeyId) - ); - return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (data.moveToFirst()) { - - byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); - if (!Arrays.equals(fingerprint, mScannedFingerprint)) { - mState = YubiImportState.BAD_FINGERPRINT; - Notify.create(getActivity(), "Fingerprint mismatch!", Style.ERROR); - return; - } - - String userId = data.getString(INDEX_USER_ID); - boolean hasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - - String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); - mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); - - mUserId.setText(userId); - - mAnimator.setDisplayedChild(2); - mState = YubiImportState.IMPORTED; - - } else { - String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint); - mUnknownFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp)); - - mAnimator.setDisplayedChild(1); - mState = YubiImportState.UNKNOWN; - } - } - private void nextClicked() { - - switch (mState) { - case UNKNOWN: - importKey(); - break; - case IMPORTED: - promoteKey(); - break; - } - - } - - public void promoteKey() { - - // Message is received after decrypting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - PromoteKeyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - result.createNotify(getActivity()).show(); - } - - } - }; - - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainIntentService.class); - - // fill values for this action - - intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING); - - Bundle data = new Bundle(); - data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mScannedMasterKeyId); - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // start service with intent - getActivity().startService(intent); - + importKey(); } public void importKey() { @@ -299,12 +160,6 @@ public class CreateKeyYubiFragment extends Fragment } - - @Override - public void onLoaderReset(Loader loader) { - - } - static long getKeyIdFromFingerprint(byte[] fingerprint) { ByteBuffer buf = ByteBuffer.wrap(fingerprint); // skip first 12 bytes of the fingerprint diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index fc30eafcc..159b98e0e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -35,6 +35,7 @@ import android.os.Message; import android.os.Messenger; import android.provider.ContactsContract; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -60,18 +61,22 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; 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; import org.sufficientlysecure.keychain.util.ExportHelper; @@ -79,12 +84,17 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.Preferences; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -public class ViewKeyActivity extends BaseActivity implements +public class ViewKeyActivity extends BaseNfcActivity implements LoaderManager.LoaderCallbacks { + public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; + public static final String EXTRA_NFC_AID = "nfc_aid"; + public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + static final int REQUEST_QR_FINGERPRINT = 1; static final int REQUEST_DELETE = 2; static final int REQUEST_EXPORT = 3; @@ -107,6 +117,8 @@ public class ViewKeyActivity extends BaseActivity implements private ImageView mQrCode; private CardView mQrCodeLayout; + private String mQrCodeLoaded; + // NFC private NfcHelper mNfcHelper; @@ -257,6 +269,15 @@ public class ViewKeyActivity extends BaseActivity implements mNfcHelper.initNfc(mDataUri); startFragment(savedInstanceState, mDataUri); + + if (savedInstanceState == null && getIntent().hasExtra(EXTRA_NFC_AID)) { + Intent intent = getIntent(); + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + showYubikeyFragment(nfcFingerprints, nfcUserId, nfcAid); + } + } @Override @@ -517,6 +538,60 @@ public class ViewKeyActivity extends BaseActivity implements } } + @Override + protected void onNfcPerform() throws IOException { + + final byte[] nfcFingerprints = nfcGetFingerprints(); + final String nfcUserId = nfcGetUserId(); + final byte[] nfcAid = nfcGetAid(); + + String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints); + final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); + + if (!mFingerprint.equals(fp)) { + try { + CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId); + ring.getMasterKeyId(); + + Notify.create(this, "Different key stored on Yubikey!", 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_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + startActivity(intent); + finish(); + } + }, R.string.snack_yubikey_view).show(); + return; + + } catch (PgpKeyNotFoundException e) { + Notify.create(this, "Different key stored on Yubikey!", Style.ERROR).show(); + return; + } + } + + showYubikeyFragment(nfcFingerprints, nfcUserId, nfcAid); + + } + + public void showYubikeyFragment(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) { + ViewKeyYubikeyFragment frag = ViewKeyYubikeyFragment.newInstance( + nfcFingerprints, nfcUserId, nfcAid); + + FragmentManager manager = getSupportFragmentManager(); + + manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE); + manager.beginTransaction() + .addToBackStack("yubikey") + .replace(R.id.view_key_fragment, frag) + .commit(); + } + private void encrypt(Uri dataUri, boolean text) { // If there is no encryption key, don't bother. if (!mHasEncrypt) { @@ -648,6 +723,7 @@ public class ViewKeyActivity extends BaseActivity implements } protected void onPostExecute(Bitmap qrCode) { + mQrCodeLoaded = fingerprint; // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. Bitmap scaled = Bitmap.createScaledBitmap(qrCode, @@ -725,7 +801,6 @@ public class ViewKeyActivity extends BaseActivity implements mName.setText(R.string.user_id_no_name); } - String oldFingerprint = mFingerprint; mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); @@ -789,7 +864,7 @@ public class ViewKeyActivity extends BaseActivity implements mStatusImage.setVisibility(View.GONE); color = getResources().getColor(R.color.primary); // reload qr code only if the fingerprint changed - if (!mFingerprint.equals(oldFingerprint)) { + if (!mFingerprint.equals(mQrCodeLoaded)) { loadQrCode(mFingerprint); } photoTask.execute(mMasterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java new file mode 100644 index 000000000..7df791fbf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java @@ -0,0 +1,129 @@ +package org.sufficientlysecure.keychain.ui; + + +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +public class ViewKeyYubikeyFragment extends Fragment { + + + public static final String ARG_FINGERPRINT = "fingerprint"; + public static final String ARG_USER_ID = "user_id"; + public static final String ARG_AID = "aid"; + private byte[] mFingerprints; + private String mUserId; + private byte[] mAid; + + public static ViewKeyYubikeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) { + + ViewKeyYubikeyFragment frag = new ViewKeyYubikeyFragment(); + + Bundle args = new Bundle(); + args.putByteArray(ARG_FINGERPRINT, fingerprints); + args.putString(ARG_USER_ID, userId); + args.putByteArray(ARG_AID, aid); + frag.setArguments(args); + + return frag; + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + mFingerprints = args.getByteArray(ARG_FINGERPRINT); + mUserId = args.getString(ARG_USER_ID); + mAid = args.getByteArray(ARG_AID); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_yubikey, null); + + TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); + TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid); + + String serno = Hex.toHexString(mAid, 10, 4); + vSerNo.setText("Serial N° " + serno); + + if (!mUserId.isEmpty()) { + vUserId.setText("Key holder: " + mUserId); + } else { + vUserId.setText("Key holder: " + ""); + } + + view.findViewById(R.id.button_import).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + promoteToSecretKey(); + } + }); + + + return view; + } + + public void promoteToSecretKey() { + + // Message is received after decrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + PromoteKeyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + result.createNotify(getActivity()).show(); + } + + } + }; + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + + intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING); + + long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints); + + Bundle data = new Bundle(); + data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, masterKeyId); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // start service with intent + getActivity().startService(intent); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index ca1d79155..2fd88fd66 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -248,12 +248,17 @@ public abstract class BaseNfcActivity extends BaseActivity { return fp; } + public byte[] nfcGetAid() throws IOException { + + String info = "00CA004F00"; + return mIsoDep.transceive(Hex.decode(info)); + + } public String nfcGetUserId() throws IOException { + String info = "00CA006500"; - String data = "00CA005E00"; - return nfcGetHolderName(nfcCommunicate(info)) + " <" + (new String(Hex.decode(nfcGetDataField( - nfcCommunicate(data))))) + ">"; + return nfcGetHolderName(nfcCommunicate(info)); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index c5403e054..ae66b59d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; +import java.nio.ByteBuffer; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -215,7 +216,15 @@ public class KeyFormattingUtils { * @return */ public static String convertFingerprintToHex(byte[] fingerprint) { - return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH); + return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH); + } + + public static long getKeyIdFromFingerprint(byte[] fingerprint) { + ByteBuffer buf = ByteBuffer.wrap(fingerprint); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); } /** diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml index 2fb3737ba..0f5e50d42 100644 --- a/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_yubikey_fragment.xml @@ -9,83 +9,30 @@ android:fillViewport="true" android:layout_above="@+id/create_key_buttons"> - + android:orientation="vertical"> - + + - - - - - - - - - - - - - - - - - - - + diff --git a/OpenKeychain/src/main/res/layout/view_key_yubikey.xml b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml new file mode 100644 index 000000000..7da1fc5c4 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +