diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
77 files changed, 3810 insertions, 1732 deletions
| 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..45a6b98b8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.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..5227c1477 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CryptoOperationFragment.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import 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.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; + +/** + * All fragments executing crypto operations need to extend this class. + */ +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(OperationResult.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/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index c75e28145..ecc99b348 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;  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.Notify; @@ -144,7 +145,7 @@ public class DecryptFilesFragment extends DecryptFragment {              return;          } -        decryptOriginalFilename(); +//        decryptOriginalFilename();      }      private String removeEncryptedAppend(String name) { @@ -174,82 +175,93 @@ public class DecryptFilesFragment extends DecryptFragment {          }      } -    private void decryptOriginalFilename() { -        Log.d(Constants.TAG, "decryptOriginalFilename"); -        Intent intent = new Intent(getActivity(), KeychainIntentService.class); - -        // fill values for this action -        Bundle data = new Bundle(); -        intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); - -        // data -        Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); - -        data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal()); -        data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); - -        data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal()); -        data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri); - -        data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); -        data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey); - -        intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - -        // Message is received after decrypting is done in KeychainIntentService -        ServiceProgressHandler saveHandler = new ServiceProgressHandler( -                getActivity(), -                getString(R.string.progress_decrypting), -                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(); - -                    DecryptVerifyResult pgpResult = -                            returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - -                    if (pgpResult.isPending()) { -                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { -                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { -                            startPassphraseDialog(Constants.key.symmetric); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == -                                DecryptVerifyResult.RESULT_PENDING_NFC) { -                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); -                        } else { -                            throw new RuntimeException("Unhandled pending result!"); -                        } -                    } else if (pgpResult.success()) { -                        // go on... -                        askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); -                    } else { -                        pgpResult.createNotify(getActivity()).show(); -                    } -                } -            } -        }; - -        // Create a new Messenger for the communication back -        Messenger messenger = new Messenger(saveHandler); -        intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - -        // show progress dialog -        saveHandler.showProgressDialog(getActivity()); - -        // start service with intent -        getActivity().startService(intent); +    // TODO: also needs to use cryptoOperation!!! (switch between this and normal decrypt +//    private void decryptOriginalFilename() { +//        Log.d(Constants.TAG, "decryptOriginalFilename"); +// +//        Intent intent = new Intent(getActivity(), KeychainIntentService.class); +// +//        // fill values for this action +//        Bundle data = new Bundle(); +//        intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); +// +//        // data +//        Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); +// +//        data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal()); +//        data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri); +// +//        data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal()); +//        data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri); +// +//        data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); +//        data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey); +// +//        intent.putExtra(KeychainIntentService.EXTRA_DATA, data); +// +//        // Message is received after decrypting is done in KeychainIntentService +//        ServiceProgressHandler saveHandler = new ServiceProgressHandler( +//                getActivity(), +//                getString(R.string.progress_decrypting), +//                ProgressDialog.STYLE_HORIZONTAL, +//                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()) { +//                    // get returned data bundle +//                    Bundle returnData = message.getData(); +// +//                    DecryptVerifyResult pgpResult = +//                            returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); +// +//                    if (pgpResult.isPending()) { +//                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { +//                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { +//                            startPassphraseDialog(Constants.key.symmetric); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == +//                                DecryptVerifyResult.RESULT_PENDING_NFC) { +//                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); +//                        } else { +//                            throw new RuntimeException("Unhandled pending result!"); +//                        } +//                    } else if (pgpResult.success()) { +//                        // go on... +//                        askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); +//                    } else { +//                        pgpResult.createNotify(getActivity()).show(); +//                    } +//                } +//            } +//        }; +// +//        // Create a new Messenger for the communication back +//        Messenger messenger = new Messenger(saveHandler); +//        intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); +// +//        // show progress dialog +//        saveHandler.showProgressDialog(getActivity()); +// +//        // start service with intent +//        getActivity().startService(intent); +//    } + +    private void decryptStart() { +        cryptoOperation(new CryptoInputParcel());      }      @Override -    protected void decryptStart() { +    protected void cryptoOperation(CryptoInputParcel cryptoInput) {          Log.d(Constants.TAG, "decryptStart");          // Send all information needed to service to decrypt in other thread @@ -284,6 +296,11 @@ public class DecryptFilesFragment extends DecryptFragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); +                // handle pending messages +                if (handlePendingMessage(message)) { +                    return; +                } +                  if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); @@ -291,20 +308,21 @@ public class DecryptFilesFragment extends DecryptFragment {                      DecryptVerifyResult pgpResult =                              returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); -                    if (pgpResult.isPending()) { -                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { -                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { -                            startPassphraseDialog(Constants.key.symmetric); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == -                                DecryptVerifyResult.RESULT_PENDING_NFC) { -                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); -                        } else { -                            throw new RuntimeException("Unhandled pending result!"); -                        } -                    } else if (pgpResult.success()) { +//                    if (pgpResult.isPending()) { +//                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { +//                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { +//                            startPassphraseDialog(Constants.key.symmetric); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == +//                                DecryptVerifyResult.RESULT_PENDING_NFC) { +//                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); +//                        } else { +//                            throw new RuntimeException("Unhandled pending result!"); +//                        } + +                    if (pgpResult.success()) {                          // display signature result in activity                          onResult(pgpResult); @@ -346,21 +364,21 @@ public class DecryptFilesFragment extends DecryptFragment {      @Override      public void onActivityResult(int requestCode, int resultCode, Intent data) {          switch (requestCode) { -            case REQUEST_CODE_PASSPHRASE: { -                if (resultCode == Activity.RESULT_OK && data != null) { -                    mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); -                    decryptOriginalFilename(); -                } -                return; -            } - -            case REQUEST_CODE_NFC_DECRYPT: { -                if (resultCode == Activity.RESULT_OK && data != null) { -                    mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); -                    decryptOriginalFilename(); -                } -                return; -            } +//            case REQUEST_CODE_PASSPHRASE: { +//                if (resultCode == Activity.RESULT_OK && data != null) { +//                    mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); +////                    decryptOriginalFilename(); +//                } +//                return; +//            } +// +//            case REQUEST_CODE_NFC_DECRYPT: { +//                if (resultCode == Activity.RESULT_OK && data != null) { +//                    mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); +////                    decryptOriginalFilename(); +//                } +//                return; +//            }              case REQUEST_CODE_INPUT: {                  if (resultCode == Activity.RESULT_OK && data != null) { @@ -383,4 +401,5 @@ public class DecryptFilesFragment extends DecryptFragment {              }          }      } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 63508e530..f00136d5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -34,11 +34,11 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;  import org.sufficientlysecure.keychain.util.Passphrase; -public abstract class DecryptFragment extends Fragment { +public abstract class DecryptFragment extends CryptoOperationFragment {      private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; -    public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; -    public static final int REQUEST_CODE_NFC_DECRYPT = 0x00008002; +//    public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; +//    public static final int REQUEST_CODE_NFC_DECRYPT = 0x00008002;      protected long mSignatureKeyId = 0; @@ -95,24 +95,24 @@ public abstract class DecryptFragment extends Fragment {          startActivity(viewKeyIntent);      } -    protected void startPassphraseDialog(long subkeyId) { -        Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); -        intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId); -        startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); -    } - -    protected void startNfcDecrypt(long subKeyId, Passphrase pin, byte[] encryptedSessionKey) { -        // build PendingIntent for Yubikey NFC operations -        Intent intent = new Intent(getActivity(), NfcActivity.class); -        intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY); -        intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService -        intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId); -        intent.putExtra(NfcActivity.EXTRA_PIN, pin); - -        intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey); - -        startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT); -    } +//    protected void startPassphraseDialog(long subkeyId) { +//        Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); +//        intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId); +//        startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); +//    } +// +//    protected void startNfcDecrypt(long subKeyId, Passphrase pin, byte[] encryptedSessionKey) { +//        // build PendingIntent for Yubikey NFC operations +//        Intent intent = new Intent(getActivity(), NfcActivity.class); +//        intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY); +//        intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService +//        intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId); +//        intent.putExtra(NfcActivity.EXTRA_PIN, pin); +// +//        intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey); +// +//        startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT); +//    }      /**       * @@ -253,9 +253,4 @@ public abstract class DecryptFragment extends Fragment {          });      } -    /** -     * Should be overridden by MessageFragment and FileFragment to start actual decryption -     */ -    protected abstract void decryptStart(); -  } 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/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index f6e21937d..523b24fd7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;  import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;  import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;  import org.sufficientlysecure.keychain.ui.util.Notify;  import org.sufficientlysecure.keychain.util.Log; @@ -147,8 +148,13 @@ public class DecryptTextFragment extends DecryptFragment {          }      } +    private void decryptStart() { +        cryptoOperation(new CryptoInputParcel()); +    } +      @Override -    protected void decryptStart() { +    protected void cryptoOperation(CryptoInputParcel cryptoInput) { +          Log.d(Constants.TAG, "decryptStart");          // Send all information needed to service to decrypt in other thread @@ -177,6 +183,11 @@ public class DecryptTextFragment extends DecryptFragment {                  // handle messages by standard KeychainIntentServiceHandler first                  super.handleMessage(message); +                // handle pending messages +                if (handlePendingMessage(message)) { +                    return; +                } +                  if (message.arg1 == MessageStatus.OKAY.ordinal()) {                      // get returned data bundle                      Bundle returnData = message.getData(); @@ -184,20 +195,20 @@ public class DecryptTextFragment extends DecryptFragment {                      DecryptVerifyResult pgpResult =                              returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); -                    if (pgpResult.isPending()) { -                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { -                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == -                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { -                            startPassphraseDialog(Constants.key.symmetric); -                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == -                                DecryptVerifyResult.RESULT_PENDING_NFC) { -                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); -                        } else { -                            throw new RuntimeException("Unhandled pending result!"); -                        } -                    } else if (pgpResult.success()) { +//                    if (pgpResult.isPending()) { +//                        if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { +//                            startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded()); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) == +//                                DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { +//                            startPassphraseDialog(Constants.key.symmetric); +//                        } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == +//                                DecryptVerifyResult.RESULT_PENDING_NFC) { +//                            startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()); +//                        } else { +//                            throw new RuntimeException("Unhandled pending result!"); +//                        } +                    if (pgpResult.success()) {                          byte[] decryptedMessage = returnData                                  .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); @@ -245,34 +256,34 @@ public class DecryptTextFragment extends DecryptFragment {          getActivity().startService(intent);      } -    @Override -    public void onActivityResult(int requestCode, int resultCode, Intent data) { -        switch (requestCode) { - -            case REQUEST_CODE_PASSPHRASE: { -                if (resultCode == Activity.RESULT_OK && data != null) { -                    mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); -                    decryptStart(); -                } else { -                    getActivity().finish(); -                } -                return; -            } - -            case REQUEST_CODE_NFC_DECRYPT: { -                if (resultCode == Activity.RESULT_OK && data != null) { -                    mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); -                    decryptStart(); -                } else { -                    getActivity().finish(); -                } -                return; -            } - -            default: { -                super.onActivityResult(requestCode, resultCode, data); -            } -        } -    } +//    @Override +//    public void onActivityResult(int requestCode, int resultCode, Intent data) { +//        switch (requestCode) { +// +//            case REQUEST_CODE_PASSPHRASE: { +//                if (resultCode == Activity.RESULT_OK && data != null) { +//                    mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); +//                    decryptStart(); +//                } else { +//                    getActivity().finish(); +//                } +//                return; +//            } +// +//            case REQUEST_CODE_NFC_DECRYPT: { +//                if (resultCode == Activity.RESULT_OK && data != null) { +//                    mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); +//                    decryptStart(); +//                } else { +//                    getActivity().finish(); +//                } +//                return; +//            } +// +//            default: { +//                super.onActivityResult(requestCode, resultCode, data); +//            } +//        } +//    }  } 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..a1edf808c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -25,18 +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 {      public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; @@ -44,8 +44,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 +64,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 +87,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 +103,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 +116,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 +148,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 +168,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/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java deleted file mode 100644 index 2a102c6c4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.net.Uri; - -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.util.ArrayList; - -public interface EncryptActivityInterface { - -    public interface UpdateListener { -        void onNotifyUpdate(); -    } - -    public boolean isUseArmor(); -    public boolean isUseCompression(); -    public boolean isEncryptFilenames(); -    public boolean isHiddenRecipients(); - -    public long getSignatureKey(); -    public long[] getEncryptionKeys(); -    public String[] getEncryptionUsers(); -    public void setSignatureKey(long signatureKey); -    public void setEncryptionKeys(long[] encryptionKeys); -    public void setEncryptionUsers(String[] encryptionUsers); - -    public void setPassphrase(Passphrase passphrase); - -    // ArrayList on purpose as only those are parcelable -    public ArrayList<Uri> getInputUris(); -    public ArrayList<Uri> getOutputUris(); -    public void setInputUris(ArrayList<Uri> uris); -    public void setOutputUris(ArrayList<Uri> uris); - -    public String getMessage(); -    public void setMessage(String message); - -    /** -     * Call this to notify the UI for changes done on the array lists or arrays, -     * automatically called if setter is used -     */ -    public void notifyUpdate(); - -    public void startEncrypt(boolean share); -} 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..64e908b1a 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 @@ -18,32 +18,25 @@  package org.sufficientlysecure.keychain.ui; +import android.app.Activity;  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 android.view.View; -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 +        EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric, +        EncryptFilesFragment.IMode {      /* Intents */      public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA; @@ -55,302 +48,22 @@ 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); +        setFullScreenDialogClose(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                finish(); +            } +        }, false); +          // Handle intent actions -        handleActions(getIntent()); -        updateModeFragment(); +        handleActions(getIntent(), savedInstanceState);      }      @Override @@ -358,73 +71,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 +103,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 = EncryptModeAsymmetricFragment.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 +                                ? EncryptModeSymmetricFragment.newInstance() +                                : EncryptModeAsymmetricFragment.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..5af353524 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,104 @@ 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 class EncryptFilesFragment extends CryptoOperationFragment { + +    public interface IMode { +        public void onModeChanged(boolean symmetric); +    } + +    public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";      public static final String ARG_URIS = "uris";      private static final int REQUEST_CODE_INPUT = 0x00007003;      private static final int REQUEST_CODE_OUTPUT = 0x00007007; -    private EncryptActivityInterface mEncryptInterface; +    private IMode mModeInterface; + +    private boolean mSymmetricMode = false; +    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<>(); +    /** +     * 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 +147,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 +164,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 +185,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); +                    Notify.Style.ERROR).show();              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,24 +221,24 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt      }      private void encryptClicked(boolean share) { -        if (mEncryptInterface.getInputUris().isEmpty()) { -            Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this); +        if (mInputUris.isEmpty()) { +            Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show();              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) { -                Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this); +            if (mInputUris.size() > 1) { +                Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show();                  return;              }              showOutputFileDialog(); @@ -189,7 +258,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_fragment, 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 +277,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 +314,250 @@ 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(); +            return false; +        } else if (mInputUris.size() > 1 && !mShareAfterEncrypt) { +            Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt"); +            // This should be impossible... +            return false; +        } else if (mInputUris.size() != mOutputUris.size()) { +            Log.e(Constants.TAG, "Aborting: 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(); +                return false; +            } +            if (mPassphrase.isEmpty()) { +                Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) +                        .show(); +                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(); +                return false; +            } +        } +        return true; +    } + +    public void startEncrypt(boolean share) { +        mShareAfterEncrypt = share; +        cryptoOperation(new CryptoInputParcel()); +    } + +    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; +    } + +    @Override +    protected void cryptoOperation(CryptoInputParcel cryptoInput) { + +        if (!inputIsValid()) { +            // Notify was created by inputIsValid. +            Log.d(Constants.TAG, "Input not valid!"); +            return; +        } +        Log.d(Constants.TAG, "Input valid!"); + +        // 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 +572,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 +588,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 +602,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 +617,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/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index c5404094a..8c117deca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.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 EncryptModeAsymmetricFragment 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 EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) { +        EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment(); + +        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/EncryptSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java index 36b3c08f9..48b1f4983 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.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 EncryptModeSymmetricFragment 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 EncryptModeSymmetricFragment newInstance() { +        EncryptModeSymmetricFragment frag = new EncryptModeSymmetricFragment(); + +        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..2ffb29b09 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.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 @@ -19,31 +19,21 @@  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; +import android.support.v4.app.FragmentTransaction; +import android.view.View; -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.compatibility.ClipboardReflection; -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.util.Notify; +import org.sufficientlysecure.keychain.ui.base.BaseActivity;  import org.sufficientlysecure.keychain.util.Log;  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 EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface { +public class EncryptTextActivity extends BaseActivity implements +        EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric, +        EncryptTextFragment.IMode {      /* Intents */      public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT; @@ -55,285 +45,22 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv      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_SIGNATURE_KEY_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 mShareAfterEncrypt = false; -    private boolean mUseCompression = true; -    private boolean mHiddenRecipients = false; - -    private long mEncryptionKeyIds[] = null; -    private String mEncryptionUserIds[] = null; -    // TODO Constants.key.none? What's wrong with a null value? -    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 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(); -        } -        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(); -    } - -    @Override -    protected void onEncryptSuccess(SignEncryptResult result) { -        if (mShareAfterEncrypt) { -            // Share encrypted message/file -            startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes())); -        } else { -            // Copy to clipboard -            copyToClipboard(result.getResultBytes()); -            result.createNotify(EncryptTextActivity.this).show(); -            // Notify.create(EncryptTextActivity.this, -            // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK) -            // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); -        } -    } - -    @Override -    protected SignEncryptParcel createEncryptBundle() { -        // fill values for this action -        SignEncryptParcel data = new SignEncryptParcel(); - -        data.setBytes(mMessage.getBytes()); -        data.setCleartextSignature(true); - -        if (mUseCompression) { -            data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); -        } else { -            data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); -        } -        data.setHiddenRecipients(mHiddenRecipients); -        data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); -        data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); - -        // Always use armor for messages -        data.setEnableAsciiArmorOutput(true); - -        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; -    } - -    private void copyToClipboard(byte[] resultBytes) { -        ClipboardReflection.copyToClipboard(this, new String(resultBytes)); -    } - -    /** -     * Create Intent Chooser but exclude OK's EncryptActivity. -     */ -    private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) { -        Intent prototype = createSendIntent(resultBytes); -        String title = getString(R.string.title_share_message); - -        // we don't want to encrypt the encrypted, no inception ;) -        String[] blacklist = new String[]{ -                Constants.PACKAGE_NAME + ".ui.EncryptTextActivity", -                "org.thialfihar.android.apg.ui.EncryptActivity" -        }; - -        return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist); -    } - -    private Intent createSendIntent(byte[] resultBytes) { -        Intent sendIntent; -        sendIntent = new Intent(Intent.ACTION_SEND); -        sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME); -        sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes)); - -        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); -                } -            } -            // pass trough email addresses as extra for email applications -            sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); -        } -        return sendIntent; -    } - -    protected boolean inputIsValid() { -        if (mMessage == null) { -            Notify.create(this, R.string.error_message, Notify.Style.ERROR) -                    .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); -            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_text_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_text_fragment)); -                return false; -            } - -        } else { -            // asymmetric encryption checks - -            boolean gotEncryptionKeys = (mEncryptionKeyIds != null -                    && mEncryptionKeyIds.length > 0); - -            if (!gotEncryptionKeys && mSigningKeyId == 0) { -                Notify.create(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR) -                        .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); -                return false; -            } -        } -        return true; -    } +    Fragment mModeFragment; +    EncryptTextFragment mEncryptFragment;      @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); -        // if called with an intent action, do not init drawer navigation -        if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) { -            // lock drawer -//            deactivateDrawerNavigation(); -            // TODO: back button to key? -        } else { -//            activateDrawerNavigation(savedInstanceState); -        } +        setFullScreenDialogClose(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                finish(); +            } +        }, false);          // Handle intent actions -        handleActions(getIntent()); -        updateModeFragment(); +        handleActions(getIntent(), savedInstanceState);      }      @Override @@ -341,58 +68,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv          setContentView(R.layout.encrypt_text_activity);      } -    @Override -    public boolean onCreateOptionsMenu(Menu menu) { -        getMenuInflater().inflate(R.menu.encrypt_text_activity, menu); -        return super.onCreateOptionsMenu(menu); -    } - -    private void updateModeFragment() { -        getSupportFragmentManager().beginTransaction() -                .replace(R.id.encrypt_pager_mode, -                        mCurrentMode == MODE_SYMMETRIC -                                ? new EncryptSymmetricFragment() -                                : new EncryptAsymmetricFragment() -                ) -                .commitAllowingStateLoss(); -        getSupportFragmentManager().executePendingTransactions(); -    } - -    @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_enable_compression: { -                mUseCompression = item.isChecked(); -                notifyUpdate(); -                break; -            } -//            case R.id.check_hidden_recipients: { -//                mHiddenRecipients = item.isChecked(); -//                notifyUpdate(); -//                break; -//            } -            default: { -                return super.onOptionsItemSelected(item); -            } -        } -        return true; -    }      /**       * 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(); @@ -423,19 +105,61 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv          }          String textData = extras.getString(EXTRA_TEXT); +        if (ACTION_ENCRYPT_TEXT.equals(action) && textData == null) { +            Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); +            return; +        }          // preselect keys given by intent -        mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); -        mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); +        long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); +        long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); -        /** -         * Main Actions -         */ -        if (ACTION_ENCRYPT_TEXT.equals(action) && textData != null) { -            mMessage = textData; -        } else if (ACTION_ENCRYPT_TEXT.equals(action)) { -            Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); + +        if (savedInstanceState == null) { +            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + +            mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds); +            transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode"); + +            mEncryptFragment = EncryptTextFragment.newInstance(textData); +            transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text"); + +            transaction.commit(); + +            getSupportFragmentManager().executePendingTransactions();          }      } +    @Override +    public void onModeChanged(boolean symmetric) { +        // switch fragments +        getSupportFragmentManager().beginTransaction() +                .replace(R.id.encrypt_mode_container, +                        symmetric +                                ? EncryptModeSymmetricFragment.newInstance() +                                : EncryptModeAsymmetricFragment.newInstance(0, null) +                ) +                .commitAllowingStateLoss(); +        getSupportFragmentManager().executePendingTransactions(); +    } + +    @Override +    public void onSignatureKeyIdChanged(long signatureKeyId) { +        mEncryptFragment.setSigningKeyId(signatureKeyId); +    } + +    @Override +    public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) { +        mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds); +    } + +    @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/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index 5d9994c5c..3303b2c65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.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 @@ -18,32 +18,102 @@  package org.sufficientlysecure.keychain.ui;  import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent;  import android.os.Bundle; -import android.support.v4.app.Fragment; +import android.os.Message; +import android.os.Messenger;  import android.text.Editable;  import android.text.TextWatcher;  import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater;  import android.view.MenuItem;  import android.view.View;  import android.view.ViewGroup;  import android.widget.TextView; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +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.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.ShareHelper; + +import java.util.HashSet; +import java.util.Set; + +public class EncryptTextFragment extends CryptoOperationFragment { + +    public interface IMode { +        public void onModeChanged(boolean symmetric); +    } -public class EncryptTextFragment extends Fragment {      public static final String ARG_TEXT = "text"; +    private IMode mModeInterface; + +    private boolean mSymmetricMode = false; +    private boolean mShareAfterEncrypt = false; +    private boolean mUseCompression = true; +    private boolean mHiddenRecipients = false; + +    private long mEncryptionKeyIds[] = null; +    private String mEncryptionUserIds[] = null; +    // TODO Constants.key.none? What's wrong with a null value? +    private long mSigningKeyId = Constants.key.none; +    private Passphrase mPassphrase = new Passphrase(); +    private String mMessage = ""; +      private TextView mText; -    private EncryptActivityInterface mEncryptInterface; +    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; +    } + +    /** +     * Creates new instance of this fragment +     */ +    public static EncryptTextFragment newInstance(String text) { +        EncryptTextFragment frag = new EncryptTextFragment(); + +        Bundle args = new Bundle(); +        args.putString(ARG_TEXT, text); +        frag.setArguments(args); + +        return frag; +    }      @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 implement IMode");          }      } @@ -54,6 +124,9 @@ public class EncryptTextFragment extends Fragment {      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {          View view = inflater.inflate(R.layout.encrypt_text_fragment, container, false); +        if (mMessage != null) { +            mText.setText(mMessage); +        }          mText = (TextView) view.findViewById(R.id.encrypt_text_text);          mText.addTextChangedListener(new TextWatcher() {              @Override @@ -68,7 +141,7 @@ public class EncryptTextFragment extends Fragment {              @Override              public void afterTextChanged(Editable s) { -                mEncryptInterface.setMessage(s.toString()); +                mMessage = s.toString();              }          }); @@ -78,29 +151,43 @@ public class EncryptTextFragment extends Fragment {      @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); +        mMessage = getArguments().getString(ARG_TEXT);          setHasOptionsMenu(true);      }      @Override -    public void onActivityCreated(Bundle savedInstanceState) { -        super.onActivityCreated(savedInstanceState); - -        String text = mEncryptInterface.getMessage(); -        if (text != null) { -            mText.setText(text); -        } +    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { +        super.onCreateOptionsMenu(menu, inflater); +        inflater.inflate(R.menu.encrypt_text_fragment, menu);      }      @Override      public boolean onOptionsItemSelected(MenuItem item) { +        if (item.isCheckable()) { +            item.setChecked(!item.isChecked()); +        }          switch (item.getItemId()) { +            case R.id.check_use_symmetric: { +                mSymmetricMode = item.isChecked(); +                mModeInterface.onModeChanged(mSymmetricMode); +                break; +            } +            case R.id.check_enable_compression: { +                mUseCompression = item.isChecked(); +                break; +            } +//            case R.id.check_hidden_recipients: { +//                mHiddenRecipients = item.isChecked(); +//                notifyUpdate(); +//                break; +//            }              case R.id.encrypt_copy: { -                mEncryptInterface.startEncrypt(false); +                startEncrypt(false);                  break;              }              case R.id.encrypt_share: { -                mEncryptInterface.startEncrypt(true); +                startEncrypt(true);                  break;              }              default: { @@ -109,4 +196,223 @@ public class EncryptTextFragment extends Fragment {          }          return true;      } + + +    protected void onEncryptSuccess(SignEncryptResult result) { +        if (mShareAfterEncrypt) { +            // Share encrypted message/file +            startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes())); +        } else { +            // Copy to clipboard +            copyToClipboard(result.getResultBytes()); +            result.createNotify(getActivity()).show(); +            // Notify.create(EncryptTextActivity.this, +            // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK) +            // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); +        } +    } + +    protected SignEncryptParcel createEncryptBundle() { +        // fill values for this action +        SignEncryptParcel data = new SignEncryptParcel(); + +        data.setBytes(mMessage.getBytes()); +        data.setCleartextSignature(true); + +        if (mUseCompression) { +            data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); +        } else { +            data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); +        } +        data.setHiddenRecipients(mHiddenRecipients); +        data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); +        data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); + +        // Always use armor for messages +        data.setEnableAsciiArmorOutput(true); + +        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; +    } + +    private void copyToClipboard(byte[] resultBytes) { +        ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes)); +    } + +    /** +     * Create Intent Chooser but exclude OK's EncryptActivity. +     */ +    private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) { +        Intent prototype = createSendIntent(resultBytes); +        String title = getString(R.string.title_share_message); + +        // we don't want to encrypt the encrypted, no inception ;) +        String[] blacklist = new String[]{ +                Constants.PACKAGE_NAME + ".ui.EncryptTextActivity", +                "org.thialfihar.android.apg.ui.EncryptActivity" +        }; + +        return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist); +    } + +    private Intent createSendIntent(byte[] resultBytes) { +        Intent sendIntent; +        sendIntent = new Intent(Intent.ACTION_SEND); +        sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME); +        sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes)); + +        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); +                } +            } +            // pass trough email addresses as extra for email applications +            sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); +        } +        return sendIntent; +    } + +    protected boolean inputIsValid() { +        if (mMessage == null) { +            Notify.create(getActivity(), R.string.error_message, Notify.Style.ERROR) +                    .show(); +            return false; +        } + +        if (mSymmetricMode) { +            // symmetric encryption checks + +            if (mPassphrase == null) { +                Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR) +                        .show(); +                return false; +            } +            if (mPassphrase.isEmpty()) { +                Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) +                        .show(); +                return false; +            } + +        } else { +            // asymmetric encryption checks + +            boolean gotEncryptionKeys = (mEncryptionKeyIds != null +                    && mEncryptionKeyIds.length > 0); + +            if (!gotEncryptionKeys && mSigningKeyId == 0) { +                Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR) +                        .show(); +                return false; +            } +        } +        return true; +    } + + +    public void startEncrypt(boolean share) { +        mShareAfterEncrypt = share; +        startEncrypt(); +    } + +    public void startEncrypt() { +        cryptoOperation(null); +    } + +    @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); +        } + +        final 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, +                ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { +            public void handleMessage(Message message) { +                // handle messages by standard KeychainIntentServiceHandler first +                super.handleMessage(message); + +                // TODO: We need a InputPendingResult! +//                // 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); +    } +  } 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/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java index e4e4e4d05..2b47fd623 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java @@ -191,9 +191,6 @@ public class NfcHelper {          mNfcAdapter.invokeBeam(mActivity);      } -    /** -     * A static subclass of {@link Handler} with a {@link WeakReference} to an {@link Activity} to avoid memory leaks. -     */      private static class NfcHandler extends Handler {          private final WeakReference<Activity> mActivityReference; @@ -203,12 +200,10 @@ public class NfcHelper {          @Override          public void handleMessage(Message msg) { -            Activity activity = mActivityReference.get(); - -            if (activity != null) { +            if (mActivityReference.get() != null) {                  switch (msg.what) {                      case NFC_SENT: -                        Notify.create(activity, R.string.nfc_successful, Notify.Style.OK).show(); +                        Notify.create(mActivityReference.get(), R.string.nfc_successful, Notify.Style.OK).show();                          break;                  }              } | 
