diff options
Diffstat (limited to 'OpenKeychain/src/main')
89 files changed, 3551 insertions, 1206 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index dbc50db26..b92d49d28 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -90,6 +90,8 @@ android:name=".ui.CreateKeyActivity" android:windowSoftInputMode="adjustResize" android:label="@string/title_manage_my_keys" + android:launchMode="singleTop" + android:allowTaskReparenting="true" android:parentActivityName=".ui.MainActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" @@ -674,6 +676,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..eb2a3d33f 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,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; @@ -60,7 +64,7 @@ public class CertifyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public CertifyResult certify(CertifyActionsParcel parcel, String keyServerUri) { + public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) { OperationLog log = new OperationLog(); log.add(LogType.MSG_CRT, 0); @@ -74,13 +78,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 (!cryptoInput.hasPassphrase()) { + return new CertifyResult(log, RequiredInputParcel.createRequiredPassphrase( + certificationKey.getKeyId(), certificationKey.getKeyId(), null)); } // certification is always with the master key id, so use that one - Passphrase passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); + Passphrase passphrase = cryptoInput.getPassphrase(); if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); @@ -92,9 +97,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 +105,10 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; + NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder( + cryptoInput.getSignatureTime(), certificationKey.getKeyId(), + certificationKey.getKeyId()); + // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { @@ -123,28 +129,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, cryptoInput.getCryptoData(), cryptoInput.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 +152,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..4072d91c5 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); @@ -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, cryptoInput, 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/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index f2516f1bd..ff0b545cd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -230,7 +230,7 @@ public class ImportExportOperation extends BaseOperation { } } catch (Keyserver.QueryFailedException e) { Log.e(Constants.TAG, "query failed", e); - log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3); + log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index fd86d4b92..ef08b0b77 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -50,7 +50,7 @@ public class PromoteKeyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public PromoteKeyResult execute(long masterKeyId) { + public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) { OperationLog log = new OperationLog(); log.add(LogType.MSG_PR, 0); @@ -58,27 +58,16 @@ public class PromoteKeyOperation extends BaseOperation { // Perform actual type change UncachedKeyRing promotedRing; { - try { - // This operation is only allowed for pure public keys - // TODO delete secret keys if they are stripped, or have been moved to the card? - if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) { - log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2); - return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); - } - log.add(LogType.MSG_PR_FETCHING, 1, KeyFormattingUtils.convertKeyIdToHex(masterKeyId)); CanonicalizedPublicKeyRing pubRing = mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); // create divert-to-card secret key from public key - promotedRing = pubRing.createDummySecretRing(); + promotedRing = pubRing.createDivertSecretRing(cardAid); - } catch (PgpKeyNotFoundException e) { - log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); - return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); } catch (NotFoundException e) { log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); 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 f56fe4bb9..0a0e63330 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/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java index abcf575af..842b75c3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java @@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult { public EditKeyResult(Parcel source) { super(source); - mMasterKeyId = source.readLong(); + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mMasterKeyId); + if (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } else { + dest.writeInt(0); + } } public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() { 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 f2a27b0fc..47f9271e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -250,12 +250,20 @@ public abstract class OperationResult implements Parcelable { public Showable createNotify(final Activity activity) { - Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast()); - Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType); - Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId()); - // Take the last message as string - int msgId = mLog.getLast().mType.getMsgId(); + String logText; + + LogEntryParcel entryParcel = mLog.getLast(); + // special case: first parameter may be a quantity + if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0 + && entryParcel.mParameters[0] instanceof Integer) { + logText = activity.getResources().getQuantityString(entryParcel.mType.getMsgId(), + (Integer) entryParcel.mParameters[0], + entryParcel.mParameters); + } else { + logText = activity.getString(entryParcel.mType.getMsgId(), + entryParcel.mParameters); + } Style style; @@ -273,19 +281,19 @@ public abstract class OperationResult implements Parcelable { } if (getLog() == null || getLog().isEmpty()) { - return Notify.create(activity, msgId, Notify.LENGTH_LONG, style); + return Notify.create(activity, logText, Notify.LENGTH_LONG, style); } - return Notify.create(activity, msgId, Notify.LENGTH_LONG, style, - new ActionListener() { - @Override - public void onAction() { - Intent intent = new Intent( - activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); - activity.startActivity(intent); - } - }, R.string.view_log); + return Notify.create(activity, logText, Notify.LENGTH_LONG, style, + new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); + activity.startActivity(intent); + } + }, R.string.view_log); } @@ -512,6 +520,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), @@ -521,6 +530,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify), MSG_MF_ERROR_NOEXIST_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_noexist_primary), MSG_MF_ERROR_NOEXIST_REVOKE (LogLevel.ERROR, R.string.msg_mf_error_noexist_revoke), + MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop), MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry), MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master), MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry), @@ -538,6 +548,9 @@ public abstract class OperationResult implements Parcelable { MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail), MSG_MF_PRIMARY_REPLACE_OLD (LogLevel.DEBUG, R.string.msg_mf_primary_replace_old), MSG_MF_PRIMARY_NEW (LogLevel.DEBUG, R.string.msg_mf_primary_new), + MSG_MF_RESTRICTED_MODE (LogLevel.INFO, R.string.msg_mf_restricted_mode), + MSG_MF_REQUIRE_DIVERT (LogLevel.OK, R.string.msg_mf_require_divert), + MSG_MF_REQUIRE_PASSPHRASE (LogLevel.OK, R.string.msg_mf_require_passphrase), MSG_MF_SUBKEY_CHANGE (LogLevel.INFO, R.string.msg_mf_subkey_change), MSG_MF_SUBKEY_NEW_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id), MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new), @@ -590,13 +603,11 @@ public abstract class OperationResult implements Parcelable { // promote key MSG_PR (LogLevel.START, R.string.msg_pr), - MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret), MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found), MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching), 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 +708,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/PromoteKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java index af9aff84a..d6c7a1ee0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java @@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult { public PromoteKeyResult(Parcel source) { super(source); - mMasterKeyId = source.readLong(); + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mMasterKeyId); + if (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } else { + dest.writeInt(0); + } } public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() { 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/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index c2506685d..8432b8f9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -98,11 +98,14 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { /** Create a dummy secret ring from this key */ public UncachedKeyRing createDummySecretRing () { - - PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), - S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY); + PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null); return new UncachedKeyRing(secRing); + } + /** Create a dummy secret ring from this key */ + public UncachedKeyRing createDivertSecretRing (byte[] cardAid) { + PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid); + return new UncachedKeyRing(secRing); } }
\ No newline at end of file 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..30be72dd5 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,131 +278,8 @@ 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); + public byte[] getIv() { + return mSecretKey.getIV(); } static class PrivateKeyNotUnlockedException extends RuntimeException { @@ -402,4 +296,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return mPrivateKey; } + // HACK, for TESTING ONLY!! + PGPSecretKey getSecretKey() { + return mSecretKey; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java new file mode 100644 index 000000000..90ec3053f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -0,0 +1,149 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.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, + publicKey.getKeyID(), publicKey.getKeyID()); + + 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..f73bada06 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; @@ -43,6 +43,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 +56,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,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * This indicator may be null. */ public class PgpKeyOperation { + private Stack<Progressable> mProgress; private AtomicBoolean mCancelled; @@ -317,7 +323,8 @@ public class PgpKeyOperation { masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); subProgressPush(50, 100); - return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, new Passphrase(), log); + CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase("")); + return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log); } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -348,8 +355,9 @@ 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, + CryptoInputParcel cryptoInput, + SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); int indent = 0; @@ -387,11 +395,24 @@ 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.isEmpty()) { + log.add(LogType.MSG_MF_ERROR_NOOP, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) { + log.add(LogType.MSG_MF_RESTRICTED_MODE, indent); return internalRestricted(sKR, saveParcel, log); } + // Do we require a passphrase? If so, pass it along + if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) { + log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); + return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredPassphrase( + masterSecretKey.getKeyID(), masterSecretKey.getKeyID(), + cryptoInput.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,33 +420,45 @@ 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, cryptoInput, saveParcel, log); } private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags, long masterKeyExpiry, - SaveKeyringParcel saveParcel, Passphrase passphrase, + CryptoInputParcel cryptoInput, + SaveKeyringParcel saveParcel, OperationLog log) { int indent = 1; + NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder( + cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(), + masterSecretKey.getKeyID()); + progress(R.string.progress_modify, 0); 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(cryptoInput.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); + } } } @@ -449,7 +482,7 @@ public class PgpKeyOperation { String userId = saveParcel.mAddUserIds.get(i); log.add(LogType.MSG_MF_UID_ADD, indent, userId); - if (userId.equals("")) { + if ("".equals(userId)) { log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } @@ -480,9 +513,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, + isPrimary, masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -509,9 +549,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, vector); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -539,9 +585,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -611,11 +663,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, false, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } + continue; } @@ -627,11 +686,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, true, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } ok = true; } @@ -718,8 +783,9 @@ public class PgpKeyOperation { } PGPPublicKey pKey = - updateMasterCertificates(masterPrivateKey, masterPublicKey, - flags, expiry, indent, log); + updateMasterCertificates( + masterSecretKey, masterPrivateKey, masterPublicKey, + flags, expiry, cryptoInput, nfcSignOps, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -756,9 +822,16 @@ public class PgpKeyOperation { pKey = PGPPublicKey.removeCertification(pKey, sig); } + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + cryptoInput.getPassphrase().getCharArray()); + PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor); + PGPSignature sig = generateSubkeyBindingSignature( + getSignatureGenerator(masterSecretKey, cryptoInput), + cryptoInput.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 +855,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPublicKey, masterPrivateKey, pKey); + + pKey = PGPPublicKey.addCertification(pKey, sig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } } subProgressPop(); @@ -828,10 +908,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, + add.mFlags, add.mExpiry); + pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } PGPSecretKey sKey; { // Build key encrypter and decrypter based on passphrase @@ -840,7 +926,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( + cryptoInput.getPassphrase().getCharArray()); PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); @@ -868,7 +955,7 @@ public class PgpKeyOperation { indent += 1; sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - passphrase, saveParcel.mNewUnlock, log, indent); + cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -892,6 +979,12 @@ public class PgpKeyOperation { } progress(R.string.progress_done, 100); + + if (!nfcSignOps.isEmpty()) { + log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent); + return new PgpEditKeyResult(log, nfcSignOps.build()); + } + log.add(LogType.MSG_MF_SUCCESS, indent); return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); @@ -1064,8 +1157,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(newPassphrase.getCharArray()); // noinspection unchecked for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { @@ -1116,9 +1208,13 @@ public class PgpKeyOperation { } /** Update all (non-revoked) uid signatures with new flags and expiry time. */ - private static PGPPublicKey updateMasterCertificates( - PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, - int flags, long expiry, int indent, OperationLog log) + private PGPPublicKey updateMasterCertificates( + PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey, + PGPPublicKey masterPublicKey, + int flags, long expiry, + CryptoInputParcel cryptoInput, + NfcSignOperationsBuilder nfcSignOps, + int indent, OperationLog log) throws PGPException, IOException, SignatureException { // keep track if we actually changed one @@ -1173,10 +1269,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, cryptoInput), + cryptoInput.getSignatureTime(), + masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + } catch (NfcInteractionNeeded e) { + nfcSignOps.addHash(e.hashToSign, e.hashAlgo); + } ok = true; } @@ -1191,15 +1293,37 @@ public class PgpKeyOperation { } - private static PGPSignature generateUserIdSignature( + 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 +1347,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 +1363,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 +1380,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 +1409,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 +1425,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 +1443,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 +1469,16 @@ public class PgpKeyOperation { return flags; } + private static boolean isDummy(PGPSecretKey secretKey) { + S2K s2k = secretKey.getS2K(); + return s2k.getType() == S2K.GNU_DUMMY_S2K + && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY; + } + + private static boolean isDivertToCard(PGPSecretKey secretKey) { + S2K s2k = secretKey.getS2K(); + return s2k.getType() == S2K.GNU_DUMMY_S2K + && 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..d5f3cf964 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(); + + 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 16a09e77b..ef19e3fa1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -72,7 +72,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * <p/> * 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,8 +99,8 @@ public class PgpSignEncryptOperation extends BaseOperation { /** * Signs and/or encrypts data based on parameters of class */ - public PgpSignEncryptResult execute(PgpSignEncryptInput input, - InputData inputData, OutputStream outputStream) { + public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, + InputData inputData, OutputStream outputStream) { int indent = 0; OperationLog log = new OperationLog(); @@ -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); @@ -495,7 +496,8 @@ public class PgpSignEncryptOperation extends BaseOperation { // 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 bd2866985..204af1b67 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; @@ -264,11 +265,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 @@ -281,8 +284,11 @@ 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() .setSignaturePassphrase(passphrase) .setEnableAsciiArmorOutput(asciiArmor) .setCleartextSignature(cleartextSign) @@ -290,7 +296,7 @@ public class OpenPgpService extends RemoteService { .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); @@ -305,7 +311,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(); @@ -401,7 +407,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.setSignaturePassphrase(passphrase) .setEnableAsciiArmorOutput(asciiArmor) .setVersionHeader(null) @@ -425,16 +431,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 } @@ -452,7 +463,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/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 507d4dea5..e19757d65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class AccountSettingsActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 407480c98..2b71d6dc1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -40,9 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.ui.BaseActivity; -import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index e8c3e4511..f312c0d44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 98a44466d..cb9f46f7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.BaseActivity; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index f4b941109..8721f4c0c 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; /** 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 5ecfb29f5..1a94d70b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; 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.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; @@ -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"; @@ -185,7 +187,7 @@ public class KeychainIntentService extends IntentService implements Progressable // promote key public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; - public static final String PROMOTE_TYPE = "promote_type"; + public static final String PROMOTE_CARD_AID = "promote_card_aid"; // consolidate public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; @@ -252,11 +254,12 @@ public class KeychainIntentService extends IntentService implements Progressable // Input CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); + CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); String keyServerUri = data.getString(UPLOAD_KEY_SERVER); // Operation CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); - CertifyResult result = op.certify(parcel, keyServerUri); + CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri); // Result sendMessageToHandler(MessageStatus.OKAY, result); @@ -470,11 +473,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); @@ -484,11 +487,12 @@ public class KeychainIntentService extends IntentService implements Progressable case ACTION_PROMOTE_KEYRING: { // Input - long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID); + long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); + byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID); // Operation PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); - PromoteKeyResult result = op.execute(keyRingId); + PromoteKeyResult result = op.execute(keyRingId, cardAid); // Result sendMessageToHandler(MessageStatus.OKAY, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 93a2bee23..8e37a8867 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -123,7 +123,7 @@ public class PassphraseCacheService extends Service { public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId, Passphrase passphrase, String primaryUserId) { - Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + masterKeyId); + Log.d(Constants.TAG, "PassphraseCacheService.addCachedPassphrase() for " + masterKeyId); Intent intent = new Intent(context, PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_ADD); @@ -137,6 +137,19 @@ public class PassphraseCacheService extends Service { context.startService(intent); } + public static void clearCachedPassphrase(Context context, long masterKeyId, long subKeyId) { + Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase() for " + masterKeyId); + + Intent intent = new Intent(context, PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); + + intent.putExtra(EXTRA_KEY_ID, masterKeyId); + intent.putExtra(EXTRA_SUBKEY_ID, subKeyId); + + context.startService(intent); + } + + /** * Gets a cached passphrase from memory by sending an intent to the service. This method is * designed to wait until the service returns the passphrase. @@ -395,12 +408,27 @@ public class PassphraseCacheService extends Service { } else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) { AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); - // Stop all ttl alarms - for (int i = 0; i < mPassphraseCache.size(); i++) { - am.cancel(buildIntent(this, mPassphraseCache.keyAt(i))); - } + if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) { - mPassphraseCache.clear(); + long keyId; + if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) { + keyId = intent.getLongExtra(EXTRA_KEY_ID, 0L); + } else { + keyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L); + } + // Stop specific ttl alarm and + am.cancel(buildIntent(this, keyId)); + mPassphraseCache.delete(keyId); + + } else { + + // Stop all ttl alarms + for (int i = 0; i < mPassphraseCache.size(); i++) { + am.cancel(buildIntent(this, mPassphraseCache.keyAt(i))); + } + mPassphraseCache.clear(); + + } updateService(); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 9fd278c13..2e0524141 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -82,10 +82,14 @@ public class SaveKeyringParcel implements Parcelable { mRevokeSubKeys = new ArrayList<>(); } + public boolean isEmpty() { + return isRestrictedOnly() && mChangeSubKeys.isEmpty(); + } + /** Returns true iff this parcel does not contain any operations which require a passphrase. */ public boolean isRestrictedOnly() { if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty() - || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty() + || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty() || !mRevokeSubKeys.isEmpty()) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 4bd3481e6..430d8a49b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.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..21aacd1f0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -0,0 +1,125 @@ +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() { + mSignatureTime = new Date(); + mPassphrase = null; + } + + public CryptoInputParcel(Date signatureTime, Passphrase passphrase) { + mSignatureTime = signatureTime == null ? new Date() : signatureTime; + mPassphrase = passphrase; + } + + public CryptoInputParcel(Passphrase passphrase) { + mSignatureTime = new Date(); + mPassphrase = 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..471fc0ec9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -0,0 +1,191 @@ +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 final byte[][] mInputHashes; + public final int[] mSignAlgos; + + private Long mMasterKeyId; + private Long mSubKeyId; + + private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, + int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { + mType = type; + mInputHashes = inputHashes; + mSignAlgos = signAlgos; + mSignatureTime = signatureTime; + mMasterKeyId = masterKeyId; + mSubKeyId = subKeyId; + } + + 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; + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; + mSubKeyId = source.readInt() != 0 ? source.readLong() : null; + + } + + public long getMasterKeyId() { + return mMasterKeyId; + } + + 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, null); + } + + public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash) { + return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, + new byte[][] { inputHash }, null, null, null, null); + } + + public static RequiredInputParcel createRequiredPassphrase( + long masterKeyId, long subKeyId, Date signatureTime) { + return new RequiredInputParcel(RequiredInputType.PASSPHRASE, + null, null, signatureTime, masterKeyId, subKeyId); + } + + public static RequiredInputParcel createRequiredPassphrase( + RequiredInputParcel req) { + return new RequiredInputParcel(RequiredInputType.PASSPHRASE, + null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId); + } + + + @Override + public int describeContents() { + return 0; + } + + @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 (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } 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<>(); + long mMasterKeyId; + long mSubKeyId; + + public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) { + mSignatureTime = signatureTime; + mMasterKeyId = masterKeyId; + mSubKeyId = subKeyId; + } + + 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, mMasterKeyId, mSubKeyId); + } + + 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/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index b7c80c1ed..016ab5f3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -23,6 +23,7 @@ import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class CertifyFingerprintActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 1fb88b182..3845e07cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + /** * Signs the specified public key with the specified secret master key diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 9b6e8d8f9..20a280a54 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -25,8 +25,6 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.PorterDuff; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Message; import android.os.Messenger; @@ -56,23 +54,20 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -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.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; 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; @@ -102,9 +97,6 @@ public class CertifyKeyFragment extends LoaderFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // Start out with a progress indicator. - setContentShown(false); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); if (mPubMasterKeyIds == null) { Log.e(Constants.TAG, "List of key ids to certify missing!"); @@ -114,6 +106,7 @@ public class CertifyKeyFragment extends LoaderFragment mPassthroughMessenger = getActivity().getIntent().getParcelableExtra( KeychainIntentService.EXTRA_MESSENGER); + mPassthroughMessenger = null; // TODO remove, development hack // preselect certify key id if given long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); @@ -143,9 +136,7 @@ public class CertifyKeyFragment extends LoaderFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - - View view = inflater.inflate(R.layout.certify_key_fragment, getContainer()); + View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); @@ -173,7 +164,7 @@ public class CertifyKeyFragment extends LoaderFragment Notify.create(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR).show(); } else { - initiateCertifying(); + cryptoOperation(new CryptoInputParcel()); } } }); @@ -183,7 +174,7 @@ public class CertifyKeyFragment extends LoaderFragment mUploadKeyCheckbox.setChecked(false); } - return root; + return view; } @Override @@ -222,17 +213,6 @@ public class CertifyKeyFragment extends LoaderFragment }) { @Override public byte[] getBlob(int column) { - // For some reason, getBlob was not implemented before ICS - if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) { - try { - // haha, yes there is int.class - Method m = MatrixCursor.class.getDeclaredMethod("get", new Class[]{int.class}); - m.setAccessible(true); - return (byte[]) m.invoke(this, 1); - } catch (Exception e) { - throw new UnsupportedOperationException(e); - } - } return super.getBlob(column); } }; @@ -307,7 +287,6 @@ public class CertifyKeyFragment extends LoaderFragment } mUserIdsAdapter.swapCursor(matrix); - setContentShown(true, isResumed()); } @Override @@ -315,49 +294,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()) { @@ -372,6 +310,7 @@ public class CertifyKeyFragment extends LoaderFragment CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); parcel.mCertifyActions.addAll(certifyActions); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); if (mUploadKeyCheckbox.isChecked()) { String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); @@ -396,11 +335,17 @@ public class CertifyKeyFragment extends LoaderFragment true, ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { 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/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index ab76f693e..0b203614b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,17 +17,25 @@ package org.sufficientlysecure.keychain.ui; +import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.view.View; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; +import java.io.IOException; import java.util.ArrayList; -public class CreateKeyActivity extends BaseActivity { +public class CreateKeyActivity extends BaseNfcActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; @@ -35,6 +43,10 @@ public class CreateKeyActivity extends BaseActivity { public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; + public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; + public static final String EXTRA_NFC_AID = "nfc_aid"; + public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + public static final String FRAGMENT_TAG = "currentFragment"; String mName; @@ -60,14 +72,29 @@ public class CreateKeyActivity extends BaseActivity { mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { + + Intent intent = getIntent(); // Initialize members with default values for a new instance - mName = getIntent().getStringExtra(EXTRA_NAME); - mEmail = getIntent().getStringExtra(EXTRA_EMAIL); - mFirstTime = getIntent().getBooleanExtra(EXTRA_FIRST_TIME, false); + mName = intent.getStringExtra(EXTRA_NAME); + mEmail = intent.getStringExtra(EXTRA_EMAIL); + mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); + + if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + + Fragment frag2 = CreateKeyYubiImportFragment.createInstance( + nfcFingerprints, nfcAid, nfcUserId); + loadFragment(frag2, FragAction.START); + + setTitle(R.string.title_import_keys); + return; + } else { + CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); + loadFragment(frag, FragAction.START); + } - // Start with first fragment of wizard - CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); - loadFragment(frag, FragAction.START); } if (mFirstTime) { @@ -80,6 +107,38 @@ public class CreateKeyActivity extends BaseActivity { } @Override + protected void onNfcPerform() throws IOException { + if (mCurrentFragment instanceof NfcListenerFragment) { + ((NfcListenerFragment) mCurrentFragment).onNfcPerform(); + return; + } + + byte[] scannedFingerprints = nfcGetFingerprints(); + byte[] nfcAid = nfcGetAid(); + String userId = nfcGetUserId(); + + try { + long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); + ring.getMasterKeyId(); + + Intent intent = new Intent(this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); + startActivity(intent); + finish(); + + } catch (PgpKeyNotFoundException e) { + Fragment frag = CreateKeyYubiImportFragment.createInstance( + scannedFingerprints, nfcAid, userId); + loadFragment(frag, FragAction.TO_RIGHT); + } + + } + + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -125,8 +184,14 @@ public class CreateKeyActivity extends BaseActivity { break; } + // do it immediately! getSupportFragmentManager().executePendingTransactions(); + + } + + interface NfcListenerFragment { + public void onNfcPerform() throws IOException; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 180a52a1c..3f56949f5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment { mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); -// mYubiKey = view.findViewById(R.id.create_key_yubikey_button); + mYubiKey = view.findViewById(R.id.create_key_yubikey_button); mCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { @@ -95,6 +95,14 @@ public class CreateKeyStartFragment extends Fragment { } }); + mYubiKey.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + }); + mImportKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java new file mode 100644 index 000000000..1cd0aaf2f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import java.io.IOException; +import java.util.ArrayList; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Preferences; + + +public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment { + + private static final String ARG_FINGERPRINT = "fingerprint"; + public static final String ARG_AID = "aid"; + public static final String ARG_USER_ID = "user_ids"; + + CreateKeyActivity mCreateKeyActivity; + + private byte[] mNfcFingerprints; + private long mNfcMasterKeyId; + private byte[] mNfcAid; + private String mNfcUserId; + private String mNfcFingerprint; + private ImportKeysListFragment mListFragment; + private TextView vSerNo; + private TextView vUserId; + + public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { + + CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment(); + + Bundle args = new Bundle(); + args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); + args.putByteArray(ARG_AID, nfcAid); + args.putString(ARG_USER_ID, userId); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + + mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT); + mNfcAid = args.getByteArray(ARG_AID); + mNfcUserId = args.getString(ARG_USER_ID); + + mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false); + + vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); + vUserId = (TextView) view.findViewById(R.id.yubikey_userid); + + { + View mBackButton = view.findViewById(R.id.create_key_back_button); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getFragmentManager().getBackStackEntryCount() == 0) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } else { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + } + }); + + View mNextButton = view.findViewById(R.id.create_key_next_button); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + importKey(); + } + }); + } + + mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true); + + view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + refreshSearch(); + } + }); + + setData(); + + getFragmentManager().beginTransaction() + .replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import") + .commit(); + + return view; + } + + @Override + public void onSaveInstanceState(Bundle args) { + super.onSaveInstanceState(args); + + args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints); + args.putByteArray(ARG_AID, mNfcAid); + args.putString(ARG_USER_ID, mNfcUserId); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + public void setData() { + String serno = Hex.toHexString(mNfcAid, 10, 4); + vSerNo.setText(getString(R.string.yubikey_serno, serno)); + + if (!mNfcUserId.isEmpty()) { + vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId)); + } else { + vUserId.setText(getString(R.string.yubikey_key_holder_unset)); + } + } + + public void refreshSearch() { + mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint, + Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); + } + + public void importKey() { + + // Message is received after decrypting is done in KeychainIntentService + ServiceProgressHandler saveHandler = new ServiceProgressHandler( + getActivity(), + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL, + ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT + ) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + ImportKeyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + if (!result.success()) { + result.createNotify(getActivity()).show(); + return; + } + + Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(mNfcMasterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + getActivity().finish(); + + } + + } + }; + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); + + String hexFp = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints); + ArrayList<ParcelableKeyRing> keyList = new ArrayList<>(); + keyList.add(new ParcelableKeyRing(hexFp, null, null)); + data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList); + + { + Preferences prefs = Preferences.getPreferences(getActivity()); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + } + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + + } + + @Override + public void onNfcPerform() throws IOException { + + mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints(); + mNfcAid = mCreateKeyActivity.nfcGetAid(); + mNfcUserId = mCreateKeyActivity.nfcGetUserId(); + + mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints); + + setData(); + refreshSearch(); + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java new file mode 100644 index 000000000..579dddf79 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + + +public class CreateKeyYubiWaitFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + View mBackButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java new file mode 100644 index 000000000..076c628b4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -0,0 +1,98 @@ +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.ServiceProgressHandler; +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; + } + break; + } + + 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 == ServiceProgressHandler.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/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index 162b10eca..dce2386b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -21,12 +21,12 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.PersistableBundle; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class DecryptFilesActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java index bc2ec014a..728e3ba41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 6dc2994cf..b607ba9f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -23,6 +23,7 @@ import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class EditKeyActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 417b50b50..bf17c2991 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; @@ -55,6 +57,7 @@ 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; @@ -64,14 +67,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; @@ -96,7 +98,6 @@ public class EditKeyFragment extends LoaderFragment implements private SaveKeyringParcel mSaveKeyringParcel; private String mPrimaryUserId; - private Passphrase mCurrentPassphrase; /** * Creates new instance of this fragment @@ -125,8 +126,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); @@ -136,7 +136,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 @@ -151,7 +151,7 @@ public class EditKeyFragment extends LoaderFragment implements if (mDataUri == null) { returnKeyringParcel(); } else { - saveInDatabase(mCurrentPassphrase); + cryptoOperation(new CryptoInputParcel()); } } }, new OnClickListener() { @@ -181,18 +181,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) { @@ -212,9 +206,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()); @@ -225,24 +216,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); @@ -258,28 +235,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 @@ -318,7 +273,6 @@ public class EditKeyFragment extends LoaderFragment implements } public Loader<Cursor> onCreateLoader(int id, Bundle args) { - setContentShown(false); switch (id) { case LOADER_ID_USER_IDS: { @@ -351,7 +305,6 @@ public class EditKeyFragment extends LoaderFragment implements break; } - setContentShown(true); } /** @@ -393,7 +346,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"); } @@ -589,8 +542,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); ServiceProgressHandler saveHandler = new ServiceProgressHandler( getActivity(), @@ -602,6 +558,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 @@ -637,7 +597,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 cd1028de4..949a595d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -25,17 +25,18 @@ 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.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.util.Passphrase; -import java.util.Date; public abstract class EncryptActivity extends BaseActivity { @@ -44,8 +45,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) { @@ -66,17 +65,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); } @@ -95,8 +88,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; @@ -110,6 +104,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; @@ -119,8 +117,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 @@ -146,9 +149,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!"); } @@ -163,8 +169,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/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index c5404094a..06c711b92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,7 +40,17 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { +public class EncryptAsymmetricFragment extends Fragment { + + public interface IAsymmetric { + + public void onSignatureKeyIdChanged(long signatureKeyId); + + public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds); + + public void onEncryptionUserIdsChanged(String[] encryptionUserIds); + } + ProviderHelper mProviderHelper; // view @@ -48,37 +58,43 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi private EncryptKeyCompletionView mEncryptKeyView; // model - private EncryptActivityInterface mEncryptInterface; + private IAsymmetric mEncryptInterface; - @Override - public void onNotifyUpdate() { - if (mSign != null) { - mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); - } +// @Override +// public void onNotifyUpdate() { +// if (mSign != null) { +// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); +// } +// } + + public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; + public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + + + /** + * Creates new instance of this fragment + */ + public static EncryptAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) { + EncryptAsymmetricFragment frag = new EncryptAsymmetricFragment(); + + Bundle args = new Bundle(); + args.putLong(ARG_SINGATURE_KEY_ID, signatureKey); + args.putLongArray(ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds); + frag.setArguments(args); + + return frag; } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { - mEncryptInterface = (EncryptActivityInterface) activity; + mEncryptInterface = (IAsymmetric) activity; } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + throw new ClassCastException(activity.toString() + " must implement IAsymmetric"); } } - private void setSignatureKeyId(long signatureKeyId) { - mEncryptInterface.setSignatureKey(signatureKeyId); - } - - private void setEncryptionKeyIds(long[] encryptionKeyIds) { - mEncryptInterface.setEncryptionKeys(encryptionKeyIds); - } - - private void setEncryptionUserIds(String[] encryptionUserIds) { - mEncryptInterface.setEncryptionUsers(encryptionUserIds); - } - /** * Inflate the layout for this fragment */ @@ -90,7 +106,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { @Override public void onKeyChanged(long masterKeyId) { - setSignatureKeyId(masterKeyId); + mEncryptInterface.onSignatureKeyIdChanged(masterKeyId); } }); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); @@ -105,7 +121,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi mProviderHelper = new ProviderHelper(getActivity()); // preselect keys given - preselectKeys(); + long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID); + long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); + preselectKeys(signatureKeyId, encryptionKeyIds); mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() { @Override @@ -127,24 +145,20 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi /** * If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those! */ - private void preselectKeys() { - // TODO all of this works under the assumption that the first suitable subkey is always used! - // not sure if we need to distinguish between different subkeys here? - long signatureKey = mEncryptInterface.getSignatureKey(); - if (signatureKey != Constants.key.none) { + private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) { + if (signatureKeyId != Constants.key.none) { try { CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(signatureKey)); + KeyRings.buildUnifiedKeyRingUri(signatureKeyId)); if (keyring.hasAnySecret()) { - setSignatureKeyId(keyring.getMasterKeyId()); - mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); + mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId()); + mSign.setSelectedKeyId(signatureKeyId); } } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } } - long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys(); if (encryptionKeyIds != null) { for (long preselectedId : encryptionKeyIds) { try { @@ -176,7 +190,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi for (int i = 0; i < keyIds.size(); i++) { keyIdsArr[i] = iterator.next(); } - setEncryptionKeyIds(keyIdsArr); - setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); + mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr); + mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()])); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index a498d0763..a6fad8881 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -37,10 +37,6 @@ import java.util.regex.Matcher; public class EncryptDecryptOverviewFragment extends Fragment { - View mEncryptFile; - View mEncryptText; - View mDecryptFile; - View mDecryptFromClipboard; View mClipboardIcon; @Override @@ -53,10 +49,10 @@ public class EncryptDecryptOverviewFragment extends Fragment { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false); - mEncryptFile = view.findViewById(R.id.encrypt_files); - mEncryptText = view.findViewById(R.id.encrypt_text); - mDecryptFile = view.findViewById(R.id.decrypt_files); - mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard); + View mEncryptFile = view.findViewById(R.id.encrypt_files); + View mEncryptText = view.findViewById(R.id.encrypt_text); + View mDecryptFile = view.findViewById(R.id.decrypt_files); + View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard); mClipboardIcon = view.findViewById(R.id.clipboard_icon); mEncryptFile.setOnClickListener(new View.OnClickListener() { 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 fe9b05226..269fdde8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> * * This program is free software: you can redistribute it and/or modify @@ -22,28 +22,19 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.view.Menu; -import android.view.MenuItem; +import android.support.v4.app.FragmentTransaction; -import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpConstants; -import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Passphrase; -import org.sufficientlysecure.keychain.util.ShareHelper; import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -public class EncryptFilesActivity extends EncryptActivity implements EncryptActivityInterface { +public class EncryptFilesActivity extends BaseActivity implements + EncryptAsymmetricFragment.IAsymmetric, EncryptSymmetricFragment.ISymmetric, + EncryptFilesFragment.IMode { /* Intents */ public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA; @@ -55,302 +46,15 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID"; public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS"; - // view - private int mCurrentMode = MODE_ASYMMETRIC; - - // tabs - private static final int MODE_ASYMMETRIC = 0; - private static final int MODE_SYMMETRIC = 1; - - // model used by fragments - private boolean mUseArmor = false; - private boolean mUseCompression = true; - private boolean mDeleteAfterEncrypt = false; - private boolean mShareAfterEncrypt = false; - private boolean mEncryptFilenames = true; - private boolean mHiddenRecipients = false; - - private long mEncryptionKeyIds[] = null; - private String mEncryptionUserIds[] = null; - private long mSigningKeyId = Constants.key.none; - private Passphrase mPassphrase = new Passphrase(); - - private ArrayList<Uri> mInputUris; - private ArrayList<Uri> mOutputUris; - private String mMessage = ""; - - public boolean isModeSymmetric() { - return MODE_SYMMETRIC == mCurrentMode; - } - - @Override - public boolean isUseArmor() { - return mUseArmor; - } - - @Override - public boolean isUseCompression() { - return mUseCompression; - } - - @Override - public boolean isEncryptFilenames() { - return mEncryptFilenames; - } - - @Override - public boolean isHiddenRecipients() { - return mHiddenRecipients; - } - - @Override - public long getSignatureKey() { - return mSigningKeyId; - } - - @Override - public long[] getEncryptionKeys() { - return mEncryptionKeyIds; - } - - @Override - public String[] getEncryptionUsers() { - return mEncryptionUserIds; - } - - @Override - public void setSignatureKey(long signatureKey) { - mSigningKeyId = signatureKey; - notifyUpdate(); - } - - @Override - public void setEncryptionKeys(long[] encryptionKeys) { - mEncryptionKeyIds = encryptionKeys; - notifyUpdate(); - } - - @Override - public void setEncryptionUsers(String[] encryptionUsers) { - mEncryptionUserIds = encryptionUsers; - notifyUpdate(); - } - - @Override - public void setPassphrase(Passphrase passphrase) { - mPassphrase = passphrase; - } - - @Override - public ArrayList<Uri> getInputUris() { - if (mInputUris == null) mInputUris = new ArrayList<>(); - return mInputUris; - } - - @Override - public ArrayList<Uri> getOutputUris() { - if (mOutputUris == null) mOutputUris = new ArrayList<>(); - return mOutputUris; - } - - @Override - public void setInputUris(ArrayList<Uri> uris) { - mInputUris = uris; - notifyUpdate(); - } - - @Override - public void setOutputUris(ArrayList<Uri> uris) { - mOutputUris = uris; - notifyUpdate(); - } - - @Override - public String getMessage() { - return mMessage; - } - - @Override - public void setMessage(String message) { - mMessage = message; - } - - @Override - public void notifyUpdate() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof EncryptActivityInterface.UpdateListener) { - ((UpdateListener) fragment).onNotifyUpdate(); - } - } - } - - @Override - public void startEncrypt(boolean share) { - mShareAfterEncrypt = share; - startEncrypt(); - } - - @Override - public void onEncryptSuccess(final SignEncryptResult result) { - if (mDeleteAfterEncrypt) { - final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]); - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris); - deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { - - @Override - public void onDeleted() { - if (mShareAfterEncrypt) { - // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt()); - } else { - // Save encrypted file - result.createNotify(EncryptFilesActivity.this).show(); - } - } - - }); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - - mInputUris.clear(); - notifyUpdate(); - } else { - if (mShareAfterEncrypt) { - // Share encrypted message/file - startActivity(sendWithChooserExcludingEncrypt()); - } else { - // Save encrypted file - result.createNotify(EncryptFilesActivity.this).show(); - } - } - } - - @Override - protected SignEncryptParcel createEncryptBundle() { - // fill values for this action - SignEncryptParcel data = new SignEncryptParcel(); - - data.addInputUris(mInputUris); - data.addOutputUris(mOutputUris); - - if (mUseCompression) { - data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); - } else { - data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); - } - data.setHiddenRecipients(mHiddenRecipients); - data.setEnableAsciiArmorOutput(mUseArmor); - data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); - data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); - - if (isModeSymmetric()) { - Log.d(Constants.TAG, "Symmetric encryption enabled!"); - Passphrase passphrase = mPassphrase; - if (passphrase.isEmpty()) { - passphrase = null; - } - data.setSymmetricPassphrase(passphrase); - } else { - data.setEncryptionMasterKeyIds(mEncryptionKeyIds); - data.setSignatureMasterKeyId(mSigningKeyId); - data.setSignaturePassphrase(mSigningKeyPassphrase); - data.setNfcState(mNfcHash, mNfcTimestamp); - } - return data; - } - - /** - * Create Intent Chooser but exclude OK's EncryptActivity. - */ - private Intent sendWithChooserExcludingEncrypt() { - Intent prototype = createSendIntent(); - String title = getString(R.string.title_share_file); - - // we don't want to encrypt the encrypted, no inception ;) - String[] blacklist = new String[]{ - Constants.PACKAGE_NAME + ".ui.EncryptFileActivity", - "org.thialfihar.android.apg.ui.EncryptActivity" - }; - - return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist); - } - - private Intent createSendIntent() { - Intent sendIntent; - // file - if (mOutputUris.size() == 1) { - sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0)); - } else { - sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); - sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); - } - sendIntent.setType(Constants.ENCRYPTED_FILES_MIME); - - if (!isModeSymmetric() && mEncryptionUserIds != null) { - Set<String> users = new HashSet<>(); - for (String user : mEncryptionUserIds) { - KeyRing.UserId userId = KeyRing.splitUserId(user); - if (userId.email != null) { - users.add(userId.email); - } - } - sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); - } - return sendIntent; - } - - protected boolean inputIsValid() { - // file checks - - if (mInputUris.isEmpty()) { - Notify.create(this, R.string.no_file_selected, Notify.Style.ERROR) - .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment)); - return false; - } else if (mInputUris.size() > 1 && !mShareAfterEncrypt) { - // This should be impossible... - return false; - } else if (mInputUris.size() != mOutputUris.size()) { - // This as well - return false; - } - - if (isModeSymmetric()) { - // symmetric encryption checks - - if (mPassphrase == null) { - Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR) - .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment)); - return false; - } - if (mPassphrase.isEmpty()) { - Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) - .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment)); - return false; - } - - } else { - // asymmetric encryption checks - - boolean gotEncryptionKeys = (mEncryptionKeyIds != null - && mEncryptionKeyIds.length > 0); - - // Files must be encrypted, only text can be signed-only right now - if (!gotEncryptionKeys) { - Notify.create(this, R.string.select_encryption_key, Notify.Style.ERROR) - .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment)); - return false; - } - } - return true; - } + Fragment mModeFragment; + EncryptFilesFragment mEncryptFragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Handle intent actions - handleActions(getIntent()); - updateModeFragment(); + handleActions(getIntent(), savedInstanceState); } @Override @@ -358,73 +62,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi setContentView(R.layout.encrypt_files_activity); } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.encrypt_file_activity, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.isCheckable()) { - item.setChecked(!item.isChecked()); - } - switch (item.getItemId()) { - case R.id.check_use_symmetric: { - mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC; - updateModeFragment(); - notifyUpdate(); - break; - } - case R.id.check_use_armor: { - mUseArmor = item.isChecked(); - notifyUpdate(); - break; - } - case R.id.check_delete_after_encrypt: { - mDeleteAfterEncrypt = item.isChecked(); - notifyUpdate(); - break; - } - case R.id.check_enable_compression: { - mUseCompression = item.isChecked(); - notifyUpdate(); - break; - } - case R.id.check_encrypt_filenames: { - mEncryptFilenames = item.isChecked(); - notifyUpdate(); - break; - } -// case R.id.check_hidden_recipients: { -// mHiddenRecipients = item.isChecked(); -// notifyUpdate(); -// break; -// } - default: { - return super.onOptionsItemSelected(item); - } - } - return true; - } - - private void updateModeFragment() { - getSupportFragmentManager().beginTransaction() - .replace(R.id.encrypt_pager_mode, - mCurrentMode == MODE_SYMMETRIC - ? new EncryptSymmetricFragment() - : new EncryptAsymmetricFragment() - ) - .commitAllowingStateLoss(); - getSupportFragmentManager().executePendingTransactions(); - } - /** * Handles all actions with this intent - * - * @param intent */ - private void handleActions(Intent intent) { + private void handleActions(Intent intent, Bundle savedInstanceState) { String action = intent.getAction(); Bundle extras = intent.getExtras(); String type = intent.getType(); @@ -453,14 +94,56 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } - mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false); + long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); + long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); + boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false); + + if (savedInstanceState == null) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + + mModeFragment = EncryptAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds); + transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode"); + + mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor); + transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files"); + + transaction.commit(); + + getSupportFragmentManager().executePendingTransactions(); + } + } + + @Override + public void onModeChanged(boolean symmetric) { + // switch fragments + getSupportFragmentManager().beginTransaction() + .replace(R.id.encrypt_mode_container, + symmetric + ? EncryptSymmetricFragment.newInstance() + : EncryptAsymmetricFragment.newInstance(0, null) + ) + .commitAllowingStateLoss(); + getSupportFragmentManager().executePendingTransactions(); + } - // preselect keys given by intent - mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); - mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); + @Override + public void onSignatureKeyIdChanged(long signatureKeyId) { + mEncryptFragment.setSigningKeyId(signatureKeyId); + } + + @Override + public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) { + mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds); + } - // Save uris - mInputUris = uris; + @Override + public void onEncryptionUserIdsChanged(String[] encryptionUserIds) { + mEncryptFragment.setEncryptionUserIds(encryptionUserIds); + } + + @Override + public void onPassphraseChanged(Passphrase passphrase) { + mEncryptFragment.setPassphrase(passphrase); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 4ba76d8ea..771800245 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -19,14 +19,18 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.Activity; +import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.Fragment; +import android.os.Message; +import android.os.Messenger; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -35,39 +39,108 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpConstants; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.ShareHelper; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Set; -public class EncryptFilesFragment extends Fragment implements EncryptActivityInterface.UpdateListener { - public static final String ARG_URIS = "uris"; +public class EncryptFilesFragment extends CryptoOperationFragment { private static final int REQUEST_CODE_INPUT = 0x00007003; private static final int REQUEST_CODE_OUTPUT = 0x00007007; - private EncryptActivityInterface mEncryptInterface; + public interface IMode { + + public void onModeChanged(boolean symmetric); + } + + private IMode mModeInterface; + + // model used by fragments + private boolean mSymmetricMode = true; + private boolean mUseArmor = false; + private boolean mUseCompression = true; + private boolean mDeleteAfterEncrypt = false; + private boolean mShareAfterEncrypt = false; + private boolean mEncryptFilenames = true; + private boolean mHiddenRecipients = false; + + private long mEncryptionKeyIds[] = null; + private String mEncryptionUserIds[] = null; + private long mSigningKeyId = Constants.key.none; + private Passphrase mPassphrase = new Passphrase(); + + private ArrayList<Uri> mInputUris = new ArrayList<Uri>(); + private ArrayList<Uri> mOutputUris = new ArrayList<Uri>(); - // view - private View mAddView; private ListView mSelectedFiles; private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter(); private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>(); + + public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor"; + public static final String ARG_URIS = "uris"; + + + /** + * Creates new instance of this fragment + */ + public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) { + EncryptFilesFragment frag = new EncryptFilesFragment(); + + Bundle args = new Bundle(); + args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor); + args.putParcelableArrayList(ARG_URIS, uris); + frag.setArguments(args); + + return frag; + } + + public void setEncryptionKeyIds(long[] encryptionKeyIds) { + mEncryptionKeyIds = encryptionKeyIds; + } + + public void setEncryptionUserIds(String[] encryptionUserIds) { + mEncryptionUserIds = encryptionUserIds; + } + + public void setSigningKeyId(long signingKeyId) { + mSigningKeyId = signingKeyId; + } + + public void setPassphrase(Passphrase passphrase) { + mPassphrase = passphrase; + } + @Override public void onAttach(Activity activity) { super.onAttach(activity); try { - mEncryptInterface = (EncryptActivityInterface) activity; + mModeInterface = (IMode) activity; } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + throw new ClassCastException(activity.toString() + " must be IMode"); } } @@ -78,15 +151,15 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false); - mAddView = inflater.inflate(R.layout.file_list_entry_add, null); - mAddView.setOnClickListener(new View.OnClickListener() { + View addView = inflater.inflate(R.layout.file_list_entry_add, null); + addView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addInputUri(); } }); mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list); - mSelectedFiles.addFooterView(mAddView); + mSelectedFiles.addFooterView(addView); mSelectedFiles.setAdapter(mAdapter); return view; @@ -95,16 +168,18 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setHasOptionsMenu(true); + + mInputUris = getArguments().getParcelableArrayList(ARG_URIS); + mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR); } private void addInputUri() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT); } else { - FileHelper.openFile(EncryptFilesFragment.this, mEncryptInterface.getInputUris().isEmpty() ? - null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1), + FileHelper.openFile(EncryptFilesFragment.this, mInputUris.isEmpty() ? + null : mInputUris.get(mInputUris.size() - 1), "*/*", REQUEST_CODE_INPUT); } } @@ -114,32 +189,30 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt return; } - if (mEncryptInterface.getInputUris().contains(inputUri)) { + if (mInputUris.contains(inputUri)) { Notify.create(getActivity(), getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), Notify.Style.ERROR).show(this); return; } - mEncryptInterface.getInputUris().add(inputUri); - mEncryptInterface.notifyUpdate(); + mInputUris.add(inputUri); mSelectedFiles.requestFocus(); } private void delInputUri(int position) { - mEncryptInterface.getInputUris().remove(position); - mEncryptInterface.notifyUpdate(); + mInputUris.remove(position); mSelectedFiles.requestFocus(); } private void showOutputFileDialog() { - if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) { + if (mInputUris.size() > 1 || mInputUris.isEmpty()) { throw new IllegalStateException(); } - Uri inputUri = mEncryptInterface.getInputUris().get(0); + Uri inputUri = mInputUris.get(0); String targetName = - (mEncryptInterface.isEncryptFilenames() ? "1" : FileHelper.getFilename(getActivity(), inputUri)) - + (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), inputUri)) + + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { File file = new File(inputUri.getPath()); File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; @@ -152,23 +225,23 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt } private void encryptClicked(boolean share) { - if (mEncryptInterface.getInputUris().isEmpty()) { + if (mInputUris.isEmpty()) { Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this); return; } if (share) { - mEncryptInterface.getOutputUris().clear(); + mOutputUris.clear(); int filenameCounter = 1; - for (Uri uri : mEncryptInterface.getInputUris()) { + for (Uri uri : mInputUris) { String targetName = - (mEncryptInterface.isEncryptFilenames() ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri)) - + (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); - mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName)); + (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri)) + + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName)); filenameCounter++; } - mEncryptInterface.startEncrypt(true); + startEncrypt(true); } else { - if (mEncryptInterface.getInputUris().size() > 1) { + if (mInputUris.size() > 1) { Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this); return; } @@ -189,7 +262,16 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt } @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.encrypt_file_activity, menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.isCheckable()) { + item.setChecked(!item.isChecked()); + } switch (item.getItemId()) { case R.id.encrypt_save: { encryptClicked(false); @@ -199,6 +281,36 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt encryptClicked(true); break; } + case R.id.check_use_symmetric: { + mSymmetricMode = item.isChecked(); + mModeInterface.onModeChanged(mSymmetricMode); + break; + } + case R.id.check_use_armor: { + mUseArmor = item.isChecked(); +// notifyUpdate(); + break; + } + case R.id.check_delete_after_encrypt: { + mDeleteAfterEncrypt = item.isChecked(); +// notifyUpdate(); + break; + } + case R.id.check_enable_compression: { + mUseCompression = item.isChecked(); +// onNotifyUpdate(); + break; + } + case R.id.check_encrypt_filenames: { + mEncryptFilenames = item.isChecked(); +// onNotifyUpdate(); + break; + } +// case R.id.check_hidden_recipients: { +// mHiddenRecipients = item.isChecked(); +// notifyUpdate(); +// break; +// } default: { return super.onOptionsItemSelected(item); } @@ -206,6 +318,251 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt return true; } + protected boolean inputIsValid() { + // file checks + + if (mInputUris.isEmpty()) { + Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR) + .show(this); + return false; + } else if (mInputUris.size() > 1 && !mShareAfterEncrypt) { + // This should be impossible... + return false; + } else if (mInputUris.size() != mOutputUris.size()) { + // This as well + return false; + } + + if (mSymmetricMode) { + // symmetric encryption checks + + if (mPassphrase == null) { + Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR) + .show(this); + return false; + } + if (mPassphrase.isEmpty()) { + Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) + .show(this); + return false; + } + + } else { + // asymmetric encryption checks + + boolean gotEncryptionKeys = (mEncryptionKeyIds != null + && mEncryptionKeyIds.length > 0); + + // Files must be encrypted, only text can be signed-only right now + if (!gotEncryptionKeys) { + Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR) + .show(this); + return false; + } + } + return true; + } + + public void startEncrypt(boolean share) { + mShareAfterEncrypt = share; + startEncrypt(); + } + + public void onEncryptSuccess(final SignEncryptResult result) { + if (mDeleteAfterEncrypt) { + final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]); + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris); + deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { + + @Override + public void onDeleted() { + if (mShareAfterEncrypt) { + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt()); + } else { + // Save encrypted file + result.createNotify(getActivity()).show(); + } + } + + }); + deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + + mInputUris.clear(); + onNotifyUpdate(); + } else { + if (mShareAfterEncrypt) { + // Share encrypted message/file + startActivity(sendWithChooserExcludingEncrypt()); + } else { + // Save encrypted file + result.createNotify(getActivity()).show(); + } + } + } + + protected SignEncryptParcel createEncryptBundle() { + // fill values for this action + SignEncryptParcel data = new SignEncryptParcel(); + + data.addInputUris(mInputUris); + data.addOutputUris(mOutputUris); + + if (mUseCompression) { + data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); + } else { + data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); + } + data.setHiddenRecipients(mHiddenRecipients); + data.setEnableAsciiArmorOutput(mUseArmor); + data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); + data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); + + if (mSymmetricMode) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + Passphrase passphrase = mPassphrase; + if (passphrase.isEmpty()) { + passphrase = null; + } + data.setSymmetricPassphrase(passphrase); + } else { + data.setEncryptionMasterKeyIds(mEncryptionKeyIds); + data.setSignatureMasterKeyId(mSigningKeyId); +// data.setSignaturePassphrase(mSigningKeyPassphrase); + } + return data; + } + + /** + * Create Intent Chooser but exclude OK's EncryptActivity. + */ + private Intent sendWithChooserExcludingEncrypt() { + Intent prototype = createSendIntent(); + String title = getString(R.string.title_share_file); + + // we don't want to encrypt the encrypted, no inception ;) + String[] blacklist = new String[]{ + Constants.PACKAGE_NAME + ".ui.EncryptFileActivity", + "org.thialfihar.android.apg.ui.EncryptActivity" + }; + + return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist); + } + + private Intent createSendIntent() { + Intent sendIntent; + // file + if (mOutputUris.size() == 1) { + sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0)); + } else { + sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); + } + sendIntent.setType(Constants.ENCRYPTED_FILES_MIME); + + if (!mSymmetricMode && mEncryptionUserIds != null) { + Set<String> users = new HashSet<>(); + for (String user : mEncryptionUserIds) { + KeyRing.UserId userId = KeyRing.splitUserId(user); + if (userId.email != null) { + users.add(userId.email); + } + } + sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); + } + return sendIntent; + } + + public void startEncrypt() { + cryptoOperation(new CryptoInputParcel()); + } + + // public void startEncrypt(CryptoInputParcel cryptoInput) { + @Override + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + + if (!inputIsValid()) { + // Notify was created by inputIsValid. + return; + } + + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(getActivity(), 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, input); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + ServiceProgressHandler serviceHandler = new ServiceProgressHandler( + getActivity(), + getString(R.string.progress_encrypting), + ProgressDialog.STYLE_HORIZONTAL, + true, + ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + // handle pending messages + if (handlePendingMessage(message)) { + return; + } + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + SignEncryptResult result = + message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT); + +// PgpSignEncryptResult pgpResult = result.getPending(); +// +// if (pgpResult != null && pgpResult.isPending()) { +// if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) == +// PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) { +// startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); +// } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == +// PgpSignEncryptResult.RESULT_PENDING_NFC) { +// +// RequiredInputParcel parcel = RequiredInputParcel.createNfcSignOperation( +// pgpResult.getNfcHash(), +// pgpResult.getNfcAlgo(), +// input.getSignatureTime()); +// startNfcSign(pgpResult.getNfcKeyId(), parcel); +// +// } else { +// throw new RuntimeException("Unhandled pending result!"); +// } +// return; +// } + + if (result.success()) { + onEncryptSuccess(result); + } else { + result.createNotify(getActivity()).show(); + } + + // no matter the result, reset parameters +// mSigningKeyPassphrase = null; + } + } + }; + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(serviceHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + serviceHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -220,10 +577,10 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { - mEncryptInterface.getOutputUris().clear(); - mEncryptInterface.getOutputUris().add(data.getData()); - mEncryptInterface.notifyUpdate(); - mEncryptInterface.startEncrypt(false); + mOutputUris.clear(); + mOutputUris.add(data.getData()); + onNotifyUpdate(); + startEncrypt(false); } return; } @@ -236,11 +593,10 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt } } - @Override public void onNotifyUpdate() { // Clear cache if needed for (Uri uri : new HashSet<>(thumbnailCache.keySet())) { - if (!mEncryptInterface.getInputUris().contains(uri)) { + if (!mInputUris.contains(uri)) { thumbnailCache.remove(uri); } } @@ -251,12 +607,12 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt private class SelectedFilesAdapter extends BaseAdapter { @Override public int getCount() { - return mEncryptInterface.getInputUris().size(); + return mInputUris.size(); } @Override public Object getItem(int position) { - return mEncryptInterface.getInputUris().get(position); + return mInputUris.get(position); } @Override @@ -266,7 +622,7 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt @Override public View getView(final int position, View convertView, ViewGroup parent) { - Uri inputUri = mEncryptInterface.getInputUris().get(position); + Uri inputUri = mInputUris.get(position); View view; if (convertView == null) { view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java index 36b3c08f9..22e116a42 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,20 +30,37 @@ import android.widget.EditText; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Passphrase; -public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { +public class EncryptSymmetricFragment extends Fragment { - EncryptActivityInterface mEncryptInterface; + public interface ISymmetric { + + public void onPassphraseChanged(Passphrase passphrase); + } + + private ISymmetric mEncryptInterface; private EditText mPassphrase; private EditText mPassphraseAgain; + /** + * Creates new instance of this fragment + */ + public static EncryptSymmetricFragment newInstance() { + EncryptSymmetricFragment frag = new EncryptSymmetricFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + @Override public void onAttach(Activity activity) { super.onAttach(activity); try { - mEncryptInterface = (EncryptActivityInterface) activity; + mEncryptInterface = (ISymmetric) activity; } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + throw new ClassCastException(activity.toString() + " must implement ISymmetric"); } } @@ -74,9 +91,9 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit p1.removeFromMemory(); p2.removeFromMemory(); if (passesEquals) { - mEncryptInterface.setPassphrase(new Passphrase(mPassphrase.getText())); + mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText())); } else { - mEncryptInterface.setPassphrase(null); + mEncryptInterface.onPassphraseChanged(null); } } }; @@ -86,8 +103,4 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit return view; } - @Override - public void onNotifyUpdate() { - - } } 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 dd09e62c3..56f0b198e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; @@ -43,7 +42,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Set; -public class EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface { +public class EncryptTextActivity extends EncryptActivity { /* Intents */ public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT; @@ -81,60 +80,38 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv return MODE_SYMMETRIC == mCurrentMode; } - @Override - public boolean isUseArmor() { - return true; - } - - @Override - public boolean isEncryptFilenames() { - return false; - } - - @Override public boolean isUseCompression() { return mUseCompression; } - @Override public boolean isHiddenRecipients() { return mHiddenRecipients; } - @Override public long getSignatureKey() { return mSigningKeyId; } - @Override public long[] getEncryptionKeys() { return mEncryptionKeyIds; } - @Override public String[] getEncryptionUsers() { return mEncryptionUserIds; } - @Override public void setSignatureKey(long signatureKey) { mSigningKeyId = signatureKey; - notifyUpdate(); } - @Override public void setEncryptionKeys(long[] encryptionKeys) { mEncryptionKeyIds = encryptionKeys; - notifyUpdate(); } - @Override public void setEncryptionUsers(String[] encryptionUsers) { mEncryptionUserIds = encryptionUsers; - notifyUpdate(); } - @Override public void setPassphrase(Passphrase passphrase) { if (mPassphrase != null) { mPassphrase.removeFromMemory(); @@ -142,50 +119,32 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv mPassphrase = passphrase; } - @Override public ArrayList<Uri> getInputUris() { if (mInputUris == null) mInputUris = new ArrayList<>(); return mInputUris; } - @Override public ArrayList<Uri> getOutputUris() { if (mOutputUris == null) mOutputUris = new ArrayList<>(); return mOutputUris; } - @Override public void setInputUris(ArrayList<Uri> uris) { mInputUris = uris; - notifyUpdate(); } - @Override public void setOutputUris(ArrayList<Uri> uris) { mOutputUris = uris; - notifyUpdate(); } - @Override public String getMessage() { return mMessage; } - @Override public void setMessage(String message) { mMessage = message; } - @Override - public void notifyUpdate() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof UpdateListener) { - ((UpdateListener) fragment).onNotifyUpdate(); - } - } - } - - @Override public void startEncrypt(boolean share) { mShareAfterEncrypt = share; startEncrypt(); @@ -237,7 +196,6 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv data.setEncryptionMasterKeyIds(mEncryptionKeyIds); data.setSignatureMasterKeyId(mSigningKeyId); data.setSignaturePassphrase(mSigningKeyPassphrase); - data.setNfcState(mNfcHash, mNfcTimestamp); } return data; } @@ -349,7 +307,7 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv private void updateModeFragment() { getSupportFragmentManager().beginTransaction() - .replace(R.id.encrypt_pager_mode, + .replace(R.id.encrypt_mode, mCurrentMode == MODE_SYMMETRIC ? new EncryptSymmetricFragment() : new EncryptAsymmetricFragment() @@ -367,12 +325,10 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv case R.id.check_use_symmetric: { mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC; updateModeFragment(); - notifyUpdate(); break; } case R.id.check_enable_compression: { mUseCompression = item.isChecked(); - notifyUpdate(); break; } // case R.id.check_hidden_recipients: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index 6c3336547..c757c8e88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -26,6 +26,8 @@ import com.astuetz.PagerSlidingTabStrip; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + public class HelpActivity extends BaseActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index dc4a2eb10..7fe5be793 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.service.CloudImportService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; @@ -47,7 +48,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize import java.io.IOException; import java.util.ArrayList; -public class ImportKeysActivity extends BaseActivity { +public class ImportKeysActivity extends BaseNfcActivity { + public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 6a6140892..b9fdbea5c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -53,9 +53,11 @@ import java.util.List; public class ImportKeysListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { + private static final String ARG_DATA_URI = "uri"; private static final String ARG_BYTES = "bytes"; - private static final String ARG_SERVER_QUERY = "query"; + public static final String ARG_SERVER_QUERY = "query"; + public static final String ARG_NON_INTERACTIVE = "non_interactive"; private Activity mActivity; private ImportKeysAdapter mAdapter; @@ -66,6 +68,7 @@ public class ImportKeysListFragment extends ListFragment implements private static final int LOADER_ID_CLOUD = 1; private LongSparseArray<ParcelableKeyRing> mCachedKeyData; + private boolean mNonInteractive; public LoaderState getLoaderState() { return mLoaderState; @@ -118,16 +121,19 @@ public class ImportKeysListFragment extends ListFragment implements } - /** - * Creates new instance of this fragment - */ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) { + return newInstance(bytes, dataUri, serverQuery, false); + } + + public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, + String serverQuery, boolean nonInteractive) { ImportKeysListFragment frag = new ImportKeysListFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_BYTES, bytes); args.putParcelable(ARG_DATA_URI, dataUri); args.putString(ARG_SERVER_QUERY, serverQuery); + args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); frag.setArguments(args); @@ -173,9 +179,11 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter = new ImportKeysAdapter(mActivity); setListAdapter(mAdapter); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - byte[] bytes = getArguments().getByteArray(ARG_BYTES); - String query = getArguments().getString(ARG_SERVER_QUERY); + Bundle args = getArguments(); + Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null; + byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null; + String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null; + mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false; if (dataUri != null || bytes != null) { mLoaderState = new BytesLoaderState(bytes, dataUri); @@ -203,6 +211,10 @@ public class ImportKeysListFragment extends ListFragment implements public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); + if (mNonInteractive) { + return; + } + // Select checkbox! // Update underlying data and notify adapter of change. The adapter will // update the view automatically. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java index 0de7bb391..df325d31d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -22,6 +22,8 @@ import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + public class LogDisplayActivity extends BaseActivity { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java index 7311f4879..57acf3e93 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java @@ -22,6 +22,7 @@ import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java index 0ccb206d1..a1affbc39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java @@ -21,6 +21,7 @@ import android.widget.Toast; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java new file mode 100644 index 000000000..511183b04 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -0,0 +1,114 @@ +/** + * 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.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.view.WindowManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.io.IOException; + +/** + * 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 BaseNfcActivity { + + public static final String EXTRA_REQUIRED_INPUT = "required_input"; + + public static final String RESULT_DATA = "result_data"; + + RequiredInputParcel mRequiredInput; + + @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(); + Bundle data = intent.getExtras(); + + mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); + + // obtain passphrase for this subkey + obtainYubikeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); + } + + @Override + protected void initLayout() { + setContentView(R.layout.nfc_activity); + } + + @Override + protected void onNfcPerform() throws IOException { + + CryptoInputParcel resultData = new CryptoInputParcel(mRequiredInput.mSignatureTime); + + switch (mRequiredInput.mType) { + + case NFC_DECRYPT: + for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { + byte[] hash = mRequiredInput.mInputHashes[i]; + byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); + resultData.addCryptoData(hash, decryptedSessionKey); + } + break; + + case NFC_SIGN: + for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { + byte[] hash = mRequiredInput.mInputHashes[i]; + int algo = mRequiredInput.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(NfcOperationActivity.RESULT_DATA, resultData); + setResult(RESULT_OK, result); + finish(); + + } + + @Override + public void handlePinError() { + + // avoid a loop + Preferences prefs = Preferences.getPreferences(this); + if (prefs.useDefaultYubikeyPin()) { + toast(getString(R.string.error_pin_nodefault)); + setResult(RESULT_CANCELED); + finish(); + return; + } + + // clear (invalid) passphrase + PassphraseCacheService.clearCachedPassphrase( + this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); + + obtainYubikeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); + + } +} 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/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java index 43af07bbe..d4858ee5d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index c58a945d3..aa3c36d11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index 080dc2495..9f2e46b38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.widget.Editor; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index c518cbcdb..5c8e6bb5d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index a80503591..8d876ba69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 0c2d8693f..b063df2fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -35,6 +35,7 @@ import android.os.Message; import android.os.Messenger; import android.provider.ContactsContract; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -60,17 +61,22 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; @@ -78,15 +84,21 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.Preferences; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -public class ViewKeyActivity extends BaseActivity implements +public class ViewKeyActivity extends BaseNfcActivity implements LoaderManager.LoaderCallbacks<Cursor> { + public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; + public static final String EXTRA_NFC_AID = "nfc_aid"; + public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + static final int REQUEST_QR_FINGERPRINT = 1; static final int REQUEST_DELETE = 2; static final int REQUEST_EXPORT = 3; + public static final String EXTRA_DISPLAY_RESULT = "display_result"; ExportHelper mExportHelper; ProviderHelper mProviderHelper; @@ -106,6 +118,8 @@ public class ViewKeyActivity extends BaseActivity implements private ImageView mQrCode; private CardView mQrCodeLayout; + private String mQrCodeLoaded; + // NFC private NfcHelper mNfcHelper; @@ -255,7 +269,21 @@ public class ViewKeyActivity extends BaseActivity implements mNfcHelper = new NfcHelper(this, mProviderHelper); mNfcHelper.initNfc(mDataUri); + if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) { + OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT); + result.createNotify(this).show(); + } + startFragment(savedInstanceState, mDataUri); + + if (savedInstanceState == null && getIntent().hasExtra(EXTRA_NFC_AID)) { + Intent intent = getIntent(); + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + showYubikeyFragment(nfcFingerprints, nfcUserId, nfcAid); + } + } @Override @@ -516,6 +544,72 @@ public class ViewKeyActivity extends BaseActivity implements } } + @Override + protected void onNfcPerform() throws IOException { + + final byte[] nfcFingerprints = nfcGetFingerprints(); + final String nfcUserId = nfcGetUserId(); + final byte[] nfcAid = nfcGetAid(); + + String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints); + final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); + + if (!mFingerprint.equals(fp)) { + try { + CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId); + ring.getMasterKeyId(); + + Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG, + Style.WARN, new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + ViewKeyActivity.this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + startActivity(intent); + finish(); + } + }, R.string.snack_yubikey_view).show(); + return; + + } catch (PgpKeyNotFoundException e) { + Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG, + Style.WARN, new ActionListener() { + @Override + public void onAction() { + Intent intent = new Intent( + ViewKeyActivity.this, CreateKeyActivity.class); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + startActivity(intent); + finish(); + } + }, R.string.snack_yubikey_import).show(); + return; + } + } + + showYubikeyFragment(nfcFingerprints, nfcUserId, nfcAid); + + } + + public void showYubikeyFragment(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) { + ViewKeyYubikeyFragment frag = ViewKeyYubikeyFragment.newInstance( + nfcFingerprints, nfcUserId, nfcAid); + + FragmentManager manager = getSupportFragmentManager(); + + manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE); + manager.beginTransaction() + .addToBackStack("yubikey") + .replace(R.id.view_key_fragment, frag) + .commit(); + } + private void encrypt(Uri dataUri, boolean text) { // If there is no encryption key, don't bother. if (!mHasEncrypt) { @@ -647,6 +741,7 @@ public class ViewKeyActivity extends BaseActivity implements } protected void onPostExecute(Bitmap qrCode) { + mQrCodeLoaded = fingerprint; // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. Bitmap scaled = Bitmap.createScaledBitmap(qrCode, @@ -724,7 +819,6 @@ public class ViewKeyActivity extends BaseActivity implements mName.setText(R.string.user_id_no_name); } - String oldFingerprint = mFingerprint; mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); @@ -788,7 +882,7 @@ public class ViewKeyActivity extends BaseActivity implements mStatusImage.setVisibility(View.GONE); color = getResources().getColor(R.color.primary); // reload qr code only if the fingerprint changed - if (!mFingerprint.equals(oldFingerprint)) { + if (!mFingerprint.equals(mQrCodeLoaded)) { loadQrCode(mFingerprint); } photoTask.execute(mMasterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index f17d6e0fd..9e8a12c8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java new file mode 100644 index 000000000..1482b70a7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubikeyFragment.java @@ -0,0 +1,220 @@ +package org.sufficientlysecure.keychain.ui; + + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +public class ViewKeyYubikeyFragment extends Fragment + implements LoaderCallbacks<Cursor> { + + public static final String ARG_FINGERPRINT = "fingerprint"; + public static final String ARG_USER_ID = "user_id"; + public static final String ARG_CARD_AID = "aid"; + private byte[][] mFingerprints; + private String mUserId; + private byte[] mCardAid; + private long mMasterKeyId; + private Button vButton; + private TextView vStatus; + + public static ViewKeyYubikeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) { + + ViewKeyYubikeyFragment frag = new ViewKeyYubikeyFragment(); + + Bundle args = new Bundle(); + args.putByteArray(ARG_FINGERPRINT, fingerprints); + args.putString(ARG_USER_ID, userId); + args.putByteArray(ARG_CARD_AID, aid); + frag.setArguments(args); + + return frag; + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT)); + mFingerprints = new byte[buf.remaining()/40][]; + for (int i = 0; i < mFingerprints.length; i++) { + mFingerprints[i] = new byte[20]; + buf.get(mFingerprints[i]); + } + mUserId = args.getString(ARG_USER_ID); + mCardAid = args.getByteArray(ARG_CARD_AID); + + mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[0]); + + getLoaderManager().initLoader(0, null, this); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_yubikey, null); + + TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); + TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid); + + String serno = Hex.toHexString(mCardAid, 10, 4); + vSerNo.setText(getString(R.string.yubikey_serno, serno)); + + if (!mUserId.isEmpty()) { + vUserId.setText(getString(R.string.yubikey_key_holder, mUserId)); + } else { + vUserId.setText(getString(R.string.yubikey_key_holder_unset)); + } + + vButton = (Button) view.findViewById(R.id.button_bind); + vButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + promoteToSecretKey(); + } + }); + + vStatus = (TextView) view.findViewById(R.id.yubikey_status); + + return view; + } + + public void promoteToSecretKey() { + + ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + + PromoteKeyResult result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + result.createNotify(getActivity()).show(); + } + + } + }; + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + + intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING); + + Bundle data = new Bundle(); + data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId); + data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // start service with intent + getActivity().startService(intent); + + } + + public static final String[] PROJECTION = new String[]{ + Keys._ID, + Keys.KEY_ID, + Keys.RANK, + Keys.HAS_SECRET, + Keys.FINGERPRINT + }; + private static final int INDEX_KEY_ID = 1; + private static final int INDEX_RANK = 2; + private static final int INDEX_HAS_SECRET = 3; + private static final int INDEX_FINGERPRINT = 4; + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId), + PROJECTION, null, null, null); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + if (!data.moveToFirst()) { + // wut? + return; + } + + boolean allBound = true; + boolean noneBound = true; + + do { + SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET)); + byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); + Integer index = naiveIndexOf(mFingerprints, fingerprint); + if (index == null) { + continue; + } + if (keyType == SecretKeyType.DIVERT_TO_CARD) { + noneBound = false; + } else { + allBound = false; + } + } while (data.moveToNext()); + + if (allBound) { + vButton.setVisibility(View.GONE); + vStatus.setText(R.string.yubikey_status_bound); + } else { + vButton.setVisibility(View.VISIBLE); + vStatus.setText(noneBound + ? R.string.yubikey_status_unbound + : R.string.yubikey_status_partly); + } + + } + + public Integer naiveIndexOf(byte[][] haystack, byte[] needle) { + for (int i = 0; i < haystack.length; i++) { + if (Arrays.equals(needle, haystack[i])) { + return i; + } + } + return null; + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 41fa50705..07d2ef8c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -15,7 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package org.sufficientlysecure.keychain.ui; +package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.os.Bundle; @@ -63,8 +63,8 @@ public abstract class BaseActivity extends ActionBarActivity { * Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines * see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs */ - protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, - View.OnClickListener cancelOnClickListener) { + public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, + View.OnClickListener cancelOnClickListener) { setActionBarIcon(R.drawable.ic_close_white_24dp); // Inflate the custom action bar view diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java new file mode 100644 index 000000000..a8a5a1f28 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -0,0 +1,496 @@ +package org.sufficientlysecure.keychain.ui.base; + + +import java.io.IOException; +import java.nio.ByteBuffer; + +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.widget.Toast; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.Preferences; + + +public abstract class BaseNfcActivity extends BaseActivity { + + public static final int REQUEST_CODE_PASSPHRASE = 1; + + protected Passphrase mPin; + private NfcAdapter mNfcAdapter; + private IsoDep mIsoDep; + + private static final int TIMEOUT = 100000; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); + } + + } + + /** + * This activity is started as a singleTop activity. + * All new NFC Intents which are delivered to this activity are handled here + */ + @Override + public void onNewIntent(Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + handleNdefDiscoveredIntent(intent); + } catch (IOException e) { + handleNfcError(e); + } + } + } + + public void handleNfcError(IOException e) { + + Log.e(Constants.TAG, "nfc error", e); + Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); + + } + + public void handlePinError() { + toast("Wrong PIN!"); + setResult(RESULT_CANCELED); + finish(); + } + + /** + * Called when the system is about to start resuming a previous activity, + * disables NFC Foreground Dispatch + */ + public void onPause() { + super.onPause(); + Log.d(Constants.TAG, "BaseNfcActivity.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, "BaseNfcActivity.onResume"); + + enableNfcForegroundDispatch(); + } + + protected void obtainYubikeyPin(RequiredInputParcel requiredInput) { + + Preferences prefs = Preferences.getPreferences(this); + if (prefs.useDefaultYubikeyPin()) { + mPin = new Passphrase("123456"); + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(requiredInput)); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + + } + + protected void setYubikeyPin(Passphrase pin) { + mPin = pin; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: + CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_DATA); + mPin = input.getPassphrase(); + break; + + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** Handle NFC communication and return a result. + * + * This method is called by onNewIntent above upon discovery of an NFC tag. + * It handles initialization and login to the application, subsequently + * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then + * finishes the activity with an appropiate result. + * + * On general communication, see also + * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx + * + * References to pages are generally related to the OpenPGP Application + * on ISO SmartCard Systems specification. + * + */ + protected void handleNdefDiscoveredIntent(Intent intent) throws IOException { + + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + // Connect to the detected tag, setting a couple of settings + mIsoDep = IsoDep.get(detectedTag); + mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation + mIsoDep.connect(); + + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. + // See specification, page 51 + String accepted = "9000"; + + // Command APDU (page 51) for SELECT FILE command (page 29) + String opening = + "00" // CLA + + "A4" // INS + + "04" // P1 + + "00" // P2 + + "06" // Lc (number of bytes) + + "D27600012401" // Data (6 bytes) + + "00"; // Le + if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection + throw new IOException("Initialization failed!"); + } + + if (mPin != null) { + + byte[] pin = new String(mPin.getCharArray()).getBytes(); + + // Command APDU for VERIFY command (page 32) + String login = + "00" // CLA + + "20" // INS + + "00" // P1 + + "82" // P2 (PW1) + + String.format("%02x", pin.length) // Lc + + Hex.toHexString(pin); + if (!nfcCommunicate(login).equals(accepted)) { // login + handlePinError(); + return; + } + + } + + onNfcPerform(); + + mIsoDep.close(); + mIsoDep = null; + + } + + protected void onNfcPerform() throws IOException { + + final byte[] nfcFingerprints = nfcGetFingerprints(); + final String nfcUserId = nfcGetUserId(); + final byte[] nfcAid = nfcGetAid(); + + String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints); + final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); + + try { + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); + ring.getMasterKeyId(); + + Intent intent = new Intent( + BaseNfcActivity.this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + startActivity(intent); + finish(); + } catch (PgpKeyNotFoundException e) { + Intent intent = new Intent( + BaseNfcActivity.this, CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + startActivity(intent); + finish(); + } + + } + + /** Return the key id from application specific data stored on tag, or null + * if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The long key id of the requested key, or null if not found. + */ + public Long nfcGetKeyId(int idx) throws IOException { + byte[] fp = nfcGetFingerprint(idx); + if (fp == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(fp); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); + } + + /** Return fingerprints of all keys from application specific data stored + * on tag, or null if data not available. + * + * @return The fingerprints of all subkeys in a contiguous byte array. + */ + public byte[] nfcGetFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mIsoDep.transceive(Hex.decode(data)); + + Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); + Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); + + Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); + if (fptlv == null) { + return null; + } + + return fptlv.mV; + } + + /** Return the fingerprint from application specific data stored on tag, or + * null if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The fingerprint of the requested key, or null if not found. + */ + public byte[] nfcGetFingerprint(int idx) throws IOException { + byte[] data = nfcGetFingerprints(); + + // return the master key fingerprint + ByteBuffer fpbuf = ByteBuffer.wrap(data); + byte[] fp = new byte[20]; + fpbuf.position(idx * 20); + fpbuf.get(fp, 0, 20); + + return fp; + } + + public byte[] nfcGetAid() throws IOException { + + String info = "00CA004F00"; + return mIsoDep.transceive(Hex.decode(info)); + + } + + public String nfcGetUserId() throws IOException { + + String info = "00CA006500"; + return nfcGetHolderName(nfcCommunicate(info)); + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + */ + public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { + + // dsi, including Lc + String dsi; + + Log.i(Constants.TAG, "Hash: " + hashAlgo); + switch (hashAlgo) { + case HashAlgorithmTags.SHA1: + if (hash.length != 20) { + throw new IOException("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 IOException("Bad hash length (" + hash.length + ", expected 20!"); + } + dsi = "233021300906052B2403020105000414" + getHex(hash); + break; + case HashAlgorithmTags.SHA224: + if (hash.length != 28) { + throw new IOException("Bad hash length (" + hash.length + ", expected 28!"); + } + dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); + break; + case HashAlgorithmTags.SHA256: + if (hash.length != 32) { + throw new IOException("Bad hash length (" + hash.length + ", expected 32!"); + } + dsi = "333031300D060960864801650304020105000420" + getHex(hash); + break; + case HashAlgorithmTags.SHA384: + if (hash.length != 48) { + throw new IOException("Bad hash length (" + hash.length + ", expected 48!"); + } + dsi = "433041300D060960864801650304020205000430" + getHex(hash); + break; + case HashAlgorithmTags.SHA512: + if (hash.length != 64) { + throw new IOException("Bad hash length (" + hash.length + ", expected 64!"); + } + dsi = "533051300D060960864801650304020305000440" + getHex(hash); + break; + default: + throw new IOException("Not supported hash algo!"); + } + + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) + String apdu = + "002A9E9A" // CLA, INS, P1, P2 + + dsi // digital signature input + + "00"; // Le + + String response = nfcCommunicate(apdu); + + // split up response into signature and status + String status = response.substring(response.length()-4); + String signature = response.substring(0, response.length() - 4); + + // while we are getting 0x61 status codes, retrieve more data + while (status.substring(0, 2).equals("61")) { + Log.d(Constants.TAG, "requesting more data, status " + status); + // Send GET RESPONSE command + response = nfcCommunicate("00C00000" + status.substring(2)); + status = response.substring(response.length()-4); + signature += response.substring(0, response.length()-4); + } + + Log.d(Constants.TAG, "final response:" + status); + + if ( ! "9000".equals(status)) { + throw new IOException("Bad NFC response code: " + status); + } + + // Make sure the signature we received is actually the expected number of bytes long! + if (signature.length() != 256 && signature.length() != 512) { + throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); + } + + return Hex.decode(signature); + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { + String firstApdu = "102a8086fe"; + String secondApdu = "002a808603"; + String le = "00"; + + byte[] one = new byte[254]; + // leave out first byte: + System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); + + byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; + for (int i = 0; i < two.length; i++) { + two[i] = encryptedSessionKey[i + one.length + 1]; + } + + String first = nfcCommunicate(firstApdu + getHex(one)); + String second = nfcCommunicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = nfcGetDataField(second); + + Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + + return Hex.decode(decryptedSessionKey); + } + + /** + * Prints a message to the screen + * + * @param text the text which should be contained within the toast + */ + protected void toast(String text) { + Toast.makeText(this, text, Toast.LENGTH_LONG).show(); + } + + /** + * Receive new NFC Intents to this activity only by enabling foreground dispatch. + * This can only be done in onResume! + */ + public void enableNfcForegroundDispatch() { + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + Intent nfcI = new Intent(this, getClass()) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); + IntentFilter[] writeTagFilters = new IntentFilter[]{ + new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + }; + + // https://code.google.com/p/android/issues/detail?id=62918 + // maybe mNfcAdapter.enableReaderMode(); ? + try { + mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); + } catch (IllegalStateException e) { + Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e); + } + Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); + } + + /** + * Disable foreground dispatch in onPause! + */ + public void disableNfcForegroundDispatch() { + mNfcAdapter.disableForegroundDispatch(this); + Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); + } + + public String nfcGetHolderName(String name) { + String slength; + int ilength; + name = name.substring(6); + slength = name.substring(0, 2); + ilength = Integer.parseInt(slength, 16) * 2; + name = name.substring(2, ilength + 2); + name = (new String(Hex.decode(name))).replace('<', ' '); + return (name); + } + + private String nfcGetDataField(String output) { + return output.substring(0, output.length() - 4); + } + + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoDep.transceive(Hex.decode(apdu))); + } + + public static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + +} diff --git a/OpenKeychain/src/main/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/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index c5403e054..ae66b59d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; +import java.nio.ByteBuffer; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -215,7 +216,15 @@ public class KeyFormattingUtils { * @return */ public static String convertFingerprintToHex(byte[] fingerprint) { - return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH); + return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH); + } + + public static long getKeyIdFromFingerprint(byte[] fingerprint) { + ByteBuffer buf = ByteBuffer.wrap(fingerprint); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); } /** diff --git a/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png Binary files differnew file mode 100644 index 000000000..428ad6fad --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png Binary files differnew file mode 100644 index 000000000..6fb41223d --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/yubi_icon_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon.png Binary files differnew file mode 100644 index 000000000..05c21c052 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png Binary files differnew file mode 100644 index 000000000..753f22607 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/yubi_icon_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png Binary files differnew file mode 100644 index 000000000..cfa799e74 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon_24dp.png Binary files differnew file mode 100644 index 000000000..05c21c052 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/yubi_icon_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png b/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png Binary files differnew file mode 100644 index 000000000..f20f562ec --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon_24dp.png Binary files differnew file mode 100644 index 000000000..9bae15a02 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/yubi_icon_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png Binary files differnew file mode 100644 index 000000000..cfa799e74 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/yubi_icon_24dp.png diff --git a/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml index 79ffe58b1..2db147475 100644 --- a/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_start_fragment.xml @@ -50,22 +50,22 @@ android:clickable="true" style="?android:attr/borderlessButtonStyle" /> - <!--<TextView--> - <!--android:id="@+id/create_key_yubikey_button"--> - <!--android:paddingLeft="16dp"--> - <!--android:paddingRight="16dp"--> - <!--android:textAppearance="?android:attr/textAppearanceMedium"--> - <!--android:layout_width="match_parent"--> - <!--android:layout_height="wrap_content"--> - <!--android:layout_weight="1"--> - <!--android:text="@string/first_time_yubikey"--> - <!--android:textAllCaps="true"--> - <!--android:minHeight="?android:attr/listPreferredItemHeight"--> - <!--android:drawableRight="@drawable/ic_chevron_right_grey_24dp"--> - <!--android:drawablePadding="8dp"--> - <!--android:gravity="right|center_vertical"--> - <!--android:clickable="true"--> - <!--style="?android:attr/borderlessButtonStyle" />--> + <TextView + android:id="@+id/create_key_yubikey_button" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/first_time_yubikey" + android:textAllCaps="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableRight="@drawable/ic_chevron_right_grey_24dp" + android:drawablePadding="8dp" + android:gravity="right|center_vertical" + android:clickable="true" + style="?android:attr/borderlessButtonStyle" /> <TextView android:id="@+id/create_key_import_button" @@ -101,4 +101,4 @@ android:clickable="true" style="?android:attr/borderlessButtonStyle" /> </LinearLayout> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_import_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_import_fragment.xml new file mode 100644 index 000000000..e70188e49 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/create_yubikey_import_fragment.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + android:id="@+id/yubikey_status_layout" > + + <ImageView + android:layout_margin="14dp" + android:layout_width="32dp" + android:layout_height="32dp" + android:scaleType="centerCrop" + android:src="@drawable/yubi_icon"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:orientation="vertical"> + + <TextView + android:id="@+id/yubikey_serno" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="Yubikey #" + /> + + <TextView + android:id="@+id/yubikey_userid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="User ID" + /> + + <TextView + android:id="@+id/yubikey_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="Unknown key, hit next to import" + /> + + </LinearLayout> + + <ImageButton + android:id="@+id/button_search" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_search_grey_24dp" + android:layout_gravity="center_vertical" + android:background="?android:selectableItemBackground" /> + + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_marginTop="4dp" + android:layout_height="1dip" + android:layout_below="@id/yubikey_status_layout" + android:background="?android:attr/listDivider" /> + + <FrameLayout + android:id="@+id/yubikey_import_fragment" + android:layout_marginTop="8dp" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@id/yubikey_status_layout" + android:layout_above="@id/create_key_buttons" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:background="@color/holo_gray_bright" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/create_key_back_button" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:textAllCaps="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="left|center_vertical" + android:clickable="true" + style="?android:attr/borderlessButtonStyle" /> + + <TextView + android:id="@+id/create_key_next_button" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_import" + android:textAllCaps="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableRight="@drawable/ic_key_plus_grey600_24dp" + android:drawablePadding="8dp" + android:gravity="right|center_vertical" + android:clickable="true" + style="?android:attr/borderlessButtonStyle" /> + + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml new file mode 100644 index 000000000..c7f9821eb --- /dev/null +++ b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true" + android:layout_above="@+id/create_key_buttons"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginLeft="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Hold Yubikey against device dawg" + /> + + <ImageView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:src="@drawable/yubikey_phone" /> + + </LinearLayout> + + </ScrollView> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:background="@color/holo_gray_bright" + android:id="@+id/create_key_buttons"> + + <TextView + android:id="@+id/create_key_back_button" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_back" + android:textAllCaps="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableLeft="@drawable/ic_chevron_left_grey_24dp" + android:drawablePadding="8dp" + android:gravity="left|center_vertical" + android:clickable="true" + style="?android:attr/borderlessButtonStyle" /> + + <TextView + android:id="@+id/create_key_next_button" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/btn_next" + android:textAllCaps="true" + android:minHeight="?android:attr/listPreferredItemHeight" + android:drawableRight="@drawable/yubi_icon_24dp" + android:drawablePadding="16dp" + android:gravity="right|center_vertical" + android:clickable="false" + style="?android:attr/borderlessButtonStyle" /> + + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/encrypt_files_activity.xml b/OpenKeychain/src/main/res/layout/encrypt_files_activity.xml index ce8b1302c..435ea96df 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_files_activity.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_files_activity.xml @@ -23,14 +23,12 @@ <include layout="@layout/notify_area" /> <FrameLayout - android:id="@+id/encrypt_pager_mode" + android:id="@+id/encrypt_mode_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" /> + android:layout_height="wrap_content" /> - <fragment - android:id="@+id/encrypt_file_fragment" - android:name="org.sufficientlysecure.keychain.ui.EncryptFilesFragment" + <FrameLayout + android:id="@+id/encrypt_file_container" android:layout_width="match_parent" android:layout_height="match_parent" /> diff --git a/OpenKeychain/src/main/res/layout/encrypt_files_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_files_fragment.xml index 029e735b3..5831d52e1 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_files_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_files_fragment.xml @@ -1,9 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingLeft="16dp" - android:paddingRight="16dp" + android:layout_height="match_parent" android:orientation="vertical"> <ListView @@ -12,6 +10,8 @@ android:divider="@android:color/transparent" android:focusable="true" android:focusableInTouchMode="true" + android:paddingLeft="16dp" + android:paddingRight="16dp" android:layout_marginTop="8dp" android:layout_width="match_parent" android:layout_height="match_parent" /> diff --git a/OpenKeychain/src/main/res/layout/encrypt_text_activity.xml b/OpenKeychain/src/main/res/layout/encrypt_text_activity.xml index 809e64f02..dcf5bd041 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_text_activity.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_text_activity.xml @@ -23,7 +23,7 @@ <include layout="@layout/notify_area" /> <FrameLayout - android:id="@+id/encrypt_pager_mode" + android:id="@+id/encrypt_mode" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> diff --git a/OpenKeychain/src/main/res/layout/view_key_yubikey.xml b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml new file mode 100644 index 000000000..83272ef4e --- /dev/null +++ b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml @@ -0,0 +1,103 @@ +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:card_view="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <android.support.v7.widget.CardView + android:id="@+id/card_view" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:transitionName="card" + card_view:cardBackgroundColor="@android:color/white" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true" + card_view:cardCornerRadius="4dp" + android:animateLayoutChanges="true"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_yubikey"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:orientation="horizontal"> + + <ImageView + android:layout_margin="14dp" + android:layout_width="32dp" + android:layout_height="32dp" + android:scaleType="centerCrop" + android:src="@drawable/yubi_icon"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:orientation="vertical"> + + <TextView + android:id="@+id/yubikey_serno" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="Yubikey #" + /> + + <TextView + android:id="@+id/yubikey_userid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="User ID" + /> + + <TextView + android:id="@+id/yubikey_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="Key matches!" + /> + + </LinearLayout> + + </LinearLayout> + + <Button + android:id="@+id/button_bind" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right|end" + android:text="@string/button_bind_key" + android:textColor="@color/link_text_material_light" + style="?android:attr/borderlessButtonStyle" + android:visibility="gone" + /> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + </LinearLayout> + +</ScrollView> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 8c4fe2a33..f6fbd132c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -42,6 +42,7 @@ <!-- section --> <string name="section_user_ids">"Identities"</string> + <string name="section_yubikey">"Yubikey"</string> <string name="section_linked_system_contact">"Linked System Contact"</string> <string name="section_should_you_trust">"Should you trust this key?"</string> <string name="section_proof_details">Proof verification</string> @@ -885,6 +886,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> @@ -896,9 +898,11 @@ <string name="msg_mf_error_restricted">"Tried to execute restricted operation without passphrase! This is a programming error, please file a bug report!"</string> <string name="msg_mf_error_revoked_primary">"Revoked user IDs cannot be primary!"</string> <string name="msg_mf_error_null_expiry">"Expiry time cannot be "same as before" on subkey creation. This is a programming error, please file a bug report!"</string> + <string name="msg_mf_error_noop">"Nothing to do!"</string> <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_error_subkey_missing">"Tried to operate on missing subkey %s!"</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> @@ -908,8 +912,10 @@ <string name="msg_mf_passphrase_fail">"Passphrase for subkey could not be changed! (Does it have a different one from the other keys?)"</string> <string name="msg_mf_primary_replace_old">"Replacing certificate of previous primary user ID"</string> <string name="msg_mf_primary_new">"Generating new certificate for new primary user ID"</string> + <string name="msg_mf_restricted_mode">"Changing to restricted operation mode"</string> <string name="msg_mf_subkey_change">"Modifying subkey %s"</string> - <string name="msg_mf_error_subkey_missing">"Tried to operate on missing subkey %s!"</string> + <string name="msg_mf_require_divert">"Diverting to card/nfc for crypto operations"</string> + <string name="msg_mf_require_passphrase">"Passphrase required for operations"</string> <string name="msg_mf_subkey_new">"Adding new subkey of type %s"</string> <string name="msg_mf_subkey_new_id">"New subkey ID: %s"</string> <string name="msg_mf_error_past_expiry">"Expiry date cannot be in the past!"</string> @@ -969,13 +975,11 @@ <!-- Promote key --> <string name="msg_pr">"Promoting public key to secret key"</string> - <string name="msg_pr_error_already_secret">"Key is already a secret key!"</string> <string name="msg_pr_error_key_not_found">"Key not found!"</string> <string name="msg_pr_fetching">"Fetching key to modify (%s)"</string> <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> @@ -1087,9 +1091,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> @@ -1107,7 +1111,7 @@ <string name="msg_import_fetch_error_decode">"Error decoding retrieved keyring!"</string> <string name="msg_import_fetch_error">"Key could not be retrieved! (Network problems?)"</string> <string name="msg_import_fetch_keybase">"Retrieving from keybase.io: %s"</string> - <string name="msg_import_fetch_keyserver_error">"Could not retrieve key from keybase!"</string> + <string name="msg_import_fetch_keyserver_error">"Could not retrieve key from keyservers: %s"</string> <string name="msg_import_fetch_keyserver">"Retrieving from keyserver: %s"</string> <string name="msg_import_fetch_keyserver_ok">"Key retrieval successful"</string> <string name="msg_import_keyserver">"Using keyserver %s"</string> @@ -1250,5 +1254,18 @@ <string name="nfc_write_succesful">Successfully written on NFC tag</string> <string name="unlocked">Unlocked</string> <string name="nfc_settings">Settings</string> + <string name="snack_yubikey_view">"View"</string> + <string name="snack_yubikey_import">"Import"</string> + <string name="button_bind_key">"Bind Key"</string> + <string name="yubikey_serno">"Serial No: %s"</string> + <string name="yubikey_key_holder">"Key holder: "</string> + <string name="yubikey_key_holder_unset">"Key holder: <unset>"</string> + <string name="yubikey_status_bound">"Yubikey matches and is bound to key"</string> + <string name="yubikey_status_unbound">"Yubikey matches, can be bound to key"</string> + <string name="yubikey_status_partly">"Yubikey matches, partly bound to key"</string> + <string name="btn_import">"Import"</string> + <string name="snack_yubi_other">Different key stored on Yubikey!</string> + <string name="error_nfc">"NFC Error: %s"</string> + <string name="error_pin_nodefault">Default PIN was rejected!</string> </resources> |