Merge branch 'master' into yubikey
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ public class OpenPgpSignatureResultBuilder {
|
||||
this.mSignatureAvailable = signatureAvailable;
|
||||
}
|
||||
|
||||
public boolean isValidSignature() {
|
||||
return mValidSignature;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult build() {
|
||||
if (mSignatureAvailable) {
|
||||
OpenPgpSignatureResult result = new OpenPgpSignatureResult();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
int startProgress = currentProgress;
|
||||
int endProgress = 100;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, n);
|
||||
signatureGenerator.update(buffer, 0, length);
|
||||
|
||||
alreadyWritten += length;
|
||||
if (mData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / mData.getSize();
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
}
|
||||
|
||||
literalGen.close();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -325,6 +333,50 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} 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);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))));
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user