diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
13 files changed, 1114 insertions, 554 deletions
| diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java new file mode 100644 index 000000000..d35f1d751 --- /dev/null +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java @@ -0,0 +1,76 @@ +/** + * 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.spongycastle.openpgp.operator.jcajce; + +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; + +import java.nio.ByteBuffer; +import java.util.Map; + +public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory +{ +    private final PublicKeyDataDecryptorFactory mWrappedDecryptor; +    private final Map<ByteBuffer, byte[]> mSessionKeyCache; + +    private OperatorHelper mOperatorHelper; + +    public CachingDataDecryptorFactory(String providerName, +            final Map<ByteBuffer,byte[]> sessionKeyCache) +    { +        mWrappedDecryptor = null; +        mSessionKeyCache = sessionKeyCache; + +        mOperatorHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); +    } + +    public CachingDataDecryptorFactory(PublicKeyDataDecryptorFactory wrapped, +            final Map<ByteBuffer,byte[]> sessionKeyCache) +    { +        mWrappedDecryptor = wrapped; +        mSessionKeyCache = sessionKeyCache; + +    } + +    public boolean hasCachedSessionData(PGPPublicKeyEncryptedData encData) throws PGPException { +        ByteBuffer bi = ByteBuffer.wrap(encData.getSessionKey()[0]); +        return mSessionKeyCache.containsKey(bi); +    } + +    public Map<ByteBuffer, byte[]> getCachedSessionKeys() { +        return mSessionKeyCache; +    } + +    public boolean canDecrypt() { +        return mWrappedDecryptor != null; +    } + +    @Override +    public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { +        ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]);  // encoded MPI +        if (mSessionKeyCache.containsKey(bi)) { +            return mSessionKeyCache.get(bi); +        } + +        byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData); +        mSessionKeyCache.put(bi, sessionData); +        return sessionData; +    } + +    @Override +    public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) +            throws PGPException { +        if (mWrappedDecryptor != null) { +            return mWrappedDecryptor.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); +        } +        return mOperatorHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); +    } + +} diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java deleted file mode 100644 index 067bb3e19..000000000 --- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java +++ /dev/null @@ -1,278 +0,0 @@ -/** - * 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.spongycastle.openpgp.operator.jcajce; - -import org.spongycastle.bcpg.PublicKeyAlgorithmTags; -import org.spongycastle.jcajce.util.DefaultJcaJceHelper; -import org.spongycastle.jcajce.util.NamedJcaJceHelper; -import org.spongycastle.jcajce.util.ProviderJcaJceHelper; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.operator.PGPDataDecryptor; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; - -import java.nio.ByteBuffer; -import java.security.Provider; -import java.util.Map; - - -/** - * This class is based on JcePublicKeyDataDecryptorFactoryBuilder - * - */ -public class NfcSyncPublicKeyDataDecryptorFactoryBuilder -{ -    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); -    private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); -    private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); -//    private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); -//    private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); - -    public static class NfcInteractionNeeded extends RuntimeException -    { -        public byte[] encryptedSessionKey; - -        public NfcInteractionNeeded(byte[] encryptedSessionKey) -        { -            super("NFC interaction required!"); -            this.encryptedSessionKey = encryptedSessionKey; -        } -    } - -    public NfcSyncPublicKeyDataDecryptorFactoryBuilder() -    { -    } - -    /** -     * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. -     * -     * @param provider  provider object for cryptographic primitives. -     * @return  the current builder. -     */ -    public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider) -    { -        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); -        keyConverter.setProvider(provider); -        this.contentHelper = helper; - -        return this; -    } - -    /** -     * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. -     * -     * @param providerName  the name of the provider to reference for cryptographic primitives. -     * @return  the current builder. -     */ -    public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(String providerName) -    { -        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); -        keyConverter.setProvider(providerName); -        this.contentHelper = helper; - -        return this; -    } - -    public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider) -    { -        this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider)); - -        return this; -    } - -    public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName) -    { -        this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); - -        return this; -    } - -    public PublicKeyDataDecryptorFactory build(final Map<ByteBuffer,byte[]> nfcDecryptedMap) { -        return new PublicKeyDataDecryptorFactory() -        { -            public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) -                    throws PGPException -            { -                if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) -                { -                    throw new PGPException("ECDH not supported!"); -                } - -                return decryptSessionData(keyAlgorithm, secKeyData, nfcDecryptedMap); -            } - -            public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) -                    throws PGPException -            { -                return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); -            } -        }; -    } - -//    public PublicKeyDataDecryptorFactory build(final PrivateKey privKey) -//    { -//        return new PublicKeyDataDecryptorFactory() -//        { -//            public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) -//                    throws PGPException -//            { -//                if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) -//                { -//                    throw new PGPException("ECDH requires use of PGPPrivateKey for decryption"); -//                } -//                return decryptSessionData(keyAlgorithm, privKey, secKeyData); -//            } -// -//            public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) -//                    throws PGPException -//            { -//                return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); -//            } -//        }; -//    } - -//    public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey, final byte[] nfcDecrypted) -//    { -//        return new PublicKeyDataDecryptorFactory() -//        { -//            public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) -//                    throws PGPException -//            { -//                if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) -//                { -//                    return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData); -//                } -// -//                return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData, nfcDecrypted); -//            } -// -//            public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) -//                    throws PGPException -//            { -//                return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); -//            } -//        }; -//    } - -//    private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData) -//            throws PGPException -//    { -//        ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); -//        X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); -// -//        byte[] enc = secKeyData[0]; -// -//        int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; -//        byte[] pEnc = new byte[pLen]; -// -//        System.arraycopy(enc, 2, pEnc, 0, pLen); -// -//        byte[] keyEnc = new byte[enc[pLen + 2]]; -// -//        System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); -// -//        Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); -// -//        ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize(); -// -//        RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); -//        Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap"); -// -//        try -//        { -//            c.init(Cipher.UNWRAP_MODE, key); -// -//            Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); -// -//            return PGPPad.unpadSessionData(paddedSessionKey.getEncoded()); -//        } -//        catch (InvalidKeyException e) -//        { -//            throw new PGPException("error setting asymmetric cipher", e); -//        } -//        catch (NoSuchAlgorithmException e) -//        { -//            throw new PGPException("error setting asymmetric cipher", e); -//        } -//    } - -    private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData, -            Map<ByteBuffer,byte[]> nfcDecryptedMap) -        throws PGPException -    { -//        Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); -// -//        try -//        { -//            c1.init(Cipher.DECRYPT_MODE, privKey); -//        } -//        catch (InvalidKeyException e) -//        { -//            throw new PGPException("error setting asymmetric cipher", e); -//        } - -        if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT -                || keyAlgorithm == PGPPublicKey.RSA_GENERAL) -        { -            ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]);  // encoded MPI - -            if (nfcDecryptedMap.containsKey(bi)) { -                return nfcDecryptedMap.get(bi); -            } else { -                // catch this when decryptSessionData() is executed and divert digest to card, -                // when doing the operation again reuse nfcDecrypted -                throw new NfcInteractionNeeded(bi.array()); -            } - -//            c1.update(bi, 2, bi.length - 2); -        } -        else -        { -            throw new PGPException("ElGamal not supported!"); - -//            ElGamalKey k = (ElGamalKey)privKey; -//            int size = (k.getParameters().getP().bitLength() + 7) / 8; -//            byte[] tmp = new byte[size]; -// -//            byte[] bi = secKeyData[0]; // encoded MPI -//            if (bi.length - 2 > size)  // leading Zero? Shouldn't happen but... -//            { -//                c1.update(bi, 3, bi.length - 3); -//            } -//            else -//            { -//                System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); -//                c1.update(tmp); -//            } -// -//            bi = secKeyData[1];  // encoded MPI -//            for (int i = 0; i != tmp.length; i++) -//            { -//                tmp[i] = 0; -//            } -// -//            if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... -//            { -//                c1.update(bi, 3, bi.length - 3); -//            } -//            else -//            { -//                System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); -//                c1.update(tmp); -//            } -        } - -//        try -//        { -//            return c1.doFinal(); -//        } -//        catch (Exception e) -//        { -//            throw new PGPException("exception decrypting session data", e); -//        } -    } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index ac571390a..25a86f137 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -22,6 +22,7 @@ import android.os.Parcel;  import org.openintents.openpgp.OpenPgpMetadata;  import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;  import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;  import org.sufficientlysecure.keychain.util.Passphrase; @@ -36,6 +37,8 @@ public class DecryptVerifyResult extends InputPendingResult {      // https://tools.ietf.org/html/rfc4880#page56      String mCharset; +    CryptoInputParcel mCachedCryptoInputParcel; +      byte[] mOutputBytes;      public DecryptVerifyResult(int result, OperationLog log) { @@ -50,6 +53,7 @@ public class DecryptVerifyResult extends InputPendingResult {          super(source);          mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());          mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader()); +        mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader());      } @@ -65,6 +69,14 @@ public class DecryptVerifyResult extends InputPendingResult {          mSignatureResult = signatureResult;      } +    public CryptoInputParcel getCachedCryptoInputParcel() { +        return mCachedCryptoInputParcel; +    } + +    public void setCachedCryptoInputParcel(CryptoInputParcel cachedCryptoInputParcel) { +        mCachedCryptoInputParcel = cachedCryptoInputParcel; +    } +      public OpenPgpMetadata getDecryptMetadata() {          return mDecryptMetadata;      } @@ -97,6 +109,7 @@ public class DecryptVerifyResult extends InputPendingResult {          super.writeToParcel(dest, flags);          dest.writeParcelable(mSignatureResult, 0);          dest.writeParcelable(mDecryptMetadata, 0); +        dest.writeParcelable(mCachedCryptoInputParcel, 0);      }      public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() { 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 17d342341..31a3925da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -21,23 +21,18 @@ package org.sufficientlysecure.keychain.pgp;  import org.spongycastle.bcpg.S2K;  import org.spongycastle.openpgp.PGPException;  import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing;  import org.spongycastle.openpgp.PGPSecretKey;  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.PBESecretKeyDecryptor;  import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;  import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;  import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;  import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -51,7 +46,6 @@ import java.security.interfaces.RSAPrivateCrtKey;  import java.util.ArrayList;  import java.util.Date;  import java.util.HashMap; -import java.util.List;  import java.util.Map; @@ -270,19 +264,20 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {          }      } -    public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) { +    public CachingDataDecryptorFactory getCachingDecryptorFactory(CryptoInputParcel cryptoInput) {          if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {              throw new PrivateKeyNotUnlockedException();          }          if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { -            return new NfcSyncPublicKeyDataDecryptorFactoryBuilder() -                    .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( -                            cryptoInput.getCryptoData() -                    ); +            return new CachingDataDecryptorFactory( +                    Constants.BOUNCY_CASTLE_PROVIDER_NAME, +                    cryptoInput.getCryptoData());          } else { -            return new JcePublicKeyDataDecryptorFactoryBuilder() -                    .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey); +            return new CachingDataDecryptorFactory( +                    new JcePublicKeyDataDecryptorFactoryBuilder() +                            .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey), +                    cryptoInput.getCryptoData());          }      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index fd3d41f93..4651f12d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -40,11 +40,10 @@ import org.spongycastle.openpgp.PGPUtil;  import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;  import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;  import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.operations.BaseOperation; @@ -538,24 +537,33 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>              currentProgress += 2;              updateProgress(R.string.progress_preparing_streams, currentProgress, 100); -            try { -                PublicKeyDataDecryptorFactory decryptorFactory -                        = secretEncryptionKey.getDecryptorFactory(cryptoInput); -                try { -                    clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); -                } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) { -                    log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1); -                    return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); -                } +            CachingDataDecryptorFactory decryptorFactory +                    = secretEncryptionKey.getCachingDecryptorFactory(cryptoInput); + +            // special case: if the decryptor does not have a session key cached for this encrypted +            // data, and can't actually decrypt on its own, return a pending intent +            if (!decryptorFactory.canDecrypt() +                    && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) { -                symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); -            } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {                  log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);                  return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(                          secretEncryptionKey.getRing().getMasterKeyId(), -                        secretEncryptionKey.getKeyId(), e.encryptedSessionKey +                        secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]                  )); +              } + +            try { +                clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); +            } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) { +                log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1); +                return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); +            } + +            symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); + +            cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys()); +              encryptedData = encryptedDataAsymmetric;          } else {              // there wasn't even any useful data @@ -662,9 +670,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>              PGPLiteralData literalData = (PGPLiteralData) dataChunk; -            // reported size may be null if partial packets are involved (highly unlikely though) -            Long originalSize = literalData.getDataLengthIfAvailable(); -              String originalFilename = literalData.getFileName();              String mimeType = null;              if (literalData.getFormat() == PGPLiteralData.TEXT @@ -687,12 +692,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>                  }              } -            metadata = new OpenPgpMetadata( -                    originalFilename, -                    mimeType, -                    literalData.getModificationTime().getTime(), -                    originalSize == null ? 0 : originalSize); -              if (!"".equals(originalFilename)) {                  log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);              } @@ -700,15 +699,26 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>                      mimeType);              log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,                      new Date(literalData.getModificationTime().getTime()).toString()); -            if (originalSize != null) { -                log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, -                        Long.toString(originalSize)); -            } else { -                log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); -            }              // return here if we want to decrypt the metadata only              if (input.isDecryptMetadataOnly()) { + +                // this operation skips the entire stream to find the data length! +                Long originalSize = literalData.findDataLength(); + +                if (originalSize != null) { +                    log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, +                            Long.toString(originalSize)); +                } else { +                    log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); +                } + +                metadata = new OpenPgpMetadata( +                        originalFilename, +                        mimeType, +                        literalData.getModificationTime().getTime(), +                        originalSize == null ? 0 : originalSize); +                  log.add(LogType.MSG_DC_OK_META_ONLY, indent);                  DecryptVerifyResult result =                          new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); @@ -757,6 +767,21 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>                  // TODO: slow annealing to fake a progress?              } +            // after going through the stream, size should be available +            Long originalSize = literalData.getDataLengthIfAvailable(); +            if (originalSize != null) { +                log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, +                        Long.toString(originalSize)); +            } else { +                log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); +            } + +            metadata = new OpenPgpMetadata( +                    originalFilename, +                    mimeType, +                    literalData.getModificationTime().getTime(), +                    originalSize == null ? 0 : originalSize); +              if (signature != null) {                  updateProgress(R.string.progress_verifying_signature, 90, 100);                  log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); @@ -818,6 +843,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>          // Return a positive result, with metadata and verification info          DecryptVerifyResult result =                  new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); +        result.setCachedCryptoInputParcel(cryptoInput);          result.setDecryptMetadata(metadata);          result.setSignatureResult(signatureResultBuilder.build());          result.setCharset(charset); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index 45f806960..a3d00a9a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -56,6 +56,11 @@ public class TemporaryStorageProvider extends ContentProvider {          return context.getContentResolver().insert(BASE_URI, contentValues);      } +    public static Uri createFile(Context context) { +        ContentValues contentValues = new ContentValues(); +        return context.getContentResolver().insert(BASE_URI, contentValues); +    } +      public static int cleanUp(Context context) {          return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",                  new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 3d1ccaca1..ee7caf2d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -97,8 +97,12 @@ public class CryptoInputParcel implements Parcelable {          mCryptoData.put(ByteBuffer.wrap(hash), signedHash);      } +    public void addCryptoData(Map<ByteBuffer, byte[]> cachedSessionKeys) { +        mCryptoData.putAll(cachedSessionKeys); +    } +      public Map<ByteBuffer, byte[]> getCryptoData() { -        return Collections.unmodifiableMap(mCryptoData); +        return mCryptoData;      }      public Date getSignatureTime() { @@ -138,4 +142,5 @@ public class CryptoInputParcel implements Parcelable {          b.append("}");          return b.toString();      } +  } 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 81fb6a392..b56c38d19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -17,11 +17,12 @@  package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; +  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.View;  import android.widget.Toast; @@ -29,7 +30,7 @@ import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;  import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.util.Log; +  public class DecryptFilesActivity extends BaseActivity { @@ -94,13 +95,26 @@ public class DecryptFilesActivity extends BaseActivity {          }          boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action); -        DecryptFilesFragment frag = DecryptFilesFragment.newInstance(uri, showOpenDialog); +        DecryptFilesInputFragment frag = DecryptFilesInputFragment.newInstance(uri, showOpenDialog);          // Add the fragment to the 'fragment_container' FrameLayout          // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!          getSupportFragmentManager().beginTransaction()                  .replace(R.id.decrypt_files_fragment_container, frag) -                .commitAllowingStateLoss(); +                .commit(); + +    } + +    public void displayListFragment(Uri inputUri) { + +        ArrayList<Uri> uris = new ArrayList<>(); +        uris.add(inputUri); +        DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(uris); + +        getSupportFragmentManager().beginTransaction() +                .replace(R.id.decrypt_files_fragment_container, frag) +                .addToBackStack("list") +                .commit();      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java deleted file mode 100644 index bc7705233..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ /dev/null @@ -1,225 +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.annotation.SuppressLint; -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.service.KeychainService; -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.util.Notify; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.File; - -public class DecryptFilesFragment extends DecryptFragment { -    public static final String ARG_URI = "uri"; -    public static final String ARG_OPEN_DIRECTLY = "open_directly"; - -    private static final int REQUEST_CODE_INPUT = 0x00007003; -    private static final int REQUEST_CODE_OUTPUT = 0x00007007; - -    // view -    private TextView mFilename; -    private CheckBox mDeleteAfter; -    private View mDecryptButton; - -    // model -    private Uri mInputUri = null; -    private Uri mOutputUri = null; - -    /** -     * Creates new instance of this fragment -     */ -    public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) { -        DecryptFilesFragment frag = new DecryptFilesFragment(); - -        Bundle args = new Bundle(); -        args.putParcelable(ARG_URI, uri); -        args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); - -        frag.setArguments(args); - -        return frag; -    } - -    /** -     * Inflate the layout for this fragment -     */ -    @Override -    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -        View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false); - -        mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); -        mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption); -        mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); -        view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { -            public void onClick(View v) { -                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { -                    FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); -                } else { -                    FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", -                            REQUEST_CODE_INPUT); -                } -            } -        }); -        mDecryptButton.setOnClickListener(new View.OnClickListener() { -            @Override -            public void onClick(View v) { -                decryptAction(); -            } -        }); - -        return view; -    } - -    @Override -    public void onSaveInstanceState(Bundle outState) { -        super.onSaveInstanceState(outState); - -        outState.putParcelable(ARG_URI, mInputUri); -    } - -    @Override -    public void onActivityCreated(Bundle savedInstanceState) { -        super.onActivityCreated(savedInstanceState); - -        Bundle state = savedInstanceState != null ? savedInstanceState : getArguments(); -        setInputUri(state.<Uri>getParcelable(ARG_URI)); - -        // should only come from args -        if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) { -            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { -                FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); -            } else { -                FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); -            } -        } -    } - -    private void setInputUri(Uri inputUri) { -        if (inputUri == null) { -            mInputUri = null; -            mFilename.setText(""); -            return; -        } - -        mInputUri = inputUri; -        mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); -    } - -    private void decryptAction() { -        if (mInputUri == null) { -            Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); -            return; -        } - -        cryptoOperation(); -    } - -    private String removeEncryptedAppend(String name) { -        if (name.endsWith(Constants.FILE_EXTENSION_ASC) -                || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) -                || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { -            return name.substring(0, name.length() - 4); -        } -        return name; -    } - -    private void askForOutputFilename(String originalFilename) { -        if (TextUtils.isEmpty(originalFilename)) { -            originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); -        } - -        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { -            File file = new File(mInputUri.getPath()); -            File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; -            File targetFile = new File(parentDir, originalFilename); -            FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), -                    getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); -        } else { -            FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT); -        } -    } - -    @Override -    public void onActivityResult(int requestCode, int resultCode, Intent data) { -        switch (requestCode) { -            case REQUEST_CODE_INPUT: { -                if (resultCode == Activity.RESULT_OK && data != null) { -                    setInputUri(data.getData()); -                } -                return; -            } - -            case REQUEST_CODE_OUTPUT: { -                // This happens after output file was selected, so start our operation -                if (resultCode == Activity.RESULT_OK && data != null) { -                    mOutputUri = data.getData(); -                    cryptoOperation(); -                } -                return; -            } - -            default: { -                super.onActivityResult(requestCode, resultCode, data); -            } -        } -    } - -    @Override -    protected void onVerifyLoaded(boolean hideErrorOverlay) { - -    } - -    @Override -    protected PgpDecryptVerifyInputParcel createOperationInput() { -        return new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri).setAllowSymmetricDecryption(true); -    } - -    @Override -    protected void onCryptoOperationSuccess(DecryptVerifyResult result) { - -        // display signature result in activity -        loadVerifyResult(result); - -        // TODO delete after decrypt not implemented! - -    } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java new file mode 100644 index 000000000..be00183b2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -0,0 +1,143 @@ +/* + * 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.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.FileHelper; + +public class DecryptFilesInputFragment extends Fragment { +    public static final String ARG_URI = "uri"; +    public static final String ARG_OPEN_DIRECTLY = "open_directly"; + +    private static final int REQUEST_CODE_INPUT = 0x00007003; + +    private TextView mFilename; +    private View mDecryptButton; + +    private Uri mInputUri = null; + +    public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { +        DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); + +        Bundle args = new Bundle(); +        args.putParcelable(ARG_URI, uri); +        args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly); + +        frag.setArguments(args); + +        return frag; +    } + +    @Override +    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +        View view = inflater.inflate(R.layout.decrypt_files_input_fragment, container, false); + +        // hide result view for this fragment +        getActivity().findViewById(R.id.result_main_layout).setVisibility(View.GONE); + +        mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); +        mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); +        view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { +            public void onClick(View v) { +                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { +                    FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); +                } else { +                    FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", +                            REQUEST_CODE_INPUT); +                } +            } +        }); +        mDecryptButton.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                decryptAction(); +            } +        }); + +        return view; +    } + +    @Override +    public void onSaveInstanceState(Bundle outState) { +        super.onSaveInstanceState(outState); + +        outState.putParcelable(ARG_URI, mInputUri); +    } + +    @Override +    public void onActivityCreated(Bundle savedInstanceState) { +        super.onActivityCreated(savedInstanceState); + +        Bundle state = savedInstanceState != null ? savedInstanceState : getArguments(); +        setInputUri(state.<Uri>getParcelable(ARG_URI)); + +        // should only come from args +        if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) { +            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { +                FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); +            } else { +                FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); +            } +        } +    } + +    private void setInputUri(Uri inputUri) { +        if (inputUri == null) { +            mInputUri = null; +            mFilename.setText(""); +            return; +        } + +        mInputUri = inputUri; +        mFilename.setText(FileHelper.getFilename(getActivity(), mInputUri)); +    } + +    private void decryptAction() { +        if (mInputUri == null) { +            Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); +            return; +        } + +        DecryptFilesActivity activity = (DecryptFilesActivity) getActivity(); +        activity.displayListFragment(mInputUri); +    } + +    @Override +    public void onActivityResult(int requestCode, int resultCode, Intent data) { +        if (requestCode != REQUEST_CODE_INPUT) { +            return; +        } + +        if (resultCode == Activity.RESULT_OK && data != null) { +            setInputUri(data.getData()); +        } +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java new file mode 100644 index 000000000..92790b98c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -0,0 +1,628 @@ +/* + * 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.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnDismissListener; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import org.openintents.openpgp.OpenPgpMetadata; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; +import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; +import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +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.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +public class DecryptFilesListFragment +        extends CryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult> +        implements OnMenuItemClickListener { +    public static final String ARG_URIS = "uris"; + +    private static final int REQUEST_CODE_OUTPUT = 0x00007007; + +    private ArrayList<Uri> mInputUris; +    private HashMap<Uri, Uri> mOutputUris; +    private ArrayList<Uri> mPendingInputUris; + +    private Uri mCurrentInputUri; + +    private DecryptFilesAdapter mAdapter; + +    /** +     * Creates new instance of this fragment +     */ +    public static DecryptFilesListFragment newInstance(ArrayList<Uri> uris) { +        DecryptFilesListFragment frag = new DecryptFilesListFragment(); + +        Bundle args = new Bundle(); +        args.putParcelableArrayList(ARG_URIS, uris); +        frag.setArguments(args); + +        return frag; +    } + +    /** +     * Inflate the layout for this fragment +     */ +    @Override +    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +        View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); + +        RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); + +        vFilesList.addItemDecoration(new SpacesItemDecoration( +                FormattingUtils.dpToPx(getActivity(), 4))); +        vFilesList.setHasFixedSize(true); +        vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); +        vFilesList.setItemAnimator(new DefaultItemAnimator()); + +        mAdapter = new DecryptFilesAdapter(getActivity(), this); +        vFilesList.setAdapter(mAdapter); + +        return view; +    } + +    @Override +    public void onSaveInstanceState(Bundle outState) { +        super.onSaveInstanceState(outState); + +        outState.putParcelableArrayList(ARG_URIS, mInputUris); +    } + +    @Override +    public void onActivityCreated(Bundle savedInstanceState) { +        super.onActivityCreated(savedInstanceState); + +        displayInputUris(getArguments().<Uri>getParcelableArrayList(ARG_URIS)); +    } + +    private String removeEncryptedAppend(String name) { +        if (name.endsWith(Constants.FILE_EXTENSION_ASC) +                || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) +                || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { +            return name.substring(0, name.length() - 4); +        } +        return name; +    } + +    private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { +        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; +            File targetFile = new File(parentDir, originalFilename); +            FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), +                    getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); +        } else { +            FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); +        } +    } + +    @Override +    public void onActivityResult(int requestCode, int resultCode, Intent data) { +        switch (requestCode) { +            case REQUEST_CODE_OUTPUT: { +                // This happens after output file was selected, so start our operation +                if (resultCode == Activity.RESULT_OK && data != null) { +                    Uri saveUri = data.getData(); +                    Uri outputUri = mOutputUris.get(mCurrentInputUri); +                    // TODO save from outputUri to saveUri + +                    mCurrentInputUri = null; +                } +                return; +            } + +            default: { +                super.onActivityResult(requestCode, resultCode, data); +            } +        } +    } + +    private void displayInputUris(ArrayList<Uri> uris) { +        mInputUris = uris; +        mOutputUris = new HashMap<>(uris.size()); +        for (Uri uri : uris) { +            mAdapter.add(uri); +            mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); +        } + +        mPendingInputUris = uris; + +        cryptoOperation(); +    } + +    @Override +    protected void onCryptoSetProgress(String msg, int progress, int max) { +        mAdapter.setProgress(mCurrentInputUri, progress, max, msg); +    } + +    @Override +    public void showProgressFragment( +            String progressDialogMessage, int progressDialogStyle, boolean cancelable) { +        // progress shown inline, so never mind +    } + +    @Override +    protected void dismissProgress() { +        // progress shown inline, so never mind +    } + +    @Override +    protected void onCryptoOperationError(DecryptVerifyResult result) { +        final Uri uri = mCurrentInputUri; +        mCurrentInputUri = null; + +        mAdapter.addResult(uri, result, null, null, null); +    } + +    @Override +    protected void onCryptoOperationSuccess(DecryptVerifyResult result) { +        final Uri uri = mCurrentInputUri; +        mCurrentInputUri = null; + +        Drawable icon = null; +        OnClickListener onFileClick = null, onKeyClick = null; + +        if (result.getDecryptMetadata() != null && result.getDecryptMetadata().getMimeType() != null) { +            icon = loadIcon(result.getDecryptMetadata().getMimeType()); +        } + +        OpenPgpSignatureResult sigResult = result.getSignatureResult(); +        if (sigResult != null) { +            final long keyId = sigResult.getKeyId(); +            if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { +                onKeyClick = new OnClickListener() { +                    @Override +                    public void onClick(View view) { +                        Activity activity = getActivity(); +                        if (activity == null) { +                            return; +                        } +                        Intent intent = new Intent(activity, ViewKeyActivity.class); +                        intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); +                        activity.startActivity(intent); +                    } +                }; +            } +        } + +        if (result.success() && result.getDecryptMetadata() != null) { +            final OpenPgpMetadata metadata = result.getDecryptMetadata(); +            onFileClick = new OnClickListener() { +                @Override +                public void onClick(View view) { +                    Activity activity = getActivity(); +                    if (activity == null || mCurrentInputUri != null) { +                        return; +                    } + +                    Uri outputUri = mOutputUris.get(uri); +                    Intent intent = new Intent(); +                    intent.setDataAndType(outputUri, metadata.getMimeType()); +                    activity.startActivity(intent); +                } +            }; +        } + +        mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + +    } + +    @Override +    protected PgpDecryptVerifyInputParcel createOperationInput() { + +        if (mCurrentInputUri == null) { +            if (mPendingInputUris.isEmpty()) { +                // nothing left to do +                return null; +            } + +            mCurrentInputUri = mPendingInputUris.remove(0); +        } + +        Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); +        Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); + +        return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) +                .setAllowSymmetricDecryption(true); + +    } + +    @Override +    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { +        super.onCreateContextMenu(menu, v, menuInfo); +    } + +    @Override +    public boolean onMenuItemClick(MenuItem menuItem) { +        if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { +            return false; +        } +        Activity activity = getActivity(); +        if (activity == null) { +            return false; +        } + +        ViewModel model = mAdapter.mMenuClickedModel; +        DecryptVerifyResult result = model.mResult; +        switch (menuItem.getItemId()) { +            case R.id.view_log: +                Intent intent = new Intent(activity, LogDisplayActivity.class); +                intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); +                activity.startActivity(intent); +                return true; +            case R.id.decrypt_save: +                OpenPgpMetadata metadata = result.getDecryptMetadata(); +                if (metadata == null) { +                    return true; +                } +                mCurrentInputUri = model.mInputUri; +                askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); +                return true; +            case R.id.decrypt_delete: +                Notify.create(activity, "decrypt/delete not yet implemented", Style.ERROR).show(this); +                return true; +        } +        return false; +    } + +    public static class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> { +        private Context mContext; +        private ArrayList<ViewModel> mDataset; +        private OnMenuItemClickListener mMenuItemClickListener; +        private ViewModel mMenuClickedModel; + +        public class ViewModel { +            Context mContext; +            Uri mInputUri; +            DecryptVerifyResult mResult; +            Drawable mIcon; + +            OnClickListener mOnFileClickListener; +            OnClickListener mOnKeyClickListener; + +            int mProgress, mMax; +            String mProgressMsg; + +            ViewModel(Context context, Uri uri) { +                mContext = context; +                mInputUri = uri; +                mProgress = 0; +                mMax = 100; +            } + +            void addResult(DecryptVerifyResult result) { +                mResult = result; +            } + +            void addIcon(Drawable icon) { +                mIcon = icon; +            } + +            void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { +                mOnFileClickListener = onFileClick; +                mOnKeyClickListener = onKeyClick; +            } + +            boolean hasResult() { +                return mResult != null; +            } + +            void setProgress(int progress, int max, String msg) { +                if (msg != null) { +                    mProgressMsg = msg; +                } +                mProgress = progress; +                mMax = max; +            } + +            // Depends on inputUri only +            @Override +            public boolean equals(Object o) { +                if (this == o) return true; +                if (o == null || getClass() != o.getClass()) return false; +                ViewModel viewModel = (ViewModel) o; +                return !(mResult != null ? !mResult.equals(viewModel.mResult) +                        : viewModel.mResult != null); +            } + +            // Depends on inputUri only +            @Override +            public int hashCode() { +                return mResult != null ? mResult.hashCode() : 0; +            } + +            @Override +            public String toString() { +                return mResult.toString(); +            } +        } + +        // Provide a suitable constructor (depends on the kind of dataset) +        public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) { +            mContext = context; +            mMenuItemClickListener = menuItemClickListener; +            mDataset = new ArrayList<>(); +        } + +        // Create new views (invoked by the layout manager) +        @Override +        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { +            //inflate your layout and pass it to view holder +            View v = LayoutInflater.from(parent.getContext()) +                    .inflate(R.layout.decrypt_list_entry, parent, false); +            return new ViewHolder(v); +        } + +        // Replace the contents of a view (invoked by the layout manager) +        @Override +        public void onBindViewHolder(ViewHolder holder, final int position) { +            // - get element from your dataset at this position +            // - replace the contents of the view with that element +            final ViewModel model = mDataset.get(position); + +            if (model.hasResult()) { +                if (holder.vAnimator.getDisplayedChild() != 1) { +                    holder.vAnimator.setDisplayedChild(1); +                } + +                KeyFormattingUtils.setStatus(mContext, holder, model.mResult); + +                OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); +                holder.vFilename.setText(metadata.getFilename()); + +                long size = metadata.getOriginalSize(); +                if (size == -1 || size == 0) { +                    holder.vFilesize.setText(""); +                } else { +                    holder.vFilesize.setText(FileHelper.readableFileSize(size)); +                } + +                // TODO thumbnail from OpenPgpMetadata +                if (model.mIcon != null) { +                    holder.vThumbnail.setImageDrawable(model.mIcon); +                } else { +                    holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); +                } + +                holder.vFile.setOnClickListener(model.mOnFileClickListener); +                holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + +                holder.vContextMenu.setTag(model); +                holder.vContextMenu.setOnClickListener(new OnClickListener() { +                    @Override +                    public void onClick(View view) { +                        mMenuClickedModel = model; +                        PopupMenu menu = new PopupMenu(mContext, view); +                        menu.inflate(R.menu.decrypt_item_context_menu); +                        menu.setOnMenuItemClickListener(mMenuItemClickListener); +                        menu.setOnDismissListener(new OnDismissListener() { +                            @Override +                            public void onDismiss(PopupMenu popupMenu) { +                                mMenuClickedModel = null; +                            } +                        }); +                        menu.show(); +                    } +                }); + +            } else { +                if (holder.vAnimator.getDisplayedChild() != 0) { +                    holder.vAnimator.setDisplayedChild(0); +                } + +                holder.vProgress.setProgress(model.mProgress); +                holder.vProgress.setMax(model.mMax); +                holder.vProgressMsg.setText(model.mProgressMsg); +            } + +        } + +        // Return the size of your dataset (invoked by the layout manager) +        @Override +        public int getItemCount() { +            return mDataset.size(); +        } + +        public void add(Uri uri) { +            ViewModel newModel = new ViewModel(mContext, uri); +            mDataset.add(newModel); +            notifyItemInserted(mDataset.size()); +        } + +        public void setProgress(Uri uri, int progress, int max, String msg) { +            ViewModel newModel = new ViewModel(mContext, uri); +            int pos = mDataset.indexOf(newModel); +            mDataset.get(pos).setProgress(progress, max, msg); +            notifyItemChanged(pos); +        } + +        public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, +                OnClickListener onFileClick, OnClickListener onKeyClick) { + +            ViewModel model = new ViewModel(mContext, uri); +            int pos = mDataset.indexOf(model); +            model = mDataset.get(pos); + +            model.addResult(result); +            if (icon != null) { +                model.addIcon(icon); +            } +            model.setOnClickListeners(onFileClick, onKeyClick); + +            notifyItemChanged(pos); +        } + +    } + + +    // Provide a reference to the views for each data item +    // Complex data items may need more than one view per item, and +    // you provide access to all the views for a data item in a view holder +    public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder { +        public ViewAnimator vAnimator; + +        public ProgressBar vProgress; +        public TextView vProgressMsg; + +        public View vFile; +        public TextView vFilename; +        public TextView vFilesize; +        public ImageView vThumbnail; + +        public ImageView vEncStatusIcon; +        public TextView vEncStatusText; + +        public ImageView vSigStatusIcon; +        public TextView vSigStatusText; +        public View vSignatureLayout; +        public TextView vSignatureName; +        public TextView vSignatureMail; +        public TextView vSignatureAction; + +        public View vContextMenu; + +        public ViewHolder(View itemView) { +            super(itemView); + +            vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator); + +            vProgress = (ProgressBar) itemView.findViewById(R.id.progress); +            vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); + +            vFile = itemView.findViewById(R.id.file); +            vFilename = (TextView) itemView.findViewById(R.id.filename); +            vFilesize = (TextView) itemView.findViewById(R.id.filesize); +            vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + +            vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); +            vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); + +            vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); +            vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); +            vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); +            vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); +            vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); +            vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + +            vContextMenu = itemView.findViewById(R.id.context_menu); + +        } + +        @Override +        public ImageView getEncryptionStatusIcon() { +            return vEncStatusIcon; +        } + +        @Override +        public TextView getEncryptionStatusText() { +            return vEncStatusText; +        } + +        @Override +        public ImageView getSignatureStatusIcon() { +            return vSigStatusIcon; +        } + +        @Override +        public TextView getSignatureStatusText() { +            return vSigStatusText; +        } + +        @Override +        public View getSignatureLayout() { +            return vSignatureLayout; +        } + +        @Override +        public TextView getSignatureAction() { +            return vSignatureAction; +        } + +        @Override +        public TextView getSignatureUserName() { +            return vSignatureName; +        } + +        @Override +        public TextView getSignatureUserEmail() { +            return vSignatureMail; +        } + +        @Override +        public boolean hasEncrypt() { +            return true; +        } +    } + +    private Drawable loadIcon(String mimeType) { +        final Intent intent = new Intent(Intent.ACTION_VIEW); +        intent.setType(mimeType); + +        final List<ResolveInfo> matches = getActivity() +                .getPackageManager().queryIntentActivities(intent, 0); +        //noinspection LoopStatementThatDoesntLoop +        for (ResolveInfo match : matches) { +            return match.loadIcon(getActivity().getPackageManager()); +        } +        return null; + +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 637a7dcff..e21589bcd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -213,5 +213,4 @@ public abstract class CryptoOperationFragment <T extends Parcelable, S extends O      } -  } 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 dd85a6e46..11524aa08 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 @@ -24,9 +24,11 @@ import android.graphics.PorterDuff;  import android.text.Spannable;  import android.text.SpannableStringBuilder;  import android.text.style.ForegroundColorSpan; +import android.view.View;  import android.widget.ImageView;  import android.widget.TextView; +import org.openintents.openpgp.OpenPgpSignatureResult;  import org.spongycastle.asn1.ASN1ObjectIdentifier;  import org.spongycastle.asn1.nist.NISTNamedCurves;  import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; @@ -34,6 +36,8 @@ import org.spongycastle.bcpg.PublicKeyAlgorithmTags;  import org.spongycastle.util.encoders.Hex;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.KeyRing;  import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;  import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;  import org.sufficientlysecure.keychain.util.Log; @@ -376,7 +380,6 @@ public class KeyFormattingUtils {      /**       * Converts the given bytes to a unique RGB color using SHA1 algorithm       * -     * @param bytes       * @return an integer array containing 3 numeric color representations (Red, Green, Black)       * @throws java.security.NoSuchAlgorithmException       * @throws java.security.DigestException @@ -394,7 +397,7 @@ public class KeyFormattingUtils {      public static final int DEFAULT_COLOR = -1; -    public static enum State { +    public enum State {          REVOKED,          EXPIRED,          VERIFIED, @@ -420,9 +423,165 @@ public class KeyFormattingUtils {          setStatusImage(context, statusIcon, statusText, state, color, false);      } +    public interface StatusHolder { +        ImageView getEncryptionStatusIcon(); +        TextView getEncryptionStatusText(); + +        ImageView getSignatureStatusIcon(); +        TextView getSignatureStatusText(); + +        View getSignatureLayout(); +        TextView getSignatureUserName(); +        TextView getSignatureUserEmail(); +        TextView getSignatureAction(); + +        boolean hasEncrypt(); + +    } + +    @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated +    public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) { + +        OpenPgpSignatureResult signatureResult = result.getSignatureResult(); + +        if (holder.hasEncrypt()) { +            int encText, encIcon, encColor; +            if (signatureResult != null && signatureResult.isSignatureOnly()) { +                encIcon = R.drawable.status_lock_open_24dp; +                encText = R.string.decrypt_result_not_encrypted; +                encColor = R.color.android_red_light; +            } else { +                encIcon = R.drawable.status_lock_closed_24dp; +                encText = R.string.decrypt_result_encrypted; +                encColor = R.color.android_green_light; +            } + +            int encColorRes = context.getResources().getColor(encColor); +            holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon)); +            holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN); +            holder.getEncryptionStatusText().setText(encText); +            holder.getEncryptionStatusText().setTextColor(encColorRes); +        } + +        int sigText, sigIcon, sigColor; +        int sigActionText, sigActionIcon; + +        if (signatureResult == null) { + +            sigText = R.string.decrypt_result_no_signature; +            sigIcon = R.drawable.status_signature_invalid_cutout_24dp; +            sigColor = R.color.bg_gray; + +            // won't be used, but makes compiler happy +            sigActionText = 0; +            sigActionIcon = 0; + +        } else switch (signatureResult.getStatus()) { + +            case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { +                sigText = R.string.decrypt_result_signature_certified; +                sigIcon = R.drawable.status_signature_verified_cutout_24dp; +                sigColor = R.color.android_green_light; + +                sigActionText = R.string.decrypt_result_action_show; +                sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; +                break; +            } + +            case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { +                sigText = R.string.decrypt_result_signature_uncertified; +                sigIcon = R.drawable.status_signature_unverified_cutout_24dp; +                sigColor = R.color.android_orange_light; + +                sigActionText = R.string.decrypt_result_action_show; +                sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; +                break; +            } + +            case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { +                sigText = R.string.decrypt_result_signature_revoked_key; +                sigIcon = R.drawable.status_signature_revoked_cutout_24dp; +                sigColor = R.color.android_red_light; + +                sigActionText = R.string.decrypt_result_action_show; +                sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; +                break; +            } + +            case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { +                sigText = R.string.decrypt_result_signature_expired_key; +                sigIcon = R.drawable.status_signature_expired_cutout_24dp; +                sigColor = R.color.android_red_light; + +                sigActionText = R.string.decrypt_result_action_show; +                sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; +                break; +            } + +            case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { +                sigText = R.string.decrypt_result_signature_missing_key; +                sigIcon = R.drawable.status_signature_unknown_cutout_24dp; +                sigColor = R.color.android_red_light; + +                sigActionText = R.string.decrypt_result_action_Lookup; +                sigActionIcon = R.drawable.ic_file_download_grey_24dp; +                break; +            } + +            default: +            case OpenPgpSignatureResult.SIGNATURE_ERROR: { +                sigText = R.string.decrypt_result_invalid_signature; +                sigIcon = R.drawable.status_signature_invalid_cutout_24dp; +                sigColor = R.color.android_red_light; + +                sigActionText = R.string.decrypt_result_action_show; +                sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; +                break; +            } + +        } + +        int sigColorRes = context.getResources().getColor(sigColor); +        holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); +        holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); +        holder.getSignatureStatusText().setText(sigText); +        holder.getSignatureStatusText().setTextColor(sigColorRes); + +        if (signatureResult != null) { + +            holder.getSignatureLayout().setVisibility(View.VISIBLE); + +            holder.getSignatureAction().setText(sigActionText); +            holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds( +                    0, 0, sigActionIcon, 0); + +            String userId = signatureResult.getPrimaryUserId(); +            KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); +            if (userIdSplit.name != null) { +                holder.getSignatureUserName().setText(userIdSplit.name); +            } else { +                holder.getSignatureUserName().setText(R.string.user_id_no_name); +            } +            if (userIdSplit.email != null) { +                holder.getSignatureUserEmail().setVisibility(View.VISIBLE); +                holder.getSignatureUserEmail().setText(userIdSplit.email); +            } else { +                holder.getSignatureUserEmail().setVisibility(View.GONE); +            } + +        } else { + +            holder.getSignatureLayout().setVisibility(View.GONE); + +        } + + +    } +      /**       * Sets status image based on constant       */ +    @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21      public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText,                                        State state, int color, boolean big) {          switch (state) { | 
