From dbfa55f6b963ff8c5a975c45a2805838eb1781f7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Jun 2015 00:05:55 +0200 Subject: [PATCH 01/67] introduce CachingDataDecryptorFactory towards cached session keys this commit introduces the CachingDataDecryptorFactory, which wraps a DataDecryptorFactory but supports caching of decrypted session keys. this change also gets rid of runtimeexception based control flow in PgpDecryptVerify. --- .../jcajce/CachingDataDecryptorFactory.java | 76 +++++ ...cPublicKeyDataDecryptorFactoryBuilder.java | 278 ------------------ .../results/DecryptVerifyResult.java | 13 + .../keychain/pgp/CanonicalizedSecretKey.java | 23 +- .../keychain/pgp/PgpDecryptVerify.java | 38 ++- .../service/input/CryptoInputParcel.java | 7 +- ...nt.java => DecryptFilesInputFragment.java} | 29 +- ...t.xml => decrypt_files_input_fragment.xml} | 0 8 files changed, 145 insertions(+), 319 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java delete mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/{DecryptFilesFragment.java => DecryptFilesInputFragment.java} (91%) rename OpenKeychain/src/main/res/layout/{decrypt_files_fragment.xml => decrypt_files_input_fragment.xml} (100%) 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 mSessionKeyCache; + + private OperatorHelper mOperatorHelper; + + public CachingDataDecryptorFactory(String providerName, + final Map sessionKeyCache) + { + mWrappedDecryptor = null; + mSessionKeyCache = sessionKeyCache; + + mOperatorHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + } + + public CachingDataDecryptorFactory(PublicKeyDataDecryptorFactory wrapped, + final Map 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 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 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 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 CREATOR = new Creator() { 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 4382c9fae..235b0a671 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -24,6 +24,7 @@ import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; @@ -40,11 +41,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; @@ -541,24 +541,33 @@ public class PgpDecryptVerify extends BaseOperation { 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 @@ -821,6 +830,7 @@ public class PgpDecryptVerify extends BaseOperation { // 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/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 cachedSessionKeys) { + mCryptoData.putAll(cachedSessionKeys); + } + public Map 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/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java similarity index 91% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 254b926d4..3f15376cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.File; -public class DecryptFilesFragment extends DecryptFragment { +public class DecryptFilesInputFragment extends DecryptFragment { public static final String ARG_URI = "uri"; public static final String ARG_OPEN_DIRECTLY = "open_directly"; @@ -69,8 +69,8 @@ public class DecryptFilesFragment extends DecryptFragment { /** * Creates new instance of this fragment */ - public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) { - DecryptFilesFragment frag = new DecryptFilesFragment(); + public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { + DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_URI, uri); @@ -94,9 +94,9 @@ public class DecryptFilesFragment extends DecryptFragment { 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); + FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", + FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); } } @@ -128,9 +128,9 @@ public class DecryptFilesFragment extends DecryptFragment { // 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); + FileHelper.openDocument(DecryptFilesInputFragment.this, "*/*", REQUEST_CODE_INPUT); } else { - FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); + FileHelper.openFile(DecryptFilesInputFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT); } } } @@ -180,6 +180,10 @@ public class DecryptFilesFragment extends DecryptFragment { } } + private void displayMetadata(DecryptVerifyResult result) { + + } + private void startDecrypt() { mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY; cryptoOperation(new CryptoInputParcel()); @@ -233,18 +237,19 @@ public class DecryptFilesFragment extends DecryptFragment { // get returned data bundle Bundle returnData = message.getData(); - DecryptVerifyResult pgpResult = + DecryptVerifyResult result = returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - if (pgpResult.success()) { + if (result.success()) { switch (mCurrentCryptoOperation) { case KeychainIntentService.ACTION_DECRYPT_METADATA: { - askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); + displayMetadata(result); + // askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); break; } case KeychainIntentService.ACTION_DECRYPT_VERIFY: { // display signature result in activity - loadVerifyResult(pgpResult); + loadVerifyResult(result); if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file @@ -269,7 +274,7 @@ public class DecryptFilesFragment extends DecryptFragment { } } } - pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this); + result.createNotify(getActivity()).show(DecryptFilesInputFragment.this); } } diff --git a/OpenKeychain/src/main/res/layout/decrypt_files_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml similarity index 100% rename from OpenKeychain/src/main/res/layout/decrypt_files_fragment.xml rename to OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml From cee1a8c7531904b55d13bda972b79e8b7c7ed67c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Jun 2015 03:23:51 +0200 Subject: [PATCH 02/67] multi-decrypt: first steps, split up DecryptFilesFragment --- .../keychain/ui/DecryptFilesActivity.java | 19 +- .../ui/DecryptFilesInputFragment.java | 138 +----- .../keychain/ui/DecryptFilesListFragment.java | 398 ++++++++++++++++++ .../layout/decrypt_files_input_fragment.xml | 202 +++------ .../layout/decrypt_files_list_fragment.xml | 51 +++ 5 files changed, 555 insertions(+), 253 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java create mode 100644 OpenKeychain/src/main/res/layout/decrypt_files_list_fragment.xml 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..f08ee5d3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -21,15 +21,15 @@ 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; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.util.Log; + public class DecryptFilesActivity extends BaseActivity { @@ -94,13 +94,24 @@ 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, DecryptVerifyResult result) { + + DecryptFilesListFragment frag = DecryptFilesListFragment.newInstance(inputUri, result); + + getSupportFragmentManager().beginTransaction() + .replace(R.id.decrypt_files_fragment_container, frag) + .addToBackStack("list") + .commit(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java index 3f15376cb..25494e0d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesInputFragment.java @@ -26,11 +26,9 @@ 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; @@ -40,35 +38,26 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; 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.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; 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 DecryptFilesInputFragment extends DecryptFragment { +public class DecryptFilesInputFragment extends CryptoOperationFragment { 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; - private String mCurrentCryptoOperation; - - /** - * Creates new instance of this fragment - */ public static DecryptFilesInputFragment newInstance(Uri uri, boolean openDirectly) { DecryptFilesInputFragment frag = new DecryptFilesInputFragment(); @@ -81,15 +70,14 @@ public class DecryptFilesInputFragment extends DecryptFragment { 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); + 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); - 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) { @@ -152,45 +140,6 @@ public class DecryptFilesInputFragment extends DecryptFragment { return; } - startDecryptFilenames(); - } - - 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); - } - } - - private void displayMetadata(DecryptVerifyResult result) { - - } - - private void startDecrypt() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY; - cryptoOperation(new CryptoInputParcel()); - } - - private void startDecryptFilenames() { - mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA; cryptoOperation(new CryptoInputParcel()); } @@ -203,7 +152,7 @@ public class DecryptFilesInputFragment extends DecryptFragment { // fill values for this action Bundle data = new Bundle(); // use current operation, either decrypt metadata or decrypt payload - intent.setAction(mCurrentCryptoOperation); + intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); // data @@ -240,41 +189,18 @@ public class DecryptFilesInputFragment extends DecryptFragment { DecryptVerifyResult result = returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - if (result.success()) { - switch (mCurrentCryptoOperation) { - case KeychainIntentService.ACTION_DECRYPT_METADATA: { - displayMetadata(result); - // askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); - break; - } - case KeychainIntentService.ACTION_DECRYPT_VERIFY: { - // display signature result in activity - loadVerifyResult(result); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); - setInputUri(null); - } - - /* - // A future open after decryption feature - if () { - Intent viewFile = new Intent(Intent.ACTION_VIEW); - viewFile.setInputData(mOutputUri); - startActivity(viewFile); - } - */ - break; - } - default: { - Log.e(Constants.TAG, "Bug: not supported operation!"); - break; - } - } + DecryptFilesActivity activity = ((DecryptFilesActivity) getActivity()); + if (activity == null) { + // nothing we can do + return; } - result.createNotify(getActivity()).show(DecryptFilesInputFragment.this); + + if (result.success()) { + activity.displayListFragment(mInputUri, result); + return; + } + result.createNotify(activity).show(DecryptFilesInputFragment.this); + } } @@ -293,31 +219,13 @@ public class DecryptFilesInputFragment extends DecryptFragment { @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; - } + if (requestCode != REQUEST_CODE_INPUT) { + 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(); - startDecrypt(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } + if (resultCode == Activity.RESULT_OK && data != null) { + setInputUri(data.getData()); } } - @Override - protected void onVerifyLoaded(boolean hideErrorOverlay) { - - } } 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..e86275b5d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesListFragment.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.OpenPgpMetadata; +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.KeychainIntentService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.DecryptFilesListFragment.DecryptFilesAdapter.ViewModel; +import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + + +public class DecryptFilesListFragment extends DecryptFragment { + public static final String ARG_URI = "uri"; + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + + private Uri mInputUri = null; + private DecryptVerifyResult mResult; + + private Uri mOutputUri = null; + private RecyclerView mFilesList; + private DecryptFilesAdapter mAdapter; + + /** + * Creates new instance of this fragment + */ + public static DecryptFilesListFragment newInstance(Uri uri, DecryptVerifyResult result) { + DecryptFilesListFragment frag = new DecryptFilesListFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_URI, uri); + args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result); + + 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); + + mFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); + + mFilesList.addItemDecoration(new SpacesItemDecoration( + FormattingUtils.dpToPx(getActivity(), 4))); + // mFilesList.setHasFixedSize(true); + mFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); + mFilesList.setItemAnimator(new DefaultItemAnimator()); + + mAdapter = new DecryptFilesAdapter(getActivity(), new ArrayList()); + mFilesList.setAdapter(mAdapter); + + 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 = getArguments(); + mInputUri = state.getParcelable(ARG_URI); + + if (savedInstanceState == null) { + displayMetadata(state.getParcelable(ARG_DECRYPT_VERIFY_RESULT)); + } + + } + + 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); + } + } + + private void displayMetadata(DecryptVerifyResult result) { + loadVerifyResult(result); + + OpenPgpMetadata metadata = result.getDecryptMetadata(); + mAdapter.add(metadata); + mFilesList.requestFocus(); + + } + + @Override + @SuppressLint("HandlerLeak") + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + // 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(); + // use current operation, either decrypt metadata or decrypt payload + intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); + + // data + + Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); + + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri) + .setAllowSymmetricDecryption(true); + + data.putParcelable(KeychainIntentService.DECRYPT_VERIFY_PARCEL, input); + data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput); + + 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) { + @Override + 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 result = + returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); + + if (result.success()) { + // display signature result in activity + loadVerifyResult(result); + + /* + // A future open after decryption feature + if () { + Intent viewFile = new Intent(Intent.ACTION_VIEW); + viewFile.setInputData(mOutputUri); + startActivity(viewFile); + } + */ + } + result.createNotify(getActivity()).show(DecryptFilesListFragment.this); + } + + } + }; + + // 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); + } + + @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) { + mOutputUri = data.getData(); + // startDecrypt(); + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + protected void onVerifyLoaded(boolean hideErrorOverlay) { + + } + + public static class DecryptFilesAdapter extends RecyclerView.Adapter { + private Context mContext; + private List mDataset; + + public static class ViewModel { + OpenPgpMetadata mMetadata; + Bitmap thumbnail; + + ViewModel(Context context, OpenPgpMetadata metadata) { + mMetadata = metadata; + int px = FormattingUtils.dpToPx(context, 48); + // this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px)); + } + + // 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 !(mMetadata != null ? !mMetadata.equals(viewModel.mMetadata) + : viewModel.mMetadata != null); + } + + // Depends on inputUri only + @Override + public int hashCode() { + return mMetadata != null ? mMetadata.hashCode() : 0; + } + + @Override + public String toString() { + return mMetadata.toString(); + } + } + + // 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 + class ViewHolder extends RecyclerView.ViewHolder { + public TextView filename; + public TextView fileSize; + public View removeButton; + public ImageView thumbnail; + + public ViewHolder(View itemView) { + super(itemView); + filename = (TextView) itemView.findViewById(R.id.filename); + fileSize = (TextView) itemView.findViewById(R.id.filesize); + removeButton = itemView.findViewById(R.id.action_remove_file_from_list); + thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + } + } + + // Provide a suitable constructor (depends on the kind of dataset) + public DecryptFilesAdapter(Context context, List myDataset) { + mContext = context; + mDataset = myDataset; + } + + // Create new views (invoked by the layout manager) + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + //inflate your layout and pass it to view holder + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.file_list_entry, parent, false); + return new ViewHolder(v); + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { + ViewHolder thisHolder = (ViewHolder) holder; + // - get element from your dataset at this position + // - replace the contents of the view with that element + final ViewModel model = mDataset.get(position); + + thisHolder.filename.setText(model.mMetadata.getFilename()); + + long size = model.mMetadata.getOriginalSize(); + if (size == -1) { + thisHolder.fileSize.setText(""); + } else { + thisHolder.fileSize.setText(FileHelper.readableFileSize(size)); + } + thisHolder.removeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + remove(model); + } + }); + + if (model.thumbnail != null) { + thisHolder.thumbnail.setImageBitmap(model.thumbnail); + } else { + thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + } + + // Return the size of your dataset (invoked by the layout manager) + @Override + public int getItemCount() { + return mDataset.size(); + } + + public void add(OpenPgpMetadata metadata) { + ViewModel newModel = new ViewModel(mContext, metadata); + mDataset.add(newModel); + notifyItemInserted(mDataset.size()); + } + + public void addAll(ArrayList metadatas) { + if (metadatas != null) { + int startIndex = mDataset.size(); + for (OpenPgpMetadata metadata : metadatas) { + ViewModel newModel = new ViewModel(mContext, metadata); + if (mDataset.contains(newModel)) { + Log.e(Constants.TAG, "Skipped duplicate " + metadata); + } else { + mDataset.add(newModel); + } + } + notifyItemRangeInserted(startIndex, mDataset.size() - startIndex); + } + } + + public void remove(ViewModel model) { + int position = mDataset.indexOf(model); + mDataset.remove(position); + notifyItemRemoved(position); + } + + } + +} diff --git a/OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml b/OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml index 22ee7e09f..b7e70ce10 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_files_input_fragment.xml @@ -2,148 +2,82 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:id="@+id/decrypt_files_browse" + android:clickable="true" + android:background="?android:selectableItemBackground"> + android:text="@string/label_file_colon" + android:gravity="center_vertical" /> + + -