diff options
Diffstat (limited to 'OpenKeychain/src/main')
33 files changed, 1615 insertions, 573 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index d9f9766c9..d983498cc 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -674,6 +674,12 @@ android:taskAffinity=":Nfc" android:allowTaskReparenting="true" /> + <activity + android:name=".ui.NfcOperationActivity" + android:launchMode="singleTop" + android:taskAffinity=":Nfc" + android:allowTaskReparenting="true" /> + <!--<activity--> <!--android:name=".ui.NfcIntentActivity"--> <!--android:launchMode="singleTop">--> 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/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 4ceb34722..ad2b297dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -28,8 +28,9 @@ 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; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; @@ -38,6 +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.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; @@ -74,13 +77,14 @@ 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); + + 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 - Passphrase passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); + Passphrase passphrase = parcel.mCryptoInput.getPassphrase(); if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); @@ -92,9 +96,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<UncachedKeyRing> certifiedKeys = new ArrayList<>(); @@ -103,6 +104,8 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(parcel.mCryptoInput.getSignatureTime()); + // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { @@ -123,28 +126,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.mCryptoInput.getSignatureTime()); - certifiedKey = certificationKey.certifyUserIds( - publicRing, action.mUserIds, null, null); + if (!result.success()) { + certifyError += 1; + continue; } - - 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 (result.nfcInputRequired()) { + RequiredInputParcel requiredInput = result.getRequiredInput(); + allRequiredInput.addAll(requiredInput); + continue; } - if (certifiedKey == null) { - certifyError += 1; - log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3); - } - certifiedKeys.add(certifiedKey); + certifiedKeys.add(result.getCertifiedRing()); } catch (NotFoundException e) { certifyError += 1; @@ -153,6 +149,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/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index a179b53ee..28ef7582b 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.Passphrase; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -56,7 +58,7 @@ public class EditKeyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public EditKeyResult execute(SaveKeyringParcel saveParcel, Passphrase passphrase) { + public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); log.add(LogType.MSG_ED, 0); @@ -70,7 +72,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) { @@ -81,7 +83,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/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java index 94684851a..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,6 +23,7 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +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; @@ -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, RequiredInputParcel 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..e0ba28fbe --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -0,0 +1,45 @@ +package org.sufficientlysecure.keychain.operations.results; + + +import android.os.Parcel; + +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + + +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; + + final RequiredInputParcel mRequiredInput; + + public InputPendingResult(int result, OperationLog log) { + super(result, log); + mRequiredInput = null; + } + + public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) { + super(RESULT_PENDING, log); + mRequiredInput = requiredInput; + } + + public InputPendingResult(Parcel source) { + super(source); + mRequiredInput = source.readParcelable(getClass().getClassLoader()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mRequiredInput, 0); + } + + public boolean isPending() { + return (mResult & RESULT_PENDING) == RESULT_PENDING; + } + + public RequiredInputParcel getRequiredInputParcel() { + return mRequiredInput; + } + +} 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..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), @@ -697,9 +698,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/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/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java index cf40001b3..bda9893dd 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 @@ -37,7 +37,6 @@ public class PgpSignEncryptResult extends OperationResult { long mNfcKeyId; byte[] mNfcHash; int mNfcAlgo; - Date mNfcTimestamp; Passphrase mNfcPassphrase; byte[] mDetachedSignature; @@ -49,11 +48,10 @@ public class PgpSignEncryptResult extends OperationResult { mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; } - public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, Passphrase passphrase) { + public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Passphrase passphrase) { mNfcKeyId = nfcKeyId; mNfcHash = nfcHash; mNfcAlgo = nfcAlgo; - mNfcTimestamp = nfcTimestamp; mNfcPassphrase = passphrase; } @@ -73,10 +71,6 @@ public class PgpSignEncryptResult extends OperationResult { return mNfcAlgo; } - public Date getNfcTimestamp() { - return mNfcTimestamp; - } - public Passphrase getNfcPassphrase() { return mNfcPassphrase; } @@ -97,7 +91,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; } @@ -114,12 +107,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); @@ -138,4 +125,4 @@ public class PgpSignEncryptResult extends OperationResult { } }; -}
\ No newline at end of file +} 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..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,6 +21,7 @@ import android.os.Parcel; import java.util.ArrayList; + public class SignEncryptResult extends OperationResult { ArrayList<PgpSignEncryptResult> mResults; @@ -28,6 +29,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 6ce77394c..715d5af30 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -43,10 +43,13 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; +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. @@ -184,13 +187,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return PgpConstants.sPreferredHashAlgorithms; } - private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash, - Date nfcCreationTimestamp) { + private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, + Map<ByteBuffer,byte[]> 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,29 +203,43 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } } - public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext, - byte[] nfcSignedHash, Date nfcCreationTimestamp) - throws PgpGeneralException { + public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) { + PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder( + PgpConstants.CERTIFY_HASH_ALGO, signedHashes); + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } - if (nfcSignedHash != null && nfcCreationTimestamp == null) { - throw new PgpGeneralException("Got nfc hash without timestamp!!"); + + 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<ByteBuffer, byte[]> signedHashes, Date creationTimestamp) + throws PgpGeneralException { + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { + throw new PrivateKeyNotUnlockedException(); } // 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) { @@ -238,7 +255,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) { @@ -261,133 +278,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<String> userIds, - byte[] nfcSignedHash, Date nfcCreationTimestamp) { - 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, nfcSignedHash, nfcCreationTimestamp); - - 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 (nfcCreationTimestamp != null) { - spGen.setSignatureCreationTime(false, nfcCreationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); - } - 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<WrappedUserAttribute> userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) { - 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, nfcSignedHash, nfcCreationTimestamp); - - 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 (nfcCreationTimestamp != null) { - spGen.setSignatureCreationTime(false, nfcCreationTimestamp); - Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp); - } - 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..ff162775a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -0,0 +1,148 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.nio.ByteBuffer; +import java.util.Date; +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.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.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<ByteBuffer,byte[]> 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 RequiredInputParcel mRequiredInput; + final UncachedKeyRing mCertifiedRing; + + PgpCertifyResult() { + mRequiredInput = null; + mCertifiedRing = null; + } + + PgpCertifyResult(RequiredInputParcel 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 RequiredInputParcel getRequiredInput() { + return mRequiredInput; + } + + } + +} 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 b3bf92364..092fd9d48 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; @@ -86,9 +92,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * This indicator may be null. */ public class PgpKeyOperation { + private Stack<Progressable> mProgress; private AtomicBoolean mCancelled; + NfcSignOperationsBuilder mNfcSignOps; + private CryptoInputParcel mCryptoInput; + public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { @@ -97,9 +107,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() { @@ -317,7 +329,8 @@ public class PgpKeyOperation { masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); subProgressPush(50, 100); - return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, new Passphrase(), log); + mCryptoInput = new CryptoInputParcel(new Date(), new Passphrase("")); + return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, log); } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -348,8 +361,8 @@ public class PgpKeyOperation { * namely stripping of subkeys and changing the protection mode of dummy keys. * */ - public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, - Passphrase passphrase) { + public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, + SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); int indent = 0; @@ -387,11 +400,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(); @@ -399,13 +417,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, Passphrase passphrase, + SaveKeyringParcel saveParcel, OperationLog log) { int indent = 1; @@ -414,18 +432,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.getCharArray()); - 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().getCharArray()); + masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } } } @@ -480,9 +505,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(); @@ -509,9 +541,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(); @@ -539,9 +577,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(); @@ -611,11 +655,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; } @@ -627,11 +678,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; } @@ -718,7 +775,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 @@ -756,9 +814,16 @@ public class PgpKeyOperation { pKey = PGPPublicKey.removeCertification(pKey, sig); } + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + mCryptoInput.getPassphrase().getCharArray()); + 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)); } @@ -782,10 +847,17 @@ public class PgpKeyOperation { PGPPublicKey pKey = sKey.getPublicKey(); // generate and add new signature - PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey); - - pKey = PGPPublicKey.addCertification(pKey, sig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, 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)); + } catch (NfcInteractionNeeded e) { + mNfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -828,10 +900,16 @@ 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; { // Build key encrypter and decrypter based on passphrase @@ -840,7 +918,8 @@ public class PgpKeyOperation { 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.getCharArray()); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + mCryptoInput.getPassphrase().getCharArray()); PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); @@ -868,7 +947,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); @@ -892,6 +971,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)); @@ -1064,8 +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( - newPassphrase.getCharArray()); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); // noinspection unchecked for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { @@ -1116,8 +1200,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 { @@ -1173,10 +1258,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; } @@ -1191,15 +1282,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(); { @@ -1223,7 +1336,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); @@ -1239,19 +1352,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()); @@ -1260,29 +1369,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()) { @@ -1294,26 +1398,12 @@ public class PgpKeyOperation { } } - private static PGPSignature generateSubkeyBindingSignature( - PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, - PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, Passphrase passphrase) - throws IOException, PGPException, SignatureException { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.getCharArray()); - 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 @@ -1324,10 +1414,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); } @@ -1342,10 +1432,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()); @@ -1372,4 +1458,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/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java index 4a920685a..022dc4d32 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -20,11 +20,18 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; +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; @@ -37,13 +44,73 @@ public class PgpSignEncryptInput { protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED; protected Passphrase 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) { + + ClassLoader loader = getClass().getClassLoader(); + + // 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.readParcelable(loader); + mSymmetricEncryptionAlgorithm = source.readInt(); + mSignatureMasterKeyId = source.readLong(); + mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null; + mSignatureHashAlgorithm = source.readInt(); + mSignaturePassphrase = source.readParcelable(loader); + 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(loader); + } + + @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.writeParcelable(mSymmetricPassphrase, 0); + dest.writeInt(mSymmetricEncryptionAlgorithm); + dest.writeLong(mSignatureMasterKeyId); + if (mSignatureSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSignatureSubKeyId); + } else { + dest.writeInt(0); + } + dest.writeInt(mSignatureHashAlgorithm); + dest.writeParcelable(mSignaturePassphrase, 0); + 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; @@ -57,19 +124,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; } @@ -78,7 +137,7 @@ public class PgpSignEncryptInput { return mSignaturePassphrase; } - public PgpSignEncryptInput setSignaturePassphrase(Passphrase signaturePassphrase) { + public PgpSignEncryptInputParcel setSignaturePassphrase(Passphrase signaturePassphrase) { mSignaturePassphrase = signaturePassphrase; return this; } @@ -87,7 +146,7 @@ public class PgpSignEncryptInput { return mSignatureHashAlgorithm; } - public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { + public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) { mSignatureHashAlgorithm = signatureHashAlgorithm; return this; } @@ -96,7 +155,7 @@ public class PgpSignEncryptInput { return mSignatureSubKeyId; } - public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { + public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) { mSignatureSubKeyId = signatureSubKeyId; return this; } @@ -105,7 +164,7 @@ public class PgpSignEncryptInput { return mSignatureMasterKeyId; } - public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { + public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) { mSignatureMasterKeyId = signatureMasterKeyId; return this; } @@ -114,7 +173,7 @@ public class PgpSignEncryptInput { return mSymmetricEncryptionAlgorithm; } - public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; return this; } @@ -123,7 +182,7 @@ public class PgpSignEncryptInput { return mSymmetricPassphrase; } - public PgpSignEncryptInput setSymmetricPassphrase(Passphrase symmetricPassphrase) { + public PgpSignEncryptInputParcel setSymmetricPassphrase(Passphrase symmetricPassphrase) { mSymmetricPassphrase = symmetricPassphrase; return this; } @@ -132,7 +191,7 @@ public class PgpSignEncryptInput { return mEncryptionMasterKeyIds; } - public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { + public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { mEncryptionMasterKeyIds = encryptionMasterKeyIds; return this; } @@ -141,7 +200,7 @@ public class PgpSignEncryptInput { return mCompressionId; } - public PgpSignEncryptInput setCompressionId(int compressionId) { + public PgpSignEncryptInputParcel setCompressionId(int compressionId) { mCompressionId = compressionId; return this; } @@ -154,28 +213,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; } @@ -184,7 +237,7 @@ public class PgpSignEncryptInput { return mCleartextSignature; } - public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { + public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) { this.mDetachedSignature = detachedSignature; return this; } @@ -193,7 +246,7 @@ public class PgpSignEncryptInput { return mDetachedSignature; } - public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) { + public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) { this.mHiddenRecipients = hiddenRecipients; return this; } @@ -201,5 +254,29 @@ public class PgpSignEncryptInput { public boolean isHiddenRecipients() { return mHiddenRecipients; } + + public PgpSignEncryptInputParcel setCryptoInput(CryptoInputParcel cryptoInput) { + mCryptoInput = cryptoInput; + return this; + } + + public Map<ByteBuffer, byte[]> getCryptoData() { + return mCryptoInput.getCryptoData(); + } + + public Date getSignatureTime() { + return mCryptoInput.getSignatureTime(); + } + + public static final Creator<PgpSignEncryptInputParcel> CREATOR = new Creator<PgpSignEncryptInputParcel>() { + 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 bd3c31d3a..7e70b4571 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -71,7 +71,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 +99,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; @@ -281,8 +281,9 @@ public class PgpSignEncryptOperation extends BaseOperation { try { boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption; - signatureGenerator = signingKey.getSignatureGenerator( - input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); + signatureGenerator = signingKey.getDataSignatureGenerator( + 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 +490,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 975548c95..b178e9515 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 org.sufficientlysecure.keychain.util.Passphrase; @@ -42,7 +41,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<Uri> mInputUris = new ArrayList<>(); public ArrayList<Uri> mOutputUris = new ArrayList<>(); @@ -53,26 +52,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.readParcelable(Passphrase.class.getClassLoader()); - mSymmetricEncryptionAlgorithm = src.readInt(); - mSignatureMasterKeyId = src.readLong(); - mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null; - mSignatureHashAlgorithm = src.readInt(); - mSignaturePassphrase = src.readParcelable(Passphrase.class.getClassLoader()); - 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); @@ -110,34 +90,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.writeParcelable(mSymmetricPassphrase, flags); - dest.writeInt(mSymmetricEncryptionAlgorithm); - dest.writeLong(mSignatureMasterKeyId); - if (mSignatureSubKeyId != null) { - dest.writeInt(1); - dest.writeLong(mSignatureSubKeyId); - } else { - dest.writeInt(0); - } - dest.writeInt(mSignatureHashAlgorithm); - dest.writeParcelable(mSignaturePassphrase, flags); - 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 98a9ff44f..7d8aa993d 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; @@ -259,11 +260,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 @@ -276,15 +279,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); @@ -299,7 +305,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(); @@ -390,7 +396,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) @@ -413,16 +419,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 } @@ -440,7 +451,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 f4b941109..405a6a24b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -22,9 +22,13 @@ 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; /** @@ -38,14 +42,17 @@ public class CertifyActionsParcel implements Parcelable { public CertifyLevel mLevel; public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>(); + public CryptoInputParcel mCryptoInput; - public CertifyActionsParcel(long masterKeyId) { + public CertifyActionsParcel(CryptoInputParcel cryptoInput, 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()]; @@ -59,11 +66,16 @@ 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<ByteBuffer, byte[]> getSignatureData() { + return mCryptoInput.getCryptoData(); + } + public static final Creator<CertifyActionsParcel> CREATOR = new Creator<CertifyActionsParcel>() { 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 d5f13f7ce..cd3e06705 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; @@ -161,6 +162,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"; @@ -470,11 +472,11 @@ public class KeychainIntentService extends IntentService implements Progressable // Input SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - Passphrase passphrase = data.getParcelable(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/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/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java new file mode 100644 index 000000000..d77bbe7e2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -0,0 +1,115 @@ +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; + +import org.sufficientlysecure.keychain.util.Passphrase; + + +/** This is a base class for the input of crypto operations. + * + */ +public class CryptoInputParcel implements Parcelable { + + final Date mSignatureTime; + final Passphrase mPassphrase; + + // this map contains both decrypted session keys and signed hashes to be + // used in the crypto operation described by this parcel. + private HashMap<ByteBuffer,byte[]> mCryptoData = new HashMap<>(); + + public CryptoInputParcel(Date signatureTime, Passphrase 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.readParcelable(getClass().getClassLoader()); + + { + 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.writeParcelable(mPassphrase, 0); + + dest.writeInt(mCryptoData.size()); + for (HashMap.Entry<ByteBuffer,byte[]> 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<ByteBuffer, byte[]> getCryptoData() { + return Collections.unmodifiableMap(mCryptoData); + } + + public Date getSignatureTime() { + return mSignatureTime; + } + + public boolean hasPassphrase() { + return mPassphrase != null; + } + + public Passphrase getPassphrase() { + return mPassphrase; + } + + public static final Creator<CryptoInputParcel> CREATOR = new Creator<CryptoInputParcel>() { + public CryptoInputParcel createFromParcel(final Parcel source) { + return new CryptoInputParcel(source); + } + + public CryptoInputParcel[] newArray(final int size) { + return new CryptoInputParcel[size]; + } + }; + + @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 new file mode 100644 index 000000000..3d91812eb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -0,0 +1,166 @@ +package org.sufficientlysecure.keychain.service.input; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; + +import android.os.Parcel; +import android.os.Parcelable; + + +public class RequiredInputParcel implements Parcelable { + + public enum RequiredInputType { + PASSPHRASE, NFC_SIGN, NFC_DECRYPT + } + + 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, + int[] signAlgos, Date signatureTime, Long keyId) { + mType = type; + mInputHashes = inputHashes; + mSignAlgos = signAlgos; + mSignatureTime = signatureTime; + mSubKeyId = keyId; + } + + 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]; + for (int i = 0; i < count; i++) { + 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 long getSubKeyId() { + return mSubKeyId; + } + + public static RequiredInputParcel createNfcSignOperation( + byte[] inputHash, int signAlgo, Date signatureTime) { + return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, 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 + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType.ordinal()); + 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); + dest.writeLong(mSignatureTime.getTime()); + } else { + dest.writeInt(0); + } + if (mSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSubKeyId); + } else { + dest.writeInt(0); + } + + } + + public static final Creator<RequiredInputParcel> CREATOR = new Creator<RequiredInputParcel>() { + public RequiredInputParcel createFromParcel(final Parcel source) { + return new RequiredInputParcel(source); + } + + public RequiredInputParcel[] newArray(final int size) { + return new RequiredInputParcel[size]; + } + }; + + public static class NfcSignOperationsBuilder { + Date mSignatureTime; + ArrayList<Integer> mSignAlgos = new ArrayList<>(); + ArrayList<byte[]> mInputHashes = new ArrayList<>(); + + public NfcSignOperationsBuilder(Date signatureTime) { + mSignatureTime = signatureTime; + } + + public RequiredInputParcel 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 RequiredInputParcel(RequiredInputType.NFC_SIGN, + inputHashes, signAlgos, mSignatureTime, null); + } + + public void addHash(byte[] hash, int algo) { + mInputHashes.add(hash); + mSignAlgos.add(algo); + } + + 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 != RequiredInputType.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 a0f89b06a..a5f818734 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; @@ -68,10 +68,9 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.lang.reflect.Method; import java.util.ArrayList; -public class CertifyKeyFragment extends LoaderFragment - implements LoaderManager.LoaderCallbacks<Cursor> { - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; +public class CertifyKeyFragment extends CryptoOperationFragment + implements LoaderManager.LoaderCallbacks<Cursor> { private CheckBox mUploadKeyCheckbox; ListView mUserIds; @@ -101,9 +100,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!"); @@ -113,6 +109,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); @@ -142,9 +139,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); @@ -172,7 +167,7 @@ public class CertifyKeyFragment extends LoaderFragment Notify.showNotify(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR); } else { - initiateCertifying(); + cryptoOperation(null); } } }); @@ -182,7 +177,7 @@ public class CertifyKeyFragment extends LoaderFragment mUploadKeyCheckbox.setChecked(false); } - return root; + return view; } @Override @@ -306,7 +301,6 @@ public class CertifyKeyFragment extends LoaderFragment } mUserIdsAdapter.swapCursor(matrix); - setContentShown(true, isResumed()); } @Override @@ -314,49 +308,8 @@ 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) - Passphrase 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(); - } - } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PASSPHRASE: { - if (resultCode == Activity.RESULT_OK && data != null) { - startCertifying(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - - /** - * kicks off the actual signing process on a background thread - */ - private void startCertifying() { + protected void cryptoOperation(CryptoInputParcel cryptoInput) { // Bail out if there is not at least one user id selected ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { @@ -368,7 +321,7 @@ public class CertifyKeyFragment extends LoaderFragment Bundle data = new Bundle(); { // fill values for this action - CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); + CertifyActionsParcel parcel = new CertifyActionsParcel(cryptoInput, mSignMasterKeyId); parcel.mCertifyActions.addAll(certifyActions); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); @@ -388,14 +341,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..6b67d1db8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -0,0 +1,97 @@ +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.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler.MessageStatus; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + + +public abstract class CryptoOperationFragment extends Fragment { + + public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; + public static final int REQUEST_CODE_NFC = 0x00008002; + + 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_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!"); + + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); + cryptoOperation(cryptoInput); + } + 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(); + + OperationResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); + if (result == null || ! (result instanceof InputPendingResult)) { + return false; + } + + InputPendingResult pendingResult = (InputPendingResult) result; + if (pendingResult.isPending()) { + RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); + initiateInputActivity(requiredInput); + return true; + } + } + + return false; + } + + protected abstract void cryptoOperation(CryptoInputParcel cryptoInput); + +} 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 d2c1ab74c..45d681308 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; @@ -69,14 +71,13 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public class EditKeyFragment extends LoaderFragment implements + +public class EditKeyFragment extends CryptoOperationFragment implements LoaderManager.LoaderCallbacks<Cursor> { 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; @@ -101,7 +102,6 @@ public class EditKeyFragment extends LoaderFragment implements private SaveKeyringParcel mSaveKeyringParcel; private String mPrimaryUserId; - private Passphrase mCurrentPassphrase; /** * Creates new instance of this fragment @@ -130,8 +130,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); @@ -141,7 +140,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 @@ -156,7 +155,7 @@ public class EditKeyFragment extends LoaderFragment implements if (mDataUri == null) { returnKeyringParcel(); } else { - saveInDatabase(mCurrentPassphrase); + cryptoOperation(new CryptoInputParcel(new Date())); } } }, new OnClickListener() { @@ -186,18 +185,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) { @@ -217,9 +210,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()); @@ -230,24 +220,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); @@ -263,28 +239,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.getParcelableExtra(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 @@ -323,7 +277,6 @@ public class EditKeyFragment extends LoaderFragment implements } public Loader<Cursor> onCreateLoader(int id, Bundle args) { - setContentShown(false); switch (id) { case LOADER_ID_USER_IDS: { @@ -356,7 +309,6 @@ public class EditKeyFragment extends LoaderFragment implements break; } - setContentShown(true); } /** @@ -398,7 +350,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"); } @@ -594,8 +546,11 @@ public class EditKeyFragment extends LoaderFragment implements getActivity().finish(); } - private void saveInDatabase(Passphrase 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(), @@ -606,6 +561,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 @@ -641,7 +600,7 @@ public class EditKeyFragment extends LoaderFragment implements // fill values for this action Bundle data = new Bundle(); - data.putParcelable(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 5438f667c..b87dec8fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -25,16 +25,16 @@ 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.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; -import java.util.Date; public abstract class EncryptActivity extends BaseActivity { @@ -43,8 +43,6 @@ public abstract class EncryptActivity extends BaseActivity { // For NFC data protected Passphrase mSigningKeyPassphrase = null; - protected Date mNfcTimestamp = null; - protected byte[] mNfcHash = null; @Override public void onCreate(Bundle savedInstanceState) { @@ -65,17 +63,11 @@ public abstract class EncryptActivity extends BaseActivity { startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); } - protected void startNfcSign(long keyId, Passphrase 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, RequiredInputParcel 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_REQUIRED_INPUT, nfcOps); + // TODO respect keyid(?) startActivityForResult(intent, REQUEST_CODE_NFC); } @@ -94,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; @@ -109,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; @@ -118,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 @@ -142,9 +144,12 @@ 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()); + RequiredInputParcel parcel = RequiredInputParcel.createNfcSignOperation( + pgpResult.getNfcHash(), + pgpResult.getNfcAlgo(), + input.getSignatureTime()); + startNfcSign(pgpResult.getNfcKeyId(), parcel); + } else { throw new RuntimeException("Unhandled pending result!"); } @@ -159,8 +164,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 0dd672c90..eef11b855 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -253,7 +253,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 847f745d7..3047661ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -234,7 +234,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..69467daac --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -0,0 +1,490 @@ +/** + * 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.RequiredInputParcel; +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_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 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_REQUIRED_INPUT); + + } + + @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; + } + + 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", pin.length()) // Lc + + Hex.toHexString(pin.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.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.mInputHashes.length; i++) { + byte[] hash = mNfcOperations.mInputHashes[i]; + int algo = mNfcOperations.mSignAlgos[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 <email>" + * @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/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 360d30c82..9e04426eb 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.Passphrase; @@ -64,7 +68,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 @@ -87,7 +93,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); @@ -411,6 +426,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); } 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 947c316e0..4eb253825 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 @@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; 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; @@ -68,12 +67,11 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi * @param messenger to communicate back after setting the passphrase * @return */ - public static SetPassphraseDialogFragment newInstance(Messenger messenger, Passphrase 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.putParcelable(ARG_OLD_PASSPHRASE, oldPassphrase); frag.setArguments(args); @@ -89,7 +87,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi int title = getArguments().getInt(ARG_TITLE); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - Passphrase oldPassphrase = getArguments().getParcelable(ARG_OLD_PASSPHRASE); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -103,13 +100,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 (oldPassphrase.isEmpty()) { - 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/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java index a65576613..c010ac151 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2015 Kent Nguyen <kentnguyen@moneylover.me> * * This program is free software: you can redistribute it and/or modify diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f22a0c533..a7e9cdf7e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -898,6 +898,7 @@ <!-- modifySecretKeyRing --> <string name="msg_mr">"Modifying keyring %s"</string> + <string name="msg_mf_divert">"Will divert to card/nfc for crypto operations"</string> <string name="msg_mf_error_divert_serial">"The serial number of a divert-to-card key must be 16 bytes! This is a programming error, please file a bug report!"</string> <string name="msg_mf_error_encode">"Encoding exception!"</string> <string name="msg_mf_error_fingerprint">"Actual key fingerprint does not match the expected one!"</string> @@ -912,6 +913,7 @@ <string name="msg_mf_error_passphrase_master">"Fatal error decrypting master key! This is likely a programming error, please file a bug report!"</string> <string name="msg_mf_error_pgp">"Internal OpenPGP error!"</string> <string name="msg_mf_error_sig">"Signature exception!"</string> + <string name="msg_mf_input_required">"Diverting to card/nfc for crypto operations"</string> <string name="msg_mf_master">"Modifying master certifications"</string> <string name="msg_mf_notation_empty">"Adding empty notation packet"</string> <string name="msg_mf_notation_pin">"Adding PIN notation packet"</string> @@ -988,7 +990,6 @@ <string name="msg_pr_success">"Key successfully promoted"</string> <!-- Other messages used in OperationLogs --> - <string name="msg_ek_error_divert">"Editing of NFC keys is not (yet) supported!"</string> <string name="msg_ek_error_dummy">"Cannot edit keyring with stripped master key!"</string> <string name="msg_ek_error_not_found">"Key not found!"</string> @@ -1100,9 +1101,9 @@ <string name="msg_crt_error_master_not_found">"Master key not found!"</string> <string name="msg_crt_error_nothing">"No keys certified!"</string> <string name="msg_crt_error_unlock">"Error unlocking master key!"</string> - <string name="msg_crt_error_divert">"Certification with NFC is not (yet) supported!"</string> <string name="msg_crt">"Certifying keyrings"</string> <string name="msg_crt_master_fetch">"Fetching certifying master key"</string> + <string name="msg_crt_nfc_return">"Returning for NFC input"</string> <string name="msg_crt_save">"Saving certified key %s"</string> <string name="msg_crt_saving">"Saving keyrings"</string> <string name="msg_crt_unlock">"Unlocking master key"</string> |