diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
28 files changed, 822 insertions, 333 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 7d1af704d..755d74ac2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -66,7 +66,7 @@ public final class Constants { public static final String LANGUAGE = "language"; public static final String KEY_SERVERS = "keyServers"; public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion"; - public static final String CONCEAL_PGP_APPLICATION = "concealPgpApplication"; + public static final String WRITE_VERSION_HEADER = "writeVersionHeader"; public static final String FIRST_TIME = "firstTime"; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index 491709354..72e88d793 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -195,13 +195,13 @@ public class Preferences { } } - public void setConcealPgpApplication(boolean conceal) { + public void setWriteVersionHeader(boolean conceal) { SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, conceal); + editor.putBoolean(Constants.Pref.WRITE_VERSION_HEADER, conceal); editor.commit(); } - public boolean getConcealPgpApplication() { - return mSharedPreferences.getBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, false); + public boolean getWriteVersionHeader() { + return mSharedPreferences.getBoolean(Constants.Pref.WRITE_VERSION_HEADER, false); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 75f8bdb66..a116ea665 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -65,6 +65,10 @@ public class OpenPgpSignatureResultBuilder { this.mSignatureAvailable = signatureAvailable; } + public boolean isValidSignature() { + return mValidSignature; + } + public OpenPgpSignatureResult build() { if (mSignatureAvailable) { OpenPgpSignatureResult result = new OpenPgpSignatureResult(); 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 7f2d971ed..7d113241b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.pgp; +import android.webkit.MimeTypeMap; + +import org.openintents.openpgp.OpenPgpMetadata; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; @@ -43,8 +46,10 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -52,6 +57,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URLConnection; import java.security.SignatureException; import java.util.Iterator; import java.util.Set; @@ -69,6 +75,7 @@ public class PgpDecryptVerify { private boolean mAllowSymmetricDecryption; private String mPassphrase; private Set<Long> mAllowedKeyIds; + private boolean mDecryptMetadataOnly; private PgpDecryptVerify(Builder builder) { // private Constructor can only be called from Builder @@ -81,6 +88,7 @@ public class PgpDecryptVerify { this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption; this.mPassphrase = builder.mPassphrase; this.mAllowedKeyIds = builder.mAllowedKeyIds; + this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; } public static class Builder { @@ -95,6 +103,7 @@ public class PgpDecryptVerify { private boolean mAllowSymmetricDecryption = true; private String mPassphrase = null; private Set<Long> mAllowedKeyIds = null; + private boolean mDecryptMetadataOnly = false; public Builder(ProviderHelper providerHelper, PassphraseCache passphraseCache, InputData data, OutputStream outStream) { @@ -124,7 +133,16 @@ public class PgpDecryptVerify { * This means only ciphertexts encrypted for one of these private key can be decrypted. */ public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) { - this.mAllowedKeyIds = allowedKeyIds; + mAllowedKeyIds = allowedKeyIds; + return this; + } + + /** + * If enabled, the actual decryption/verification of the content will not be executed. + * The metadata only will be decrypted and returned. + */ + public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) { + mDecryptMetadataOnly = decryptMetadataOnly; return this; } @@ -146,7 +164,8 @@ public class PgpDecryptVerify { } public interface PassphraseCache { - public String getCachedPassphrase(long masterKeyId); + public String getCachedPassphrase(long masterKeyId) + throws NoSecretKeyException; } public static class InvalidDataException extends Exception { @@ -227,8 +246,6 @@ public class PgpDecryptVerify { InputStream clear; PGPEncryptedData encryptedData; - currentProgress += 5; - PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; PGPPBEEncryptedData encryptedDataSymmetric = null; CanonicalizedSecretKey secretEncryptionKey = null; @@ -239,6 +256,7 @@ public class PgpDecryptVerify { while (it.hasNext()) { Object obj = it.next(); if (obj instanceof PGPPublicKeyEncryptedData) { + currentProgress += 2; updateProgress(R.string.progress_finding_key, currentProgress, 100); PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; @@ -269,8 +287,8 @@ public class PgpDecryptVerify { // allow only specific keys for decryption? if (mAllowedKeyIds != null) { - Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); - Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds); + Log.d(Constants.TAG, "encData.getKeyID(): " + encData.getKeyID()); + Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds); Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); if (!mAllowedKeyIds.contains(masterKeyId)) { @@ -325,6 +343,7 @@ public class PgpDecryptVerify { } if (symmetricPacketFound) { + currentProgress += 2; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() @@ -336,26 +355,23 @@ public class PgpDecryptVerify { clear = encryptedDataSymmetric.getDataStream(decryptorFactory); encryptedData = encryptedDataSymmetric; - currentProgress += 5; } else if (asymmetricPacketFound) { - currentProgress += 5; + currentProgress += 2; updateProgress(R.string.progress_extracting_key, currentProgress, 100); try { if (!secretEncryptionKey.unlock(mPassphrase)) { throw new WrongPassphraseException(); } - } catch(PgpGeneralException e) { + } catch (PgpGeneralException e) { throw new KeyExtractionException(); } - currentProgress += 5; + + currentProgress += 2; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); PublicKeyDataDecryptorFactory decryptorFactory = secretEncryptionKey.getDecryptorFactory(); - clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); - encryptedData = encryptedDataAsymmetric; - currentProgress += 5; } else { // no packet has been found where we have the corresponding secret key in our db throw new NoSecretKeyException(); @@ -369,18 +385,19 @@ public class PgpDecryptVerify { CanonicalizedPublicKey signingKey = null; if (dataChunk instanceof PGPCompressedData) { + currentProgress += 2; updateProgress(R.string.progress_decompressing_data, currentProgress, 100); - PGPObjectFactory fact = new PGPObjectFactory( - ((PGPCompressedData) dataChunk).getDataStream()); + PGPCompressedData compressedData = (PGPCompressedData) dataChunk; + + PGPObjectFactory fact = new PGPObjectFactory(compressedData.getDataStream()); dataChunk = fact.nextObject(); plainFact = fact; - currentProgress += 10; } PGPOnePassSignature signature = null; - if (dataChunk instanceof PGPOnePassSignatureList) { + currentProgress += 2; updateProgress(R.string.progress_processing_signature, currentProgress, 100); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; @@ -396,8 +413,7 @@ public class PgpDecryptVerify { signingKey = signingRing.getPublicKey(sigKeyId); signatureIndex = i; } catch (ProviderHelper.NotFoundException e) { - Log.d(Constants.TAG, "key not found!"); - // try next one... + Log.d(Constants.TAG, "key not found, trying next signature..."); } } @@ -410,8 +426,8 @@ public class PgpDecryptVerify { signatureResultBuilder.keyId(signingRing.getMasterKeyId()); try { signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback()); - } catch(PgpGeneralException e) { - Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId()); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "No primary user id in keyring with master key id " + signingRing.getMasterKeyId()); } signatureResultBuilder.signatureKeyCertified(signingRing.getVerified() > 0); @@ -429,56 +445,104 @@ public class PgpDecryptVerify { } dataChunk = plainFact.nextObject(); - currentProgress += 10; } if (dataChunk instanceof PGPSignatureList) { + // skip dataChunk = plainFact.nextObject(); } if (dataChunk instanceof PGPLiteralData) { + currentProgress += 4; updateProgress(R.string.progress_decrypting, currentProgress, 100); PGPLiteralData literalData = (PGPLiteralData) dataChunk; - byte[] buffer = new byte[1 << 16]; - InputStream dataIn = literalData.getInputStream(); + // TODO: how to get the real original size? + // this is the encrypted size so if we enable compression this value is wrong! + long originalSize = mData.getSize() - mData.getStreamPosition(); + if (originalSize < 0) { + originalSize = 0; + } + + String originalFilename = literalData.getFileName(); + String mimeType = null; + if (literalData.getFormat() == PGPLiteralData.TEXT + || literalData.getFormat() == PGPLiteralData.UTF8) { + mimeType = "text/plain"; + } else { + // TODO: better would be: https://github.com/open-keychain/open-keychain/issues/753 + + // try to guess from file ending + String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename); + if (extension != null) { + MimeTypeMap mime = MimeTypeMap.getSingleton(); + mimeType = mime.getMimeTypeFromExtension(extension); + } + if (mimeType == null) { + mimeType = URLConnection.guessContentTypeFromName(originalFilename); + } + if (mimeType == null) { + mimeType = "*/*"; + } + } + + OpenPgpMetadata metadata = new OpenPgpMetadata( + originalFilename, + mimeType, + literalData.getModificationTime().getTime(), + originalSize); + result.setDecryptMetadata(metadata); - int startProgress = currentProgress; - int endProgress = 100; + Log.d(Constants.TAG, "metadata: " + metadata); + + // return here if we want to decrypt the metadata only + if (mDecryptMetadataOnly) { + return result; + } + + int endProgress; if (signature != null) { endProgress = 90; } else if (encryptedData.isIntegrityProtected()) { endProgress = 95; + } else { + endProgress = 100; } + ProgressScaler progressScaler = + new ProgressScaler(mProgressable, currentProgress, endProgress, 100); - int n; - // TODO: progress calculation is broken here! Try to rework it based on commented code! -// int progress = 0; - long startPos = mData.getStreamPosition(); - while ((n = dataIn.read(buffer)) > 0) { - mOutStream.write(buffer, 0, n); -// progress += n; + InputStream dataIn = literalData.getInputStream(); + + long alreadyWritten = 0; + long wholeSize = mData.getSize() - mData.getStreamPosition(); + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = dataIn.read(buffer)) > 0) { + mOutStream.write(buffer, 0, length); + + // update signature buffer if signature is also present if (signature != null) { try { - signature.update(buffer, 0, n); + signature.update(buffer, 0, length); } catch (SignatureException e) { - Log.d(Constants.TAG, "SIGNATURE_ERROR"); + Log.e(Constants.TAG, "SignatureException -> Not a valid signature!", e); signatureResultBuilder.validSignature(false); signature = null; } } - // TODO: dead code?! - // unknown size, but try to at least have a moving, slowing down progress bar -// currentProgress = startProgress + (endProgress - startProgress) * progress -// / (progress + 100000); - if (mData.getSize() - startPos == 0) { - currentProgress = endProgress; + + alreadyWritten += length; + if (wholeSize > 0) { + long progress = 100 * alreadyWritten / wholeSize; + // stop at 100% for wrong file sizes... + if (progress > 100) { + progress = 100; + } + progressScaler.setProgress((int) progress, 100); } else { - currentProgress = (int) (startProgress + (endProgress - startProgress) - * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos)); + // TODO: slow annealing to fake a progress? } - updateProgress(currentProgress, 100); } if (signature != null) { @@ -510,8 +574,14 @@ public class PgpDecryptVerify { } } else { // no integrity check - Log.e(Constants.TAG, "Encrypted data was not integrity protected!"); - // TODO: inform user? + Log.d(Constants.TAG, "Encrypted data was not integrity protected! MDC packet is missing!"); + + // If no valid signature is present: + // Handle missing integrity protection like failed integrity protection! + // The MDC packet can be stripped by an attacker! + if (!signatureResultBuilder.isValidSignature()) { + throw new IntegrityCheckFailedException(); + } } updateProgress(R.string.progress_done, 100, 100); @@ -581,8 +651,7 @@ public class PgpDecryptVerify { signingKey = signingRing.getPublicKey(sigKeyId); signatureIndex = i; } catch (ProviderHelper.NotFoundException e) { - Log.d(Constants.TAG, "key not found!"); - // try next one... + Log.d(Constants.TAG, "key not found, trying next signature..."); } } @@ -597,8 +666,8 @@ public class PgpDecryptVerify { signatureResultBuilder.keyId(signingRing.getMasterKeyId()); try { signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback()); - } catch(PgpGeneralException e) { - Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId()); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "No primary user id in key with master key id " + signingRing.getMasterKeyId()); } signatureResultBuilder.signatureKeyCertified(signingRing.getVerified() > 0); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java index ad240e834..506f48c52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp; import android.os.Parcel; import android.os.Parcelable; +import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; public class PgpDecryptVerifyResult implements Parcelable { @@ -31,21 +32,22 @@ public class PgpDecryptVerifyResult implements Parcelable { long mKeyIdPassphraseNeeded; OpenPgpSignatureResult mSignatureResult; + OpenPgpMetadata mDecryptMetadata; public int getStatus() { return mStatus; } - public void setStatus(int mStatus) { - this.mStatus = mStatus; + public void setStatus(int status) { + mStatus = status; } public long getKeyIdPassphraseNeeded() { return mKeyIdPassphraseNeeded; } - public void setKeyIdPassphraseNeeded(long mKeyIdPassphraseNeeded) { - this.mKeyIdPassphraseNeeded = mKeyIdPassphraseNeeded; + public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) { + mKeyIdPassphraseNeeded = keyIdPassphraseNeeded; } public OpenPgpSignatureResult getSignatureResult() { @@ -53,7 +55,15 @@ public class PgpDecryptVerifyResult implements Parcelable { } public void setSignatureResult(OpenPgpSignatureResult signatureResult) { - this.mSignatureResult = signatureResult; + mSignatureResult = signatureResult; + } + + public OpenPgpMetadata getDecryptMetadata() { + return mDecryptMetadata; + } + + public void setDecryptMetadata(OpenPgpMetadata decryptMetadata) { + mDecryptMetadata = decryptMetadata; } public PgpDecryptVerifyResult() { @@ -64,6 +74,7 @@ public class PgpDecryptVerifyResult implements Parcelable { this.mStatus = b.mStatus; this.mKeyIdPassphraseNeeded = b.mKeyIdPassphraseNeeded; this.mSignatureResult = b.mSignatureResult; + this.mDecryptMetadata = b.mDecryptMetadata; } @@ -75,6 +86,7 @@ public class PgpDecryptVerifyResult implements Parcelable { dest.writeInt(mStatus); dest.writeLong(mKeyIdPassphraseNeeded); dest.writeParcelable(mSignatureResult, 0); + dest.writeParcelable(mDecryptMetadata, 0); } public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() { @@ -83,6 +95,7 @@ public class PgpDecryptVerifyResult implements Parcelable { vr.mStatus = source.readInt(); vr.mKeyIdPassphraseNeeded = source.readLong(); vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); + vr.mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader()); return vr; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 0ceefc4d9..1cf027721 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -60,11 +60,11 @@ public class PgpHelper { } } - public static String getFullVersion(Context context) { - if(Preferences.getPreferences(context).getConcealPgpApplication()){ - return ""; - } else { + public static String getVersionForHeader(Context context) { + if(Preferences.getPreferences(context).getWriteVersionHeader()){ return "OpenKeychain v" + getVersion(context); + } else { + return null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index 846b00ef2..f14eacda2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -230,7 +230,10 @@ public class PgpImportExport { progress++; // Create an output stream ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); - arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); + String version = PgpHelper.getVersionForHeader(mContext); + if (version != null) { + arOutStream.setHeader("Version", version); + } updateProgress(progress * 100 / masterKeyIdsSize, 100); @@ -258,7 +261,10 @@ public class PgpImportExport { progress++; // Create an output stream ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); - arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); + String version = PgpHelper.getVersionForHeader(mContext); + if (version != null) { + arOutStream.setHeader("Version", version); + } updateProgress(progress * 100 / masterKeyIdsSize, 100); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index 67eced699..901611982 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedReader; import java.io.IOException; @@ -71,6 +72,7 @@ public class PgpSignEncrypt { private String mSignaturePassphrase; private boolean mEncryptToSigner; private boolean mCleartextInput; + private String mOriginalFilename; private byte[] mNfcSignedHash = null; private Date mNfcCreationTimestamp = null; @@ -105,16 +107,17 @@ public class PgpSignEncrypt { this.mCleartextInput = builder.mCleartextInput; this.mNfcSignedHash = builder.mNfcSignedHash; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; + this.mOriginalFilename = builder.mOriginalFilename; } public static class Builder { // mandatory parameter private ProviderHelper mProviderHelper; - private String mVersionHeader; private InputData mData; private OutputStream mOutStream; // optional + private String mVersionHeader = null; private Progressable mProgressable = null; private boolean mEnableAsciiArmorOutput = false; private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; @@ -126,15 +129,19 @@ public class PgpSignEncrypt { private String mSignaturePassphrase = null; private boolean mEncryptToSigner = false; private boolean mCleartextInput = false; - + private String mOriginalFilename = ""; private byte[] mNfcSignedHash = null; private Date mNfcCreationTimestamp = null; - public Builder(ProviderHelper providerHelper, String versionHeader, InputData data, OutputStream outStream) { - this.mProviderHelper = providerHelper; - this.mVersionHeader = versionHeader; - this.mData = data; - this.mOutStream = outStream; + public Builder(ProviderHelper providerHelper, InputData data, OutputStream outStream) { + mProviderHelper = providerHelper; + mData = data; + mOutStream = outStream; + } + + public Builder setVersionHeader(String versionHeader) { + mVersionHeader = versionHeader; + return this; } public Builder setProgressable(Progressable progressable) { @@ -153,12 +160,12 @@ public class PgpSignEncrypt { } public Builder setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { - this.mEncryptionMasterKeyIds = encryptionMasterKeyIds; + mEncryptionMasterKeyIds = encryptionMasterKeyIds; return this; } public Builder setSymmetricPassphrase(String symmetricPassphrase) { - this.mSymmetricPassphrase = symmetricPassphrase; + mSymmetricPassphrase = symmetricPassphrase; return this; } @@ -183,11 +190,13 @@ public class PgpSignEncrypt { } /** + * Also encrypt with the signing keyring + * * @param encryptToSigner * @return */ public Builder setEncryptToSigner(boolean encryptToSigner) { - this.mEncryptToSigner = encryptToSigner; + mEncryptToSigner = encryptToSigner; return this; } @@ -198,7 +207,12 @@ public class PgpSignEncrypt { * @return */ public Builder setCleartextInput(boolean cleartextInput) { - this.mCleartextInput = cleartextInput; + mCleartextInput = cleartextInput; + return this; + } + + public Builder setOriginalFilename(String originalFilename) { + mOriginalFilename = originalFilename; return this; } @@ -278,6 +292,18 @@ public class PgpSignEncrypt { mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId; } + ArmoredOutputStream armorOut = null; + OutputStream out; + if (mEnableAsciiArmorOutput) { + armorOut = new ArmoredOutputStream(mOutStream); + if (mVersionHeader != null) { + armorOut.setHeader("Version", mVersionHeader); + } + out = armorOut; + } else { + out = mOutStream; + } + /* Get keys for signature generation for later usage */ CanonicalizedSecretKey signingKey = null; if (enableSignature) { @@ -314,7 +340,7 @@ public class PgpSignEncrypt { mSignatureHashAlgorithm = supported.getLast(); } } - updateProgress(R.string.progress_preparing_streams, 5, 100); + updateProgress(R.string.progress_preparing_streams, 2, 100); /* Initialize PGPEncryptedDataGenerator for later usage */ PGPEncryptedDataGenerator cPk = null; @@ -354,7 +380,7 @@ public class PgpSignEncrypt { /* Initialize signature generator object for later usage */ PGPSignatureGenerator signatureGenerator = null; if (enableSignature) { - updateProgress(R.string.progress_preparing_signature, 10, 100); + updateProgress(R.string.progress_preparing_signature, 4, 100); try { boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; @@ -366,16 +392,8 @@ public class PgpSignEncrypt { } } - ArmoredOutputStream armorOut = null; - OutputStream out; - if (mEnableAsciiArmorOutput) { - armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", mVersionHeader); - out = armorOut; - } else { - out = mOutStream; - } - + ProgressScaler progressScaler = + new ProgressScaler(mProgressable, 8, 95, 100); PGPCompressedDataGenerator compressGen = null; OutputStream pOut = null; OutputStream encryptionOut = null; @@ -383,6 +401,7 @@ public class PgpSignEncrypt { if (enableEncryption) { /* actual encryption */ + updateProgress(R.string.progress_encrypting, 8, 100); encryptionOut = cPk.open(out, new byte[1 << 16]); @@ -398,26 +417,25 @@ public class PgpSignEncrypt { } PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(), new byte[1 << 16]); - updateProgress(R.string.progress_encrypting, 20, 100); - long progress = 0; - int n; + long alreadyWritten = 0; + int length; byte[] buffer = new byte[1 << 16]; InputStream in = mData.getInputStream(); - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); + while ((length = in.read(buffer)) > 0) { + pOut.write(buffer, 0, length); // update signature buffer if signature is requested if (enableSignature) { - signatureGenerator.update(buffer, 0, n); + signatureGenerator.update(buffer, 0, length); } - progress += n; - if (mData.getSize() != 0) { - updateProgress((int) (20 + (95 - 20) * progress / mData.getSize()), 100); + alreadyWritten += length; + if (mData.getSize() > 0) { + long progress = 100 * alreadyWritten / mData.getSize(); + progressScaler.setProgress((int) progress, 100); } } @@ -425,7 +443,7 @@ public class PgpSignEncrypt { } else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) { /* cleartext signature: sign-only of ascii text */ - updateProgress(R.string.progress_signing, 40, 100); + updateProgress(R.string.progress_signing, 8, 100); // write -----BEGIN PGP SIGNED MESSAGE----- armorOut.beginClearText(mSignatureHashAlgorithm); @@ -436,6 +454,7 @@ public class PgpSignEncrypt { // update signature buffer with first line processLine(reader.readLine(), armorOut, signatureGenerator); + // TODO: progress: fake annealing? while (true) { String line = reader.readLine(); @@ -458,7 +477,7 @@ public class PgpSignEncrypt { } else if (enableSignature && !mCleartextInput) { /* sign-only binary (files/data stream) */ - updateProgress(R.string.progress_signing, 40, 100); + updateProgress(R.string.progress_signing, 8, 100); InputStream in = mData.getInputStream(); @@ -472,16 +491,22 @@ public class PgpSignEncrypt { signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(), new byte[1 << 16]); + long alreadyWritten = 0; + int length; byte[] buffer = new byte[1 << 16]; - int n; - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); + while ((length = in.read(buffer)) > 0) { + pOut.write(buffer, 0, length); + + signatureGenerator.update(buffer, 0, length); - signatureGenerator.update(buffer, 0, n); + alreadyWritten += length; + if (mData.getSize() > 0) { + long progress = 100 * alreadyWritten / mData.getSize(); + progressScaler.setProgress((int) progress, 100); + } } literalGen.close(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 18ddaa0ec..73a51942d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -204,7 +204,9 @@ public class UncachedKeyRing { public void encodeArmored(OutputStream out, String version) throws IOException { ArmoredOutputStream aos = new ArmoredOutputStream(out); - aos.setHeader("Version", version); + if (version != null) { + aos.setHeader("Version", version); + } aos.write(mRing.getEncoded()); aos.close(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 56168847f..dd59f8603 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -105,8 +105,9 @@ public class KeychainContract { public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; - public static final String IS_REVOKED = KeysColumns.IS_REVOKED; + public static final String IS_REVOKED = KeychainDatabase.Tables.KEYS + "." + KeysColumns.IS_REVOKED; public static final String VERIFIED = CertsColumns.VERIFIED; + public static final String IS_EXPIRED = "is_expired"; public static final String HAS_ANY_SECRET = "has_any_secret"; public static final String HAS_ENCRYPT = "has_encrypt"; public static final String HAS_SIGN = "has_sign"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index b651069e9..c914cb5b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -271,6 +271,9 @@ public class KeychainProvider extends ContentProvider { "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); projectionMap.put(KeyRings.HAS_SIGN, "kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN); + projectionMap.put(KeyRings.IS_EXPIRED, + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY + + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); qb.setProjectionMap(projectionMap); // Need this as list so we can search in it diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 6111a4ef4..a13bb9c98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -862,7 +862,10 @@ public class ProviderHelper { UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - keyRing.encodeArmored(bos, PgpHelper.getFullVersion(mContext)); + String version = PgpHelper.getVersionForHeader(mContext); + if (version != null) { + keyRing.encodeArmored(bos, version); + } String armoredKey = bos.toString("UTF-8"); Log.d(Constants.TAG, "armoredKey:" + armoredKey); @@ -925,7 +928,7 @@ public class ProviderHelper { mContentResolver.insert(uri, contentValueForApiAccounts(accSettings)); } - public void updateApiAccount(AccountSettings accSettings, Uri uri) { + public void updateApiAccount(Uri uri, AccountSettings accSettings) { if (mContentResolver.update(uri, contentValueForApiAccounts(accSettings), null, null) <= 0) { throw new RuntimeException(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index eae217e16..6bc623b85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import org.openintents.openpgp.IOpenPgpService; +import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; @@ -184,7 +185,13 @@ public class OpenPgpService extends RemoteService { if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); } else { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId()); + try { + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId()); + } catch (PassphraseCacheService.KeyNotFoundException e) { + // secret key that is set for this account is deleted? + // show account config again! + return getCreateAccountIntent(data, data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME)); + } } if (passphrase == null) { // get PendingIntent for passphrase input, add it to given params and return to client @@ -204,9 +211,9 @@ public class OpenPgpService extends RemoteService { // sign-only PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( new ProviderHelper(getContext()), - PgpHelper.getFullVersion(getContext()), inputData, os); builder.setEnableAsciiArmorOutput(asciiArmor) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) .setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) .setSignatureMasterKeyId(accSettings.getKeyId()) .setSignaturePassphrase(passphrase) @@ -257,6 +264,10 @@ public class OpenPgpService extends RemoteService { boolean sign) { try { boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); + if (originalFilename == null) { + originalFilename = ""; + } long[] keyIds; if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) { @@ -297,12 +308,13 @@ public class OpenPgpService extends RemoteService { PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( new ProviderHelper(getContext()), - PgpHelper.getFullVersion(getContext()), inputData, os); builder.setEnableAsciiArmorOutput(asciiArmor) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) .setCompressionId(accSettings.getCompression()) .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) - .setEncryptionMasterKeyIds(keyIds); + .setEncryptionMasterKeyIds(keyIds) + .setOriginalFilename(originalFilename); if (sign) { String passphrase; @@ -359,11 +371,18 @@ public class OpenPgpService extends RemoteService { } private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, Set<Long> allowedKeyIds) { + ParcelFileDescriptor output, Set<Long> allowedKeyIds, + boolean decryptMetadataOnly) { try { // Get Input- and OutputStream from ParcelFileDescriptor InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); - OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + + OutputStream os; + if (decryptMetadataOnly) { + os = null; + } else { + os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + } Intent result = new Intent(); try { @@ -376,17 +395,24 @@ public class OpenPgpService extends RemoteService { new ProviderHelper(this), new PgpDecryptVerify.PassphraseCache() { @Override - public String getCachedPassphrase(long masterKeyId) { - return PassphraseCacheService.getCachedPassphrase( - OpenPgpService.this, masterKeyId); + public String getCachedPassphrase(long masterKeyId) throws PgpDecryptVerify.NoSecretKeyException { + try { + return PassphraseCacheService.getCachedPassphrase( + OpenPgpService.this, masterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new PgpDecryptVerify.NoSecretKeyException(); + } } }, inputData, os ); - builder.setAllowSymmetricDecryption(false) // no support for symmetric encryption - .setAllowedKeyIds(allowedKeyIds) // allow only private keys associated with - // accounts of this app - .setPassphrase(passphrase); + + // allow only private keys associated with accounts of this app + // no support for symmetric encryption + builder.setPassphrase(passphrase) + .setAllowSymmetricDecryption(false) + .setAllowedKeyIds(allowedKeyIds) + .setDecryptMetadataOnly(decryptMetadataOnly); PgpDecryptVerifyResult decryptVerifyResult; try { @@ -409,8 +435,7 @@ public class OpenPgpService extends RemoteService { if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { // get PendingIntent for passphrase input, add it to given params and return to client return getPassphraseIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded()); - } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == - decryptVerifyResult.getStatus()) { + } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { throw new PgpGeneralException("Decryption of symmetric content not supported by API!"); } @@ -434,9 +459,18 @@ public class OpenPgpService extends RemoteService { } } + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) { + OpenPgpMetadata metadata = decryptVerifyResult.getDecryptMetadata(); + if (metadata != null) { + result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); + } + } + } finally { is.close(); - os.close(); + if (os != null) { + os.close(); + } } result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); @@ -492,6 +526,7 @@ public class OpenPgpService extends RemoteService { return result; } } catch (Exception e) { + Log.d(Constants.TAG, "getKeyImpl", e); Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); @@ -537,10 +572,15 @@ public class OpenPgpService extends RemoteService { } // version code is required and needs to correspond to version code of service! - if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) { + // History of versions in org.openintents.openpgp.util.OpenPgpApi + // we support 3 and 4 + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 3 + && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError - (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); + (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n" + + "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n" + + "supported API versions: 3, 4"); result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; @@ -588,7 +628,13 @@ public class OpenPgpService extends RemoteService { Set<Long> allowedKeyIds = mProviderHelper.getAllKeyIdsForApp( ApiAccounts.buildBaseUri(currentPkg)); - return decryptAndVerifyImpl(data, input, output, allowedKeyIds); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false); + } else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) { + String currentPkg = getCurrentCallingPackage(); + Set<Long> allowedKeyIds = + mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true); } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { return getKeyImpl(data); } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 8f1f46c04..666252353 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -102,7 +102,7 @@ public class AccountSettingsActivity extends ActionBarActivity { } private void save() { - new ProviderHelper(this).updateApiAccount(mAccountSettingsFragment.getAccSettings(), mAccountUri); + new ProviderHelper(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings()); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index d0b958844..48c76d561 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -18,11 +18,13 @@ package org.sufficientlysecure.keychain.remote.ui; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.ActionBarActivity; import android.view.View; +import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.htmltextview.HtmlTextView; @@ -37,6 +39,7 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import java.security.Provider; import java.util.ArrayList; public class RemoteServiceActivity extends ActionBarActivity { @@ -76,10 +79,17 @@ public class RemoteServiceActivity extends ActionBarActivity { // select pub keys view private SelectPublicKeyFragment mSelectFragment; + private ProviderHelper mProviderHelper; + + // for ACTION_CREATE_ACCOUNT + boolean mUpdateExistingAccount; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mProviderHelper = new ProviderHelper(this); + handleActions(getIntent(), savedInstanceState); } @@ -94,6 +104,14 @@ public class RemoteServiceActivity extends ActionBarActivity { final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); + setContentView(R.layout.api_remote_register_app); + + mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + AppSettings settings = new AppSettings(packageName, packageSignature); + mAppSettingsFragment.setAppSettings(settings); + // Inflate a "Done"/"Cancel" custom action bar view ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.api_register_allow, R.drawable.ic_action_done, @@ -102,8 +120,7 @@ public class RemoteServiceActivity extends ActionBarActivity { public void onClick(View v) { // Allow - new ProviderHelper(RemoteServiceActivity.this).insertApiApp( - mAppSettingsFragment.getAppSettings()); + mProviderHelper.insertApiApp(mAppSettingsFragment.getAppSettings()); // give data through for new service call Intent resultData = extras.getParcelable(EXTRA_DATA); @@ -120,18 +137,34 @@ public class RemoteServiceActivity extends ActionBarActivity { } } ); - - setContentView(R.layout.api_remote_register_app); - - mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); - - AppSettings settings = new AppSettings(packageName, packageSignature); - mAppSettingsFragment.setAppSettings(settings); } else if (ACTION_CREATE_ACCOUNT.equals(action)) { final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final String accName = extras.getString(EXTRA_ACC_NAME); + setContentView(R.layout.api_remote_create_account); + + mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); + + TextView text = (TextView) findViewById(R.id.api_remote_create_account_text); + + // update existing? + Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName); + AccountSettings settings = mProviderHelper.getApiAccountSettings(uri); + if (settings == null) { + // create new account + settings = new AccountSettings(accName); + mUpdateExistingAccount = false; + + text.setText(R.string.api_create_account_text); + } else { + // update existing account + mUpdateExistingAccount = true; + + text.setText(R.string.api_update_account_text); + } + mAccSettingsFragment.setAccSettings(settings); + // Inflate a "Done"/"Cancel" custom action bar view ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.api_settings_save, R.drawable.ic_action_done, @@ -145,9 +178,17 @@ public class RemoteServiceActivity extends ActionBarActivity { mAccSettingsFragment.setErrorOnSelectKeyFragment( getString(R.string.api_register_error_select_key)); } else { - new ProviderHelper(RemoteServiceActivity.this).insertApiAccount( - KeychainContract.ApiAccounts.buildBaseUri(packageName), - mAccSettingsFragment.getAccSettings()); + if (mUpdateExistingAccount) { + Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName); + Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build(); + mProviderHelper.updateApiAccount( + accountUri, + mAccSettingsFragment.getAccSettings()); + } else { + mProviderHelper.insertApiAccount( + KeychainContract.ApiAccounts.buildBaseUri(packageName), + mAccSettingsFragment.getAccSettings()); + } // give data through for new service call Intent resultData = extras.getParcelable(EXTRA_DATA); @@ -166,13 +207,6 @@ public class RemoteServiceActivity extends ActionBarActivity { } ); - setContentView(R.layout.api_remote_create_account); - - mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_account_settings_fragment); - - AccountSettings settings = new AccountSettings(accName); - mAccSettingsFragment.setAccSettings(settings); } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); final Intent resultData = extras.getParcelable(EXTRA_DATA); @@ -190,7 +224,8 @@ public class RemoteServiceActivity extends ActionBarActivity { RemoteServiceActivity.this.finish(); } - }); + } + ); } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); @@ -286,7 +321,8 @@ public class RemoteServiceActivity extends ActionBarActivity { RemoteServiceActivity.this.setResult(RESULT_CANCELED); RemoteServiceActivity.this.finish(); } - }); + } + ); setContentView(R.layout.api_remote_error_message); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index a7115a53d..d6c470e11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -86,6 +86,8 @@ public class KeychainIntentService extends IntentService public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY"; + public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA"; + public static final String ACTION_SAVE_KEYRING = Constants.INTENT_PREFIX + "SAVE_KEYRING"; public static final String ACTION_DELETE_FILE_SECURELY = Constants.INTENT_PREFIX @@ -241,16 +243,17 @@ public class KeychainIntentService extends IntentService data.putInt(SELECTED_URI, i); InputData inputData = createEncryptInputData(data); OutputStream outStream = createCryptOutputStream(data); + String originalFilename = getOriginalFilename(data); /* Operation */ PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( new ProviderHelper(this), - PgpHelper.getFullVersion(this), inputData, outStream); builder.setProgressable(this); builder.setEnableAsciiArmorOutput(useAsciiArmor) + .setVersionHeader(PgpHelper.getVersionForHeader(this)) .setCompressionId(compressionId) .setSymmetricEncryptionAlgorithm( Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) @@ -261,7 +264,8 @@ public class KeychainIntentService extends IntentService .setSignatureHashAlgorithm( Preferences.getPreferences(this).getDefaultHashAlgorithm()) .setSignaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)) + .setOriginalFilename(originalFilename); // this assumes that the bytes are cleartext (valid for current implementation!) if (source == IO_BYTES) { @@ -302,15 +306,19 @@ public class KeychainIntentService extends IntentService new ProviderHelper(this), new PgpDecryptVerify.PassphraseCache() { @Override - public String getCachedPassphrase(long masterKeyId) { - return PassphraseCacheService.getCachedPassphrase( - KeychainIntentService.this, masterKeyId); + public String getCachedPassphrase(long masterKeyId) throws PgpDecryptVerify.NoSecretKeyException { + try { + return PassphraseCacheService.getCachedPassphrase( + KeychainIntentService.this, masterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new PgpDecryptVerify.NoSecretKeyException(); + } } }, - inputData, outStream); - builder.setProgressable(this); - - builder.setAllowSymmetricDecryption(true) + inputData, outStream + ); + builder.setProgressable(this) + .setAllowSymmetricDecryption(true) .setPassphrase(passphrase); PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); @@ -329,6 +337,50 @@ public class KeychainIntentService extends IntentService } catch (Exception e) { sendErrorToHandler(e); } + } else if (ACTION_DECRYPT_METADATA.equals(action)) { + try { + /* Input */ + String passphrase = data.getString(DECRYPT_PASSPHRASE); + + InputData inputData = createDecryptInputData(data); + + /* Operation */ + + Bundle resultData = new Bundle(); + + // verifyText and decrypt returning additional resultData values for the + // verification of signatures + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( + new ProviderHelper(this), + new PgpDecryptVerify.PassphraseCache() { + @Override + public String getCachedPassphrase(long masterKeyId) throws PgpDecryptVerify.NoSecretKeyException { + try { + return PassphraseCacheService.getCachedPassphrase( + KeychainIntentService.this, masterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new PgpDecryptVerify.NoSecretKeyException(); + } + } + }, + inputData, null + ); + builder.setProgressable(this) + .setAllowSymmetricDecryption(true) + .setPassphrase(passphrase) + .setDecryptMetadataOnly(true); + + PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); + + resultData.putParcelable(RESULT_DECRYPT_VERIFY_RESULT, decryptVerifyResult); + + /* Output */ + OtherHelper.logDebugBundle(resultData, "resultData"); + + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + } catch (Exception e) { + sendErrorToHandler(e); + } } else if (ACTION_SAVE_KEYRING.equals(action)) { try { /* Input */ @@ -355,7 +407,7 @@ public class KeychainIntentService extends IntentService UncachedKeyRing ring = result.getRing(); - providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); + providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); // cache new passphrase if (saveParcel.mNewPassphrase != null) { @@ -402,7 +454,7 @@ public class KeychainIntentService extends IntentService } else { // get entries from cached file FileImportCache<ParcelableKeyRing> cache = - new FileImportCache<ParcelableKeyRing>(this); + new FileImportCache<ParcelableKeyRing>(this); entries = cache.readCacheIntoList(); } @@ -575,7 +627,7 @@ public class KeychainIntentService extends IntentService CanonicalizedPublicKeyRing publicRing = providerHelper.getCanonicalizedPublicKeyRing(pubKeyId); CanonicalizedSecretKeyRing secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(masterKeyId); CanonicalizedSecretKey certificationKey = secretKeyRing.getSecretKey(); - if(!certificationKey.unlock(signaturePassphrase)) { + if (!certificationKey.unlock(signaturePassphrase)) { throw new PgpGeneralException("Error extracting key (bad passphrase?)"); } UncachedKeyRing newRing = certificationKey.certifyUserIds(publicRing, userIds); @@ -728,6 +780,27 @@ public class KeychainIntentService extends IntentService } } + private String getOriginalFilename(Bundle data) throws PgpGeneralException, FileNotFoundException { + int target = data.getInt(TARGET); + switch (target) { + case IO_BYTES: + return ""; + + case IO_URI: + Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI); + + return FileHelper.getFilename(this, providerUri); + + case IO_URIS: + providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI)); + + return FileHelper.getFilename(this, providerUri); + + default: + throw new PgpGeneralException("No target choosen!"); + } + } + private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException { int target = data.getInt(TARGET); switch (target) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index e813ca26c..8cd9876eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -77,12 +77,24 @@ public class PassphraseCacheService extends Service { private static final int NOTIFICATION_ID = 1; + private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1; + private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND = 2; + private BroadcastReceiver mIntentReceiver; private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<CachedPassphrase>(); Context mContext; + public static class KeyNotFoundException extends Exception { + public KeyNotFoundException() { + } + + public KeyNotFoundException(String name) { + super(name); + } + } + /** * This caches a new passphrase in memory by sending a new command to the service. An android * service is only run once. Thus, when the service is already started, new commands just add @@ -115,24 +127,23 @@ public class PassphraseCacheService extends Service { * @param keyId * @return passphrase or null (if no passphrase is cached for this keyId) */ - public static String getCachedPassphrase(Context context, long keyId) { + public static String getCachedPassphrase(Context context, long keyId) throws KeyNotFoundException { Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphrase() get masterKeyId for " + keyId); Intent intent = new Intent(context, PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_GET); final Object mutex = new Object(); - final Bundle returnBundle = new Bundle(); + final Message returnMessage = Message.obtain(); HandlerThread handlerThread = new HandlerThread("getPassphraseThread"); handlerThread.start(); Handler returnHandler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message message) { - if (message.obj != null) { - String passphrase = ((Bundle) message.obj).getString(EXTRA_PASSPHRASE); - returnBundle.putString(EXTRA_PASSPHRASE, passphrase); - } + // copy over result to handle after mutex.wait + returnMessage.what = message.what; + returnMessage.copyFrom(message); synchronized (mutex) { mutex.notify(); } @@ -156,10 +167,13 @@ public class PassphraseCacheService extends Service { } } - if (returnBundle.containsKey(EXTRA_PASSPHRASE)) { - return returnBundle.getString(EXTRA_PASSPHRASE); - } else { - return null; + switch (returnMessage.what) { + case MSG_PASSPHRASE_CACHE_GET_OKAY: + return returnMessage.getData().getString(EXTRA_PASSPHRASE); + case MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND: + throw new KeyNotFoundException(); + default: + throw new KeyNotFoundException("should not happen!"); } } @@ -169,7 +183,7 @@ public class PassphraseCacheService extends Service { * @param keyId * @return */ - private String getCachedPassphraseImpl(long keyId) { + private String getCachedPassphraseImpl(long keyId) throws ProviderHelper.NotFoundException { // passphrase for symmetric encryption? if (keyId == Constants.key.symmetric) { Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption"); @@ -182,46 +196,41 @@ public class PassphraseCacheService extends Service { } // try to get master key id which is used as an identifier for cached passphrases - try { - Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + keyId); - CanonicalizedSecretKeyRing key = new ProviderHelper(this).getCanonicalizedSecretKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId)); - // no passphrase needed? just add empty string and return it, then - if (!key.hasPassphrase()) { - Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!"); + Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + keyId); + CanonicalizedSecretKeyRing key = new ProviderHelper(this).getCanonicalizedSecretKeyRing( + KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId)); - try { - addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback()); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "PgpGeneralException occured"); - } - return ""; - } + // no passphrase needed? just add empty string and return it, then + if (!key.hasPassphrase()) { + Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!"); - // TODO: HACK + // TODO: HACK for yubikeys if (key.getSecretKey().getSecretKey().getS2K().getType() == S2K.GNU_DUMMY_S2K && key.getSecretKey().getSecretKey().getS2K().getProtectionMode() == 2) { // NFC! return "123456"; } - // get cached passphrase - CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId); - if (cachedPassphrase == null) { - Log.d(Constants.TAG, "PassphraseCacheService: Passphrase not (yet) cached, returning null"); - // not really an error, just means the passphrase is not cached but not empty either - return null; + try { + addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback()); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "PgpGeneralException occured"); } + return ""; + } - // set it again to reset the cache life cycle - Log.d(Constants.TAG, "PassphraseCacheService: Cache passphrase again when getting it!"); - addCachedPassphrase(this, keyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID()); - return cachedPassphrase.getPassphrase(); - - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!"); + // get cached passphrase + CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId); + if (cachedPassphrase == null) { + Log.d(Constants.TAG, "PassphraseCacheService: Passphrase not (yet) cached, returning null"); + // not really an error, just means the passphrase is not cached but not empty either return null; } + + // set it again to reset the cache life cycle + Log.d(Constants.TAG, "PassphraseCacheService: Cache passphrase again when getting it!"); + addCachedPassphrase(this, keyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID()); + return cachedPassphrase.getPassphrase(); } /** @@ -303,12 +312,19 @@ public class PassphraseCacheService extends Service { long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); - String passphrase = getCachedPassphraseImpl(keyId); Message msg = Message.obtain(); - Bundle bundle = new Bundle(); - bundle.putString(EXTRA_PASSPHRASE, passphrase); - msg.obj = bundle; + try { + String passphrase = getCachedPassphraseImpl(keyId); + msg.what = MSG_PASSPHRASE_CACHE_GET_OKAY; + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_PASSPHRASE, passphrase); + msg.setData(bundle); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!"); + msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND; + } + try { messenger.send(msg); } catch (RemoteException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 467e0c14a..c1986825c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -231,7 +231,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements */ private void initiateCertifying() { // get the user's passphrase for this key (if required) - String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); + String passphrase = null; + try { + passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + Log.e(Constants.TAG, "Key not found!", e); + finish(); + return; + } if (passphrase == null) { PassphraseDialogFragment.show(this, mMasterKeyId, new Handler() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index c33b1489a..845fbfa3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -23,8 +23,10 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; @@ -113,7 +116,8 @@ public class DecryptFileFragment extends DecryptFragment { return; } - askForOutputFilename(); +// askForOutputFilename(); + decryptOriginalFilename(null); } private String removeEncryptedAppend(String name) { @@ -123,8 +127,13 @@ public class DecryptFileFragment extends DecryptFragment { return name; } - private void askForOutputFilename() { - String targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri)); + private void askForOutputFilename(String originalFilename) { + String targetName; + if (!TextUtils.isEmpty(originalFilename)) { + targetName = originalFilename; + } else { + targetName = 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; @@ -136,6 +145,82 @@ public class DecryptFileFragment extends DecryptFragment { } } + private void decryptOriginalFilename(String passphrase) { + Log.d(Constants.TAG, "decryptOriginalFilename"); + + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA); + + // data + Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); + + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + + data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after decrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + + PgpDecryptVerifyResult decryptVerifyResult = + returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); + + if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { + showPassphraseDialogForFilename(decryptVerifyResult.getKeyIdPassphraseNeeded()); + } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == + decryptVerifyResult.getStatus()) { + showPassphraseDialogForFilename(Constants.key.symmetric); + } else { + + // go on... + askForOutputFilename(decryptVerifyResult.getDecryptMetadata().getFilename()); + } + } + } + }; + + // 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); + } + + protected void showPassphraseDialogForFilename(long keyId) { + PassphraseDialogFragment.show(getActivity(), keyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + String passphrase = + message.getData().getString(PassphraseDialogFragment.MESSAGE_DATA_PASSPHRASE); + decryptOriginalFilename(passphrase); + } + } + } + ); + } + @Override protected void decryptStart(String passphrase) { Log.d(Constants.TAG, "decryptStart"); @@ -161,7 +246,7 @@ public class DecryptFileFragment extends DecryptFragment { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after encrypting is done in KeychainIntentService + // Message is received after decrypting is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { @@ -178,7 +263,7 @@ public class DecryptFileFragment extends DecryptFragment { if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded()); } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == - decryptVerifyResult.getStatus()) { + decryptVerifyResult.getStatus()) { showPassphraseDialog(Constants.key.symmetric); } else { // display signature result in activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 5be196a45..03074bb6a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -339,6 +339,10 @@ public class EditKeyFragment extends LoaderFragment implements mSaveKeyringParcel.mRevokeUserIds.remove(userId); } else { mSaveKeyringParcel.mRevokeUserIds.add(userId); + // not possible to revoke and change to primary user id + if (mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + mSaveKeyringParcel.mChangePrimaryUserId = null; + } } break; } @@ -471,8 +475,14 @@ public class EditKeyFragment extends LoaderFragment implements } private void cachePassphraseForEdit() { - mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), - mSaveKeyringParcel.mMasterKeyId); + try { + mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), + mSaveKeyringParcel.mMasterKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + Log.e(Constants.TAG, "Key not found!", e); + getActivity().finish(); + return; + } if (mCurrentPassphrase == null) { PassphraseDialogFragment.show(getActivity(), mSaveKeyringParcel.mMasterKeyId, new Handler() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 94f828b48..7e08f6b7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -81,10 +81,6 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn PagerTabStripAdapter mTabsAdapterContent; // tabs - Bundle mAsymmetricFragmentBundle = new Bundle(); - Bundle mSymmetricFragmentBundle = new Bundle(); - Bundle mMessageFragmentBundle = new Bundle(); - Bundle mFileFragmentBundle = new Bundle(); int mSwitchToMode = PAGER_MODE_ASYMMETRIC; int mSwitchToContent = PAGER_CONTENT_MESSAGE; @@ -93,7 +89,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn private static final int PAGER_CONTENT_MESSAGE = 0; private static final int PAGER_CONTENT_FILE = 1; - // model used by message and file fragments + // model used by fragments private long mEncryptionKeyIds[] = null; private String mEncryptionUserIds[] = null; private long mSigningKeyId = Constants.key.none; @@ -267,21 +263,23 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn if (isContentMessage()) { data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, mMessage.getBytes()); + + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, + Preferences.getPreferences(this).getDefaultMessageCompression()); } else { data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS); data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUris); data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS); data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUris); + + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, + Preferences.getPreferences(this).getDefaultFileCompression()); } // Always use armor for messages data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mUseArmor || isContentMessage()); - // TODO: Only default compression right now... - int compressionId = Preferences.getPreferences(this).getDefaultMessageCompression(); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); - if (isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); String passphrase = mPassphrase; @@ -426,7 +424,6 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn if (isModeSymmetric()) { // symmetric encryption checks - if (mPassphrase == null) { Notify.showNotify(this, R.string.passphrases_do_not_match, Notify.Style.ERROR); return false; @@ -453,20 +450,24 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn return false; } - if (mSigningKeyId != 0 && PassphraseCacheService.getCachedPassphrase(this, mSigningKeyId) == null) { - PassphraseDialogFragment.show(this, mSigningKeyId, - new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - // restart - startEncrypt(); + try { + if (mSigningKeyId != 0 && PassphraseCacheService.getCachedPassphrase(this, mSigningKeyId) == null) { + PassphraseDialogFragment.show(this, mSigningKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + // restart + startEncrypt(); + } } } - } - ); + ); - return false; + return false; + } + } catch (PassphraseCacheService.KeyNotFoundException e) { + Log.e(Constants.TAG, "Key not found!", e); } } return true; @@ -502,16 +503,12 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn // Handle intent actions handleActions(getIntent()); - mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, - mAsymmetricFragmentBundle, getString(R.string.label_asymmetric)); - mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, - mSymmetricFragmentBundle, getString(R.string.label_symmetric)); + mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, null, getString(R.string.label_asymmetric)); + mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, null, getString(R.string.label_symmetric)); mViewPagerMode.setCurrentItem(mSwitchToMode); - mTabsAdapterContent.addTab(EncryptMessageFragment.class, - mMessageFragmentBundle, getString(R.string.label_message)); - mTabsAdapterContent.addTab(EncryptFileFragment.class, - mFileFragmentBundle, getString(R.string.label_files)); + mTabsAdapterContent.addTab(EncryptMessageFragment.class, null, getString(R.string.label_message)); + mTabsAdapterContent.addTab(EncryptFileFragment.class, null, getString(R.string.label_files)); mViewPagerContent.setCurrentItem(mSwitchToContent); mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor(); @@ -603,14 +600,10 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn String textData = extras.getString(EXTRA_TEXT); - long signatureKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); - long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); + mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); + mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); // preselect keys given by intent - mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS, - encryptionKeyIds); - mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID, - signatureKeyId); mSwitchToMode = PAGER_MODE_ASYMMETRIC; /** @@ -618,11 +611,11 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn */ if (ACTION_ENCRYPT.equals(action) && textData != null) { // encrypt text based on given extra - mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData); + mMessage = textData; mSwitchToContent = PAGER_CONTENT_MESSAGE; } else if (ACTION_ENCRYPT.equals(action) && uris != null && !uris.isEmpty()) { // encrypt file based on Uri - mFileFragmentBundle.putParcelableArrayList(EncryptFileFragment.ARG_URIS, uris); + mInputUris = uris; mSwitchToContent = PAGER_CONTENT_FILE; } else if (ACTION_ENCRYPT.equals(action)) { Log.e(Constants.TAG, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index 3de617ca0..a402b6f68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -123,14 +123,10 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - - long signatureKeyId = getArguments().getLong(ARG_SIGNATURE_KEY_ID); - long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); - mProviderHelper = new ProviderHelper(getActivity()); - // preselect keys given by arguments (given by Intent to EncryptActivity) - preselectKeys(signatureKeyId, encryptionKeyIds, mProviderHelper); + // preselect keys given + preselectKeys(); getLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<Cursor>() { @Override @@ -145,24 +141,17 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi KeyRings.MASTER_KEY_ID, KeyRings.KEY_ID, KeyRings.USER_ID, - KeyRings.EXPIRY, - KeyRings.IS_REVOKED, - // can certify info only related to master key - KeyRings.CAN_CERTIFY, - // has sign may be any subkey + KeyRings.IS_EXPIRED, KeyRings.HAS_SIGN, - KeyRings.HAS_ANY_SECRET, - KeyRings.HAS_SECRET + KeyRings.HAS_ANY_SECRET }; - String where = KeyRings.HAS_ANY_SECRET + " = 1"; + String where = KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeyRings.HAS_SIGN + " NOT NULL AND " + + KeyRings.IS_REVOKED + " = 0 AND " + KeyRings.IS_EXPIRED + " = 0"; // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), baseUri, projection, where, null, null); - /*return new CursorLoader(getActivity(), KeyRings.buildUnifiedKeyRingsUri(), - new String[]{KeyRings.USER_ID, KeyRings.KEY_ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET}, SIGN_KEY_SELECTION, - null, null);*/ } @Override @@ -194,19 +183,15 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi /** * If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those! - * - * @param preselectedSignatureKeyId - * @param preselectedEncryptionKeyIds */ - private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds, - ProviderHelper providerHelper) { + private void preselectKeys() { // TODO all of this works under the assumption that the first suitable subkey is always used! // not sure if we need to distinguish between different subkeys here? - if (preselectedSignatureKeyId != 0) { + long signatureKey = mEncryptInterface.getSignatureKey(); + if (signatureKey != Constants.key.none) { try { - CachedPublicKeyRing keyring = - providerHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(preselectedSignatureKeyId)); + CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingUri(signatureKey)); if(keyring.hasAnySecret()) { setSignatureKeyId(keyring.getMasterKeyId()); } @@ -215,10 +200,11 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi } } - if (preselectedEncryptionKeyIds != null) { - for (long preselectedId : preselectedEncryptionKeyIds) { + long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys(); + if (encryptionKeyIds != null) { + for (long preselectedId : encryptionKeyIds) { try { - CachedPublicKeyRing ring = providerHelper.getCachedPublicKeyRing( + CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(preselectedId)); mEncryptKeyView.addObject(mEncryptKeyView.new EncryptionKey(ring)); } catch (PgpGeneralException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index 8a9e17020..14d3d1c4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -111,8 +111,6 @@ public class EncryptFileFragment extends Fragment implements EncryptActivityInte @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - - addInputUris(getArguments().<Uri>getParcelableArrayList(ARG_URIS)); } private void addInputUri() { @@ -125,14 +123,6 @@ public class EncryptFileFragment extends Fragment implements EncryptActivityInte } } - private void addInputUris(List<Uri> uris) { - if (uris != null) { - for (Uri uri : uris) { - addInputUri(uri); - } - } - } - private void addInputUri(Uri inputUri) { if (inputUri == null) { return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index a6561dfad..de3236d35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -119,8 +119,8 @@ public class PreferencesActivity extends PreferenceActivity { initializeAsciiArmor( (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); - initializeConcealPgpApplication( - (CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION)); + initializeWriteVersionHeader( + (CheckBoxPreference) findPreference(Constants.Pref.WRITE_VERSION_HEADER)); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // Load the legacy preferences headers @@ -263,8 +263,8 @@ public class PreferencesActivity extends PreferenceActivity { initializeAsciiArmor( (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); - initializeConcealPgpApplication( - (CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION)); + initializeWriteVersionHeader( + (CheckBoxPreference) findPreference(Constants.Pref.WRITE_VERSION_HEADER)); } } @@ -386,12 +386,12 @@ public class PreferencesActivity extends PreferenceActivity { }); } - private static void initializeConcealPgpApplication(final CheckBoxPreference mConcealPgpApplication) { - mConcealPgpApplication.setChecked(sPreferences.getConcealPgpApplication()); - mConcealPgpApplication.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + private static void initializeWriteVersionHeader(final CheckBoxPreference mWriteVersionHeader) { + mWriteVersionHeader.setChecked(sPreferences.getWriteVersionHeader()); + mWriteVersionHeader.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { - mConcealPgpApplication.setChecked((Boolean) newValue); - sPreferences.setConcealPgpApplication((Boolean) newValue); + mWriteVersionHeader.setChecked((Boolean) newValue); + sPreferences.setWriteVersionHeader((Boolean) newValue); return false; } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index 370a7312f..08243f06b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -22,17 +22,22 @@ import android.database.Cursor; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -41,6 +46,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; @@ -88,9 +95,30 @@ public class ViewKeyMainFragment extends LoaderFragment implements PorterDuff.Mode.SRC_IN); mActionUpdate = view.findViewById(R.id.view_key_action_update); + mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + showUserIdInfo(position); + } + }); + return root; } + private void showUserIdInfo(final int position) { + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); + final int isVerified = mUserIdsAdapter.getIsVerified(position); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + UserIdInfoDialogFragment dialogFragment = + UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog"); + } + }); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 9bf47a387..717dcf818 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -257,6 +257,11 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC return mCursor.getInt(INDEX_IS_REVOKED) > 0; } + public int getIsVerified(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(INDEX_VERIFIED); + } + public boolean getIsRevokedPending(int position) { mCursor.moveToPosition(position); String userId = mCursor.getString(INDEX_USER_ID); @@ -280,24 +285,4 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC return view; } - // Disable selection of items for lists without checkboxes, http://stackoverflow.com/a/4075045 - @Override - public boolean areAllItemsEnabled() { - if (mCheckStates == null && mSaveKeyringParcel == null) { - return false; - } else { - return super.areAllItemsEnabled(); - } - } - - // Disable selection of items for lists without checkboxes, http://stackoverflow.com/a/4075045 - @Override - public boolean isEnabled(int position) { - if (mCheckStates == null && mSaveKeyringParcel == null) { - return false; - } else { - return super.isEnabled(position); - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java new file mode 100644 index 000000000..968b2429b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java @@ -0,0 +1,95 @@ +/* + * 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.dialog; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; + +public class UserIdInfoDialogFragment extends DialogFragment { + private static final String ARG_IS_REVOKED = "is_revoked"; + private static final String ARG_IS_VERIFIED = "is_verified"; + + /** + * Creates new instance of this dialog fragment + */ + public static UserIdInfoDialogFragment newInstance(boolean isRevoked, int isVerified) { + UserIdInfoDialogFragment frag = new UserIdInfoDialogFragment(); + Bundle args = new Bundle(); + args.putBoolean(ARG_IS_REVOKED, isRevoked); + args.putInt(ARG_IS_VERIFIED, isVerified); + + frag.setArguments(args); + + return frag; + } + + /** + * Creates dialog + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + + int isVerified = getArguments().getInt(ARG_IS_VERIFIED); + boolean isRevoked = getArguments().getBoolean(ARG_IS_REVOKED); + + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); + + String title; + String message; + if (isRevoked) { + title = getString(R.string.user_id_info_revoked_title); + message = getString(R.string.user_id_info_revoked_text); + } else { + switch (isVerified) { + case KeychainContract.Certs.VERIFIED_SECRET: + title = getString(R.string.user_id_info_verified_title); + message = getString(R.string.user_id_info_verified_text); + break; + case KeychainContract.Certs.VERIFIED_SELF: + title = getString(R.string.user_id_info_not_verified_title); + message = getString(R.string.user_id_info_not_verified_text); + break; + default: + title = getString(R.string.user_id_info_invalid_title); + message = getString(R.string.user_id_info_invalid_text); + break; + } + } + + alert.setTitle(title); + alert.setMessage(message); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + } + }); + + return alert.show(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 7e762fe77..20b9570bb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Rect; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; @@ -45,7 +46,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -113,9 +114,23 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { ((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - return new CursorLoader(getContext(), KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), - new String[]{KeychainContract.KeyRings.HAS_ENCRYPT, KeychainContract.KeyRings.KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.FINGERPRINT}, - null, null, null); + // These are the rows that we will retrieve. + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.KEY_ID, + KeyRings.USER_ID, + KeyRings.FINGERPRINT, + KeyRings.IS_EXPIRED, + KeyRings.HAS_ENCRYPT + }; + + String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND " + + KeyRings.IS_REVOKED + " = 0"; + + return new CursorLoader(getContext(), baseUri, projection, where, null, null); } @Override @@ -148,10 +163,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { ArrayList<EncryptionKey> keys = new ArrayList<EncryptionKey>(); while (cursor.moveToNext()) { try { - if (cursor.getInt(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT)) != 0) { - EncryptionKey key = new EncryptionKey(cursor); - keys.add(key); - } + EncryptionKey key = new EncryptionKey(cursor); + keys.add(key); } catch (Exception e) { Log.w(Constants.TAG, e); return; @@ -174,10 +187,10 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { } public EncryptionKey(Cursor cursor) { - this(cursor.getString(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.USER_ID)), - cursor.getLong(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.KEY_ID)), + this(cursor.getString(cursor.getColumnIndexOrThrow(KeyRings.USER_ID)), + cursor.getLong(cursor.getColumnIndexOrThrow(KeyRings.KEY_ID)), PgpKeyHelper.convertFingerprintToHex( - cursor.getBlob(cursor.getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT)))); + cursor.getBlob(cursor.getColumnIndexOrThrow(KeyRings.FINGERPRINT)))); } |