Merge branch 'master' into encrypted-export
This commit is contained in:
@@ -151,7 +151,6 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
|
||||
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
|
||||
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
|
||||
.setSignedLiteralData(true)
|
||||
.setRequiredSignerFingerprint(requiredFingerprint);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
|
||||
|
||||
@@ -514,7 +514,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_CR_ERROR_NO_USER_ID (LogLevel.ERROR, R.string.msg_cr_error_no_user_id),
|
||||
MSG_CR_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify),
|
||||
MSG_CR_ERROR_NULL_EXPIRY(LogLevel.ERROR, R.string.msg_cr_error_null_expiry),
|
||||
MSG_CR_ERROR_KEYSIZE_512 (LogLevel.ERROR, R.string.msg_cr_error_keysize_512),
|
||||
MSG_CR_ERROR_KEYSIZE_2048(LogLevel.ERROR, R.string.msg_cr_error_keysize_2048),
|
||||
MSG_CR_ERROR_NO_KEYSIZE (LogLevel.ERROR, R.string.msg_cr_error_no_keysize),
|
||||
MSG_CR_ERROR_NO_CURVE (LogLevel.ERROR, R.string.msg_cr_error_no_curve),
|
||||
MSG_CR_ERROR_UNKNOWN_ALGO (LogLevel.ERROR, R.string.msg_cr_error_unknown_algo),
|
||||
@@ -661,6 +661,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input),
|
||||
MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data),
|
||||
MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key),
|
||||
MSG_DC_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_dc_error_no_signature),
|
||||
MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception),
|
||||
MSG_DC_INTEGRITY_CHECK_OK (LogLevel.INFO, R.string.msg_dc_integrity_check_ok),
|
||||
MSG_DC_OK_META_ONLY (LogLevel.OK, R.string.msg_dc_ok_meta_only),
|
||||
@@ -687,6 +688,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_VL_ERROR_MISSING_SIGLIST (LogLevel.ERROR, R.string.msg_vl_error_no_siglist),
|
||||
MSG_VL_ERROR_MISSING_LITERAL (LogLevel.ERROR, R.string.msg_vl_error_missing_literal),
|
||||
MSG_VL_ERROR_MISSING_KEY (LogLevel.ERROR, R.string.msg_vl_error_wrong_key),
|
||||
MSG_VL_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_vl_error_no_signature),
|
||||
MSG_VL_CLEAR_SIGNATURE_CHECK (LogLevel.DEBUG, R.string.msg_vl_clear_signature_check),
|
||||
MSG_VL_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_vl_error_integrity_check),
|
||||
MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok),
|
||||
@@ -703,7 +705,6 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// pgpsignencrypt
|
||||
MSG_PSE_ASYMMETRIC (LogLevel.INFO, R.string.msg_pse_asymmetric),
|
||||
MSG_PSE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_pse_clearsign_only),
|
||||
MSG_PSE_COMPRESSING (LogLevel.DEBUG, R.string.msg_pse_compressing),
|
||||
MSG_PSE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_pse_encrypting),
|
||||
MSG_PSE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_pse_error_bad_passphrase),
|
||||
|
||||
@@ -154,8 +154,13 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
|
||||
return getRing().getEncoded();
|
||||
}
|
||||
|
||||
public boolean containsSubkey(String expectedFingerprint) {
|
||||
/// Returns true iff the keyring contains a primary key or mutually bound subkey with the expected fingerprint
|
||||
public boolean containsBoundSubkey(String expectedFingerprint) {
|
||||
for (CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||
boolean isMasterOrMutuallyBound = key.isMasterKey() || key.canSign();
|
||||
if (!isMasterOrMutuallyBound) {
|
||||
continue;
|
||||
}
|
||||
if (KeyFormattingUtils.convertFingerprintToHex(
|
||||
key.getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
|
||||
return true;
|
||||
|
||||
@@ -44,13 +44,17 @@ import java.util.Iterator;
|
||||
public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
// this is the parent key ring
|
||||
final KeyRing mRing;
|
||||
final CanonicalizedKeyRing mRing;
|
||||
|
||||
CanonicalizedPublicKey(KeyRing ring, PGPPublicKey key) {
|
||||
CanonicalizedPublicKey(CanonicalizedKeyRing ring, PGPPublicKey key) {
|
||||
super(key);
|
||||
mRing = ring;
|
||||
}
|
||||
|
||||
public CanonicalizedKeyRing getKeyRing() {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public IterableIterator<String> getUserIds() {
|
||||
return new IterableIterator<String>(mPublicKey.getUserIDs());
|
||||
}
|
||||
|
||||
@@ -91,11 +91,12 @@ public class OpenPgpSignatureResultBuilder {
|
||||
return mInsecure;
|
||||
}
|
||||
|
||||
public void initValid(CanonicalizedPublicKeyRing signingRing,
|
||||
CanonicalizedPublicKey signingKey) {
|
||||
public void initValid(CanonicalizedPublicKey signingKey) {
|
||||
setSignatureAvailable(true);
|
||||
setKnownKey(true);
|
||||
|
||||
CanonicalizedKeyRing signingRing = signingKey.getKeyRing();
|
||||
|
||||
// from RING
|
||||
setKeyId(signingRing.getMasterKeyId());
|
||||
try {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import android.net.Uri;
|
||||
@@ -36,7 +36,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||
private boolean mDecryptMetadataOnly;
|
||||
private byte[] mDetachedSignature;
|
||||
private String mRequiredSignerFingerprint;
|
||||
private boolean mSignedLiteralData;
|
||||
|
||||
public PgpDecryptVerifyInputParcel() {
|
||||
}
|
||||
@@ -61,7 +60,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||
mDecryptMetadataOnly = source.readInt() != 0;
|
||||
mDetachedSignature = source.createByteArray();
|
||||
mRequiredSignerFingerprint = source.readString();
|
||||
mSignedLiteralData = source.readInt() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +78,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||
dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
|
||||
dest.writeByteArray(mDetachedSignature);
|
||||
dest.writeString(mRequiredSignerFingerprint);
|
||||
dest.writeInt(mSignedLiteralData ? 1 : 0);
|
||||
}
|
||||
|
||||
byte[] getInputBytes() {
|
||||
@@ -150,15 +147,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isSignedLiteralData() {
|
||||
return mSignedLiteralData;
|
||||
}
|
||||
|
||||
public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
|
||||
mSignedLiteralData = signedLiteralData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
|
||||
public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
|
||||
return new PgpDecryptVerifyInputParcel(source);
|
||||
|
||||
@@ -18,13 +18,23 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPDataValidationException;
|
||||
@@ -33,18 +43,14 @@ import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyValidationException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
|
||||
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
|
||||
import org.spongycastle.util.encoders.DecoderException;
|
||||
@@ -68,17 +74,6 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
|
||||
|
||||
public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||
@@ -153,9 +148,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
|
||||
if (input.isSignedLiteralData()) {
|
||||
return verifySignedLiteralData(input, aIn, outputStream, 0);
|
||||
} else if (aIn.isClearText()) {
|
||||
if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, outputStream, 0);
|
||||
} else {
|
||||
@@ -186,133 +179,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
}
|
||||
|
||||
/**Verify signed plaintext data (PGP/INLINE). */
|
||||
@NonNull
|
||||
private DecryptVerifyResult verifySignedLiteralData(
|
||||
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
|
||||
throws IOException, PGPException {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_VL, indent);
|
||||
|
||||
// thinking that the proof-fetching operation is going to take most of the time
|
||||
updateProgress(R.string.progress_reading_data, 75, 100);
|
||||
|
||||
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
|
||||
Object o = pgpF.nextObject();
|
||||
if (o instanceof PGPCompressedData) {
|
||||
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
|
||||
|
||||
pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream());
|
||||
o = pgpF.nextObject();
|
||||
updateProgress(R.string.progress_decompressing_data, 80, 100);
|
||||
}
|
||||
|
||||
// all we want to see is a OnePassSignatureList followed by LiteralData
|
||||
if (!(o instanceof PGPOnePassSignatureList)) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o;
|
||||
|
||||
// go through all signatures (should be just one), make sure we have
|
||||
// the key and it matches the one we’re looking for
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
int signatureIndex = -1;
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
|
||||
// there has to be a key, and it has to be the right one
|
||||
if (signingKey == null) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||
Log.d(Constants.TAG, "Failed to find key in signed-literal message");
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
|
||||
if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
|
||||
" got " + fingerprint + "!");
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
|
||||
PGPOnePassSignature signature = sigList.get(signatureIndex);
|
||||
signatureResultBuilder.initValid(signingRing, signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
|
||||
o = pgpF.nextObject();
|
||||
|
||||
if (!(o instanceof PGPLiteralData)) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPLiteralData literalData = (PGPLiteralData) o;
|
||||
|
||||
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
|
||||
updateProgress(R.string.progress_decrypting, 85, 100);
|
||||
|
||||
InputStream dataIn = literalData.getInputStream();
|
||||
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
while ((length = dataIn.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, length);
|
||||
signature.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_verifying_signature, 95, 100);
|
||||
log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1);
|
||||
|
||||
PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
|
||||
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
||||
|
||||
// Verify signature and check binding signatures
|
||||
boolean validSignature = signature.verify(messageSignature);
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
|
||||
OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
|
||||
|
||||
if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_CONFIRMED
|
||||
&& signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED) {
|
||||
log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_VL_OK, indent);
|
||||
|
||||
// Return a positive result, with metadata and verification info
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureResult);
|
||||
result.setDecryptionResult(
|
||||
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class EncryptStreamResult {
|
||||
|
||||
// this is non-null iff an error occured, return directly
|
||||
@@ -368,7 +234,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
}
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
|
||||
|
||||
JcaPGPObjectFactory plainFact;
|
||||
@@ -415,10 +280,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
log.add(LogType.MSG_DC_PREP_STREAMS, indent);
|
||||
|
||||
int signatureIndex = -1;
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
|
||||
log.add(LogType.MSG_DC_CLEAR, indent);
|
||||
indent += 1;
|
||||
|
||||
@@ -435,58 +296,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
plainFact = fact;
|
||||
}
|
||||
|
||||
// resolve leading signature data
|
||||
PGPOnePassSignature signature = null;
|
||||
if (dataChunk instanceof PGPOnePassSignatureList) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
|
||||
currentProgress += 2;
|
||||
updateProgress(R.string.progress_processing_signature, currentProgress, 100);
|
||||
|
||||
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
|
||||
|
||||
// NOTE: following code is similar to processSignature, but for PGPOnePassSignature
|
||||
|
||||
// go through all signatures
|
||||
// and find out for which signature we have a key in our database
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
|
||||
if (signingKey != null) {
|
||||
// key found in our database!
|
||||
signature = sigList.get(signatureIndex);
|
||||
|
||||
signatureResultBuilder.initValid(signingRing, signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
} else {
|
||||
// no key in our database -> return "unknown pub key" status including the first key id
|
||||
if (!sigList.isEmpty()) {
|
||||
signatureResultBuilder.setSignatureAvailable(true);
|
||||
signatureResultBuilder.setKnownKey(false);
|
||||
signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
|
||||
}
|
||||
}
|
||||
|
||||
// check for insecure signing key
|
||||
// TODO: checks on signingRing ?
|
||||
if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
|
||||
if (signatureChecker.initializeOnePassSignature(dataChunk, log, indent +1)) {
|
||||
dataChunk = plainFact.nextObject();
|
||||
}
|
||||
|
||||
@@ -567,16 +378,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
return result;
|
||||
}
|
||||
|
||||
int endProgress;
|
||||
if (signature != null) {
|
||||
endProgress = 90;
|
||||
} else if (esResult != null && esResult.encryptedData.isIntegrityProtected()) {
|
||||
endProgress = 95;
|
||||
} else {
|
||||
endProgress = 100;
|
||||
}
|
||||
ProgressScaler progressScaler =
|
||||
new ProgressScaler(mProgressable, currentProgress, endProgress, 100);
|
||||
new ProgressScaler(mProgressable, currentProgress, 95, 100);
|
||||
|
||||
InputStream dataIn = literalData.getInputStream();
|
||||
|
||||
@@ -592,9 +395,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
|
||||
// update signature buffer if signature is also present
|
||||
if (signature != null) {
|
||||
signature.update(buffer, 0, length);
|
||||
}
|
||||
signatureChecker.updateSignatureData(buffer, 0, length);
|
||||
|
||||
// note down first couple of bytes for "magic bytes" file type detection
|
||||
if (alreadyWritten == 0) {
|
||||
@@ -602,7 +403,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
|
||||
alreadyWritten += length;
|
||||
|
||||
// noinspection ConstantConditions, TODO progress
|
||||
if (wholeSize > 0) {
|
||||
long progress = 100 * alreadyWritten / wholeSize;
|
||||
// stop at 100% for wrong file sizes...
|
||||
@@ -611,7 +412,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
// TODO: slow annealing to fake a progress?
|
||||
}
|
||||
|
||||
// special treatment to detect pgp mime types
|
||||
@@ -628,28 +428,15 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
metadata = new OpenPgpMetadata(
|
||||
originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
|
||||
|
||||
if (signature != null) {
|
||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||
if (signatureChecker.isInitialized()) {
|
||||
|
||||
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
|
||||
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
||||
Object o = plainFact.nextObject();
|
||||
boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1);
|
||||
|
||||
// Verify signature
|
||||
boolean validSignature = signature.verify(messageSignature);
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
if (!signatureCheckOk) {
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
}
|
||||
|
||||
indent -= 1;
|
||||
@@ -664,7 +451,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
} else if (signature == null) {
|
||||
} else if ( ! signatureChecker.isInitialized() ) {
|
||||
// If no signature is present, we *require* an MDC!
|
||||
// Handle missing integrity protection like failed integrity protection!
|
||||
// The MDC packet can be stripped by an attacker!
|
||||
@@ -681,7 +468,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
|
||||
result.setCachedCryptoInputParcel(cryptoInput);
|
||||
result.setSignatureResult(signatureResultBuilder.build());
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setDecryptionResult(decryptionResultBuilder.build());
|
||||
result.setDecryptionMetadata(metadata);
|
||||
|
||||
@@ -692,13 +479,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||
PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress) throws PGPException {
|
||||
|
||||
// TODO is this necessary?
|
||||
/*
|
||||
else if (obj instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
*/
|
||||
|
||||
EncryptStreamResult result = new EncryptStreamResult();
|
||||
|
||||
boolean asymmetricPacketFound = false;
|
||||
@@ -739,11 +519,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
if (secretKeyRing == null) {
|
||||
// continue with the next packet in the while loop
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// allow only specific keys for decryption?
|
||||
if (input.getAllowedKeyIds() != null) {
|
||||
@@ -763,11 +538,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
// get subkey which has been used for this encryption packet
|
||||
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
|
||||
if (secretEncryptionKey == null) {
|
||||
// should actually never happen, so no need to be more specific.
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* secret key exists in database and is allowed! */
|
||||
asymmetricPacketFound = true;
|
||||
@@ -969,30 +739,31 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
byte[] clearText;
|
||||
{ // read cleartext
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
updateProgress(R.string.progress_reading_data, 0, 100);
|
||||
|
||||
updateProgress(R.string.progress_reading_data, 0, 100);
|
||||
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
|
||||
int lookAhead = readInputLine(lineOut, aIn);
|
||||
byte[] lineSep = getLineSeparator();
|
||||
|
||||
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
|
||||
int lookAhead = readInputLine(lineOut, aIn);
|
||||
byte[] lineSep = getLineSeparator();
|
||||
|
||||
byte[] line = lineOut.toByteArray();
|
||||
out.write(line, 0, getLengthWithoutSeparator(line));
|
||||
out.write(lineSep);
|
||||
|
||||
while (lookAhead != -1 && aIn.isClearText()) {
|
||||
lookAhead = readInputLine(lineOut, lookAhead, aIn);
|
||||
line = lineOut.toByteArray();
|
||||
byte[] line = lineOut.toByteArray();
|
||||
out.write(line, 0, getLengthWithoutSeparator(line));
|
||||
out.write(lineSep);
|
||||
|
||||
while (lookAhead != -1 && aIn.isClearText()) {
|
||||
lookAhead = readInputLine(lineOut, lookAhead, aIn);
|
||||
line = lineOut.toByteArray();
|
||||
out.write(line, 0, getLengthWithoutSeparator(line));
|
||||
out.write(lineSep);
|
||||
}
|
||||
|
||||
out.close();
|
||||
clearText = out.toByteArray();
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
byte[] clearText = out.toByteArray();
|
||||
if (outputStream != null) {
|
||||
outputStream.write(clearText);
|
||||
outputStream.close();
|
||||
@@ -1001,51 +772,20 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
|
||||
|
||||
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
|
||||
if (sigList == null) {
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
|
||||
|
||||
Object o = pgpFact.nextObject();
|
||||
if (!signatureChecker.initializeSignature(o, log, indent+1)) {
|
||||
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
|
||||
|
||||
if (signature != null) {
|
||||
if (signatureChecker.isInitialized()) {
|
||||
try {
|
||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||
|
||||
InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
|
||||
|
||||
lookAhead = readInputLine(lineOut, sigIn);
|
||||
|
||||
processLine(signature, lineOut.toByteArray());
|
||||
|
||||
if (lookAhead != -1) {
|
||||
do {
|
||||
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
|
||||
|
||||
signature.update((byte) '\r');
|
||||
signature.update((byte) '\n');
|
||||
|
||||
processLine(signature, lineOut.toByteArray());
|
||||
} while (lookAhead != -1);
|
||||
}
|
||||
|
||||
// Verify signature and check binding signatures
|
||||
boolean validSignature = signature.verify();
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
signatureChecker.updateSignatureWithCleartext(clearText);
|
||||
signatureChecker.verifySignature(log, indent);
|
||||
|
||||
} catch (SignatureException e) {
|
||||
Log.d(Constants.TAG, "SignatureException", e);
|
||||
@@ -1064,7 +804,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
clearText.length);
|
||||
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureResultBuilder.build());
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setDecryptionResult(
|
||||
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
|
||||
result.setDecryptionMetadata(metadata);
|
||||
@@ -1078,30 +818,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
|
||||
updateProgress(R.string.progress_processing_signature, 0, 100);
|
||||
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
|
||||
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
|
||||
|
||||
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
|
||||
|
||||
PGPSignatureList sigList;
|
||||
Object o = pgpFact.nextObject();
|
||||
if (o instanceof PGPCompressedData) {
|
||||
PGPCompressedData c1 = (PGPCompressedData) o;
|
||||
pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
|
||||
sigList = (PGPSignatureList) pgpFact.nextObject();
|
||||
} else if (o instanceof PGPSignatureList) {
|
||||
sigList = (PGPSignatureList) o;
|
||||
} else {
|
||||
o = pgpFact.nextObject();
|
||||
}
|
||||
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
|
||||
|
||||
if ( ! signatureChecker.initializeSignature(o, log, indent+1)) {
|
||||
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
|
||||
if (signatureChecker.isInitialized()) {
|
||||
|
||||
if (signature != null) {
|
||||
updateProgress(R.string.progress_reading_data, 60, 100);
|
||||
|
||||
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
|
||||
@@ -1116,7 +854,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
|
||||
// update signature buffer if signature is also present
|
||||
signature.update(buffer, 0, length);
|
||||
signatureChecker.updateSignatureData(buffer, 0, length);
|
||||
|
||||
alreadyWritten += length;
|
||||
if (wholeSize > 0) {
|
||||
@@ -1127,105 +865,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
}
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
// TODO: slow annealing to fake a progress?
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||
|
||||
// Verify signature and check binding signatures
|
||||
boolean validSignature = signature.verify();
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
signatureChecker.verifySignature(log, indent);
|
||||
|
||||
// check for insecure hash algorithms
|
||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_DC_OK, indent);
|
||||
|
||||
// TODO return metadata object?
|
||||
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureResultBuilder.build());
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setDecryptionResult(
|
||||
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
|
||||
return result;
|
||||
}
|
||||
|
||||
private PGPSignature processPGPSignatureList(
|
||||
PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder,
|
||||
OperationLog log, int indent)
|
||||
throws PGPException {
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
int signatureIndex = -1;
|
||||
|
||||
// go through all signatures
|
||||
// and find out for which signature we have a key in our database
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
|
||||
PGPSignature signature = null;
|
||||
|
||||
if (signingKey != null) {
|
||||
// key found in our database!
|
||||
signature = sigList.get(signatureIndex);
|
||||
|
||||
signatureResultBuilder.initValid(signingRing, signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
} else {
|
||||
// no key in our database -> return "unknown pub key" status including the first key id
|
||||
if (!sigList.isEmpty()) {
|
||||
signatureResultBuilder.setSignatureAvailable(true);
|
||||
signatureResultBuilder.setKnownKey(false);
|
||||
signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
|
||||
}
|
||||
}
|
||||
|
||||
// check for insecure signing key
|
||||
// TODO: checks on signingRing ?
|
||||
if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||
*/
|
||||
private static void processLine(PGPSignature sig, byte[] line)
|
||||
throws SignatureException {
|
||||
int length = getLengthWithoutWhiteSpace(line);
|
||||
if (length > 0) {
|
||||
sig.update(line, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
|
||||
throws IOException {
|
||||
bOut.reset();
|
||||
@@ -1291,20 +952,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
return b == '\r' || b == '\n';
|
||||
}
|
||||
|
||||
private static int getLengthWithoutWhiteSpace(byte[] line) {
|
||||
int end = line.length - 1;
|
||||
|
||||
while (end >= 0 && isWhiteSpace(line[end])) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpace(byte b) {
|
||||
return b == '\r' || b == '\n' || b == '\t' || b == ' ';
|
||||
}
|
||||
|
||||
private static byte[] getLineSeparator() {
|
||||
String nl = System.getProperty("line.separator");
|
||||
return nl.getBytes();
|
||||
|
||||
@@ -171,8 +171,8 @@ public class PgpKeyOperation {
|
||||
log.add(LogType.MSG_CR_ERROR_NO_KEYSIZE, indent);
|
||||
return null;
|
||||
}
|
||||
if (add.mKeySize < 512) {
|
||||
log.add(LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
|
||||
if (add.mKeySize < 2048) {
|
||||
log.add(LogType.MSG_CR_ERROR_KEYSIZE_2048, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,9 +492,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
|
||||
literalGen.close();
|
||||
} else {
|
||||
pOut = null;
|
||||
// TODO: Is this log right?
|
||||
log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent);
|
||||
throw new AssertionError("cannot clearsign in non-ascii armored text, this is a bug!");
|
||||
}
|
||||
|
||||
if (enableSignature) {
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SignatureException;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
/** This class is used to track the state of a single signature verification.
|
||||
*
|
||||
*
|
||||
*/
|
||||
class PgpSignatureChecker {
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
|
||||
private CanonicalizedPublicKey signingKey;
|
||||
|
||||
private int signatureIndex;
|
||||
PGPOnePassSignature onePassSignature;
|
||||
PGPSignature signature;
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
PgpSignatureChecker(ProviderHelper providerHelper) {
|
||||
mProviderHelper = providerHelper;
|
||||
}
|
||||
|
||||
boolean initializeSignature(Object dataChunk, OperationLog log, int indent) throws PGPException {
|
||||
|
||||
if (!(dataChunk instanceof PGPSignatureList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PGPSignatureList sigList = (PGPSignatureList) dataChunk;
|
||||
findAvailableSignature(sigList);
|
||||
|
||||
if (signingKey != null) {
|
||||
|
||||
// key found in our database!
|
||||
signatureResultBuilder.initValid(signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
checkKeySecurity(log, indent);
|
||||
|
||||
|
||||
} else if (!sigList.isEmpty()) {
|
||||
|
||||
signatureResultBuilder.setSignatureAvailable(true);
|
||||
signatureResultBuilder.setKnownKey(false);
|
||||
signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
boolean initializeOnePassSignature(Object dataChunk, OperationLog log, int indent) throws PGPException {
|
||||
|
||||
if (!(dataChunk instanceof PGPOnePassSignatureList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
|
||||
|
||||
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
|
||||
findAvailableSignature(sigList);
|
||||
|
||||
if (signingKey != null) {
|
||||
|
||||
// key found in our database!
|
||||
signatureResultBuilder.initValid(signingKey);
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
onePassSignature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
|
||||
|
||||
checkKeySecurity(log, indent);
|
||||
|
||||
} else if (!sigList.isEmpty()) {
|
||||
|
||||
signatureResultBuilder.setSignatureAvailable(true);
|
||||
signatureResultBuilder.setKnownKey(false);
|
||||
signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private void checkKeySecurity(OperationLog log, int indent) {
|
||||
// TODO: checks on signingRing ?
|
||||
if (!PgpSecurityConstants.isSecureKey(signingKey)) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return signingKey != null;
|
||||
}
|
||||
|
||||
private void findAvailableSignature(PGPOnePassSignatureList sigList) {
|
||||
// go through all signatures (should be just one), make sure we have
|
||||
// the key and it matches the one we’re looking for
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
|
||||
if ( ! signingKey.canSign()) {
|
||||
continue;
|
||||
}
|
||||
signatureIndex = i;
|
||||
signingKey = keyCandidate;
|
||||
onePassSignature = sigList.get(i);
|
||||
return;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void findAvailableSignature(PGPSignatureList sigList) {
|
||||
// go through all signatures (should be just one), make sure we have
|
||||
// the key and it matches the one we’re looking for
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
|
||||
if ( ! signingKey.canSign()) {
|
||||
continue;
|
||||
}
|
||||
signatureIndex = i;
|
||||
signingKey = keyCandidate;
|
||||
signature = sigList.get(i);
|
||||
return;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found, trying next signature...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSignatureWithCleartext(byte[] clearText) throws IOException, SignatureException {
|
||||
|
||||
InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
|
||||
|
||||
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
|
||||
|
||||
int lookAhead = readInputLine(outputBuffer, sigIn);
|
||||
|
||||
processLine(signature, outputBuffer.toByteArray());
|
||||
|
||||
while (lookAhead != -1) {
|
||||
lookAhead = readInputLine(outputBuffer, lookAhead, sigIn);
|
||||
|
||||
signature.update((byte) '\r');
|
||||
signature.update((byte) '\n');
|
||||
|
||||
processLine(signature, outputBuffer.toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateSignatureData(byte[] buf, int off, int len) {
|
||||
if (signature != null) {
|
||||
signature.update(buf, off, len);
|
||||
} else if (onePassSignature != null) {
|
||||
onePassSignature.update(buf, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
void verifySignature(OperationLog log, int indent) throws PGPException {
|
||||
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||
|
||||
// Verify signature
|
||||
boolean validSignature = signature.verify();
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
|
||||
}
|
||||
|
||||
boolean verifySignatureOnePass(Object o, OperationLog log, int indent) throws PGPException {
|
||||
|
||||
if (!(o instanceof PGPSignatureList)) {
|
||||
log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent);
|
||||
return false;
|
||||
}
|
||||
PGPSignatureList signatureList = (PGPSignatureList) o;
|
||||
if (signatureList.size() <= signatureIndex) {
|
||||
log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent);
|
||||
return false;
|
||||
}
|
||||
|
||||
// PGPOnePassSignature and PGPSignature packets are "bracketed",
|
||||
// so we need to take the last-minus-index'th element here
|
||||
PGPSignature messageSignature = signatureList.get(signatureList.size() - 1 - signatureIndex);
|
||||
|
||||
// Verify signature
|
||||
boolean validSignature = onePassSignature.verify(messageSignature);
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(onePassSignature.getHashAlgorithm())) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public byte[] getSigningFingerprint() {
|
||||
return signingKey.getFingerprint();
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult getSignatureResult() {
|
||||
return signatureResultBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||
*/
|
||||
|
||||
private static void processLine(PGPSignature sig, byte[] line)
|
||||
throws SignatureException {
|
||||
int length = getLengthWithoutWhiteSpace(line);
|
||||
if (length > 0) {
|
||||
sig.update(line, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
|
||||
throws IOException {
|
||||
bOut.reset();
|
||||
|
||||
int lookAhead = -1;
|
||||
int ch;
|
||||
|
||||
while ((ch = fIn.read()) >= 0) {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPastEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
|
||||
throws IOException {
|
||||
bOut.reset();
|
||||
|
||||
int ch = lookAhead;
|
||||
|
||||
do {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPastEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
} while ((ch = fIn.read()) >= 0);
|
||||
|
||||
if (ch < 0) {
|
||||
lookAhead = -1;
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
|
||||
throws IOException {
|
||||
int lookAhead = fIn.read();
|
||||
|
||||
if (lastCh == '\r' && lookAhead == '\n') {
|
||||
bOut.write(lookAhead);
|
||||
lookAhead = fIn.read();
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int getLengthWithoutWhiteSpace(byte[] line) {
|
||||
int end = line.length - 1;
|
||||
|
||||
while (end >= 0 && isWhiteSpace(line[end])) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpace(byte b) {
|
||||
return b == '\r' || b == '\n' || b == '\t' || b == ' ';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -83,6 +83,8 @@ public class UncachedKeyRing {
|
||||
final PGPKeyRing mRing;
|
||||
final boolean mIsSecret;
|
||||
|
||||
private static final int CANONICALIZE_MAX_USER_IDS = 100;
|
||||
|
||||
UncachedKeyRing(PGPKeyRing ring) {
|
||||
mRing = ring;
|
||||
mIsSecret = ring instanceof PGPSecretKeyRing;
|
||||
@@ -460,7 +462,7 @@ public class UncachedKeyRing {
|
||||
// strip out the first found user id with this name
|
||||
modified = PGPPublicKey.removeCertification(modified, rawUserId);
|
||||
}
|
||||
if (processedUserIds.size() > 100) {
|
||||
if (processedUserIds.size() > CANONICALIZE_MAX_USER_IDS) {
|
||||
log.add(LogType.MSG_KC_UID_TOO_MANY, indent, userId);
|
||||
// strip out the user id
|
||||
modified = PGPPublicKey.removeCertification(modified, rawUserId);
|
||||
|
||||
@@ -62,7 +62,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
@@ -968,7 +967,7 @@ public class ProviderHelper {
|
||||
|
||||
// If we have an expected fingerprint, make sure it matches
|
||||
if (expectedFingerprint != null) {
|
||||
if (!canPublicRing.containsSubkey(expectedFingerprint)) {
|
||||
if (!canPublicRing.containsBoundSubkey(expectedFingerprint)) {
|
||||
log(LogType.MSG_IP_FINGERPRINT_ERROR);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||
} else {
|
||||
|
||||
@@ -49,7 +49,7 @@ public class CreateYubiKeyImportFragment
|
||||
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
|
||||
implements NfcListenerFragment {
|
||||
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
private static final String ARG_FINGERPRINTS = "fingerprint";
|
||||
public static final String ARG_AID = "aid";
|
||||
public static final String ARG_USER_ID = "user_ids";
|
||||
|
||||
@@ -72,7 +72,7 @@ public class CreateYubiKeyImportFragment
|
||||
CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
|
||||
args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints);
|
||||
args.putByteArray(ARG_AID, nfcAid);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
frag.setArguments(args);
|
||||
@@ -86,7 +86,7 @@ public class CreateYubiKeyImportFragment
|
||||
|
||||
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
|
||||
mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT);
|
||||
mNfcFingerprints = args.getByteArray(ARG_FINGERPRINTS);
|
||||
mNfcAid = args.getByteArray(ARG_AID);
|
||||
mNfcUserId = args.getString(ARG_USER_ID);
|
||||
|
||||
@@ -149,7 +149,7 @@ public class CreateYubiKeyImportFragment
|
||||
public void onSaveInstanceState(Bundle args) {
|
||||
super.onSaveInstanceState(args);
|
||||
|
||||
args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints);
|
||||
args.putByteArray(ARG_FINGERPRINTS, mNfcFingerprints);
|
||||
args.putByteArray(ARG_AID, mNfcAid);
|
||||
args.putString(ARG_USER_ID, mNfcUserId);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
public class DisplayTextFragment extends DecryptFragment {
|
||||
|
||||
@@ -60,22 +59,6 @@ public class DisplayTextFragment extends DecryptFragment {
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude decrypt activites
|
||||
*/
|
||||
private Intent sendWithChooserExcludingDecrypt(String text) {
|
||||
Intent prototype = createSendIntent(text);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to decrypt the decrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.DecryptActivity",
|
||||
"org.thialfihar.android.apg.ui.DecryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(String text) {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
|
||||
@@ -146,7 +129,8 @@ public class DisplayTextFragment extends DecryptFragment {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.decrypt_share: {
|
||||
startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString()));
|
||||
startActivity(Intent.createChooser(createSendIntent(mText.getText().toString()),
|
||||
getString(R.string.title_share_message)));
|
||||
break;
|
||||
}
|
||||
case R.id.decrypt_copy: {
|
||||
|
||||
@@ -71,7 +71,6 @@ import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
public class EncryptFilesFragment
|
||||
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
|
||||
@@ -406,7 +405,7 @@ public class EncryptFilesFragment
|
||||
public void onDeleted() {
|
||||
if (mAfterEncryptAction == AfterEncryptAction.SHARE) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file)));
|
||||
} else {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
@@ -426,7 +425,7 @@ public class EncryptFilesFragment
|
||||
|
||||
case SHARE:
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file)));
|
||||
break;
|
||||
|
||||
case COPY:
|
||||
@@ -622,22 +621,6 @@ public class EncryptFilesFragment
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptFilesActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
@@ -289,22 +288,6 @@ public class EncryptTextFragment
|
||||
result.createNotify(activity).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
|
||||
Intent prototype = createSendIntent(resultBytes);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
@@ -343,7 +326,8 @@ public class EncryptTextFragment
|
||||
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()),
|
||||
getString(R.string.title_share_message)));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(result);
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEnt
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareLogDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
@@ -149,11 +150,9 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
||||
}
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, mLogTempFile);
|
||||
intent.setType("text/plain");
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(intent);
|
||||
|
||||
ShareLogDialogFragment shareLogDialog = ShareLogDialogFragment.newInstance(mLogTempFile);
|
||||
shareLogDialog.show(getActivity().getSupportFragmentManager(), "shareLogDialog");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -348,30 +348,30 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
|
||||
/**
|
||||
* <h3>RSA</h3>
|
||||
* <p>for RSA algorithm, key length must be greater than 1024 (according to
|
||||
* <a href="https://github.com/open-keychain/open-keychain/issues/102">#102</a>). Possibility to generate keys bigger
|
||||
* <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger
|
||||
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
|
||||
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
|
||||
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
|
||||
* multiplicity of 8.</p>
|
||||
* <h3>ElGamal</h3>
|
||||
* <p>For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.</p>
|
||||
* <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p>
|
||||
* <h3>DSA</h3>
|
||||
* <p>For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.</p>
|
||||
* <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p>
|
||||
*
|
||||
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
|
||||
* inappropriate.
|
||||
*/
|
||||
private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
|
||||
final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
|
||||
final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192};
|
||||
int properKeyLength = -1;
|
||||
switch (algorithm) {
|
||||
case RSA:
|
||||
if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
|
||||
case RSA: {
|
||||
if (currentKeyLength >= 2048 && currentKeyLength <= 16384) {
|
||||
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
|
||||
}
|
||||
break;
|
||||
case ELGAMAL:
|
||||
}
|
||||
case ELGAMAL: {
|
||||
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
|
||||
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
|
||||
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
|
||||
@@ -386,11 +386,14 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
properKeyLength = elGamalSupportedLengths[minimalIndex];
|
||||
break;
|
||||
case DSA:
|
||||
if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
|
||||
}
|
||||
case DSA: {
|
||||
// Bouncy Castle supports 4096 maximum
|
||||
if (currentKeyLength >= 2048 && currentKeyLength <= 4096) {
|
||||
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return properKeyLength;
|
||||
}
|
||||
@@ -424,7 +427,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
|
||||
keySizeAdapter.clear();
|
||||
switch (algorithm) {
|
||||
case RSA:
|
||||
case RSA: {
|
||||
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
|
||||
mKeySizeSpinner.setSelection(1);
|
||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
||||
@@ -450,7 +453,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
mFlagAuthenticate.setChecked(false);
|
||||
break;
|
||||
case ELGAMAL:
|
||||
}
|
||||
case ELGAMAL: {
|
||||
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
|
||||
mKeySizeSpinner.setSelection(3);
|
||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
||||
@@ -466,7 +470,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mFlagAuthenticate.setChecked(false);
|
||||
mFlagAuthenticate.setEnabled(false);
|
||||
break;
|
||||
case DSA:
|
||||
}
|
||||
case DSA: {
|
||||
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
|
||||
mKeySizeSpinner.setSelection(2);
|
||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
||||
@@ -482,7 +487,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mFlagAuthenticate.setChecked(false);
|
||||
mFlagAuthenticate.setEnabled(false);
|
||||
break;
|
||||
case ECDSA:
|
||||
}
|
||||
case ECDSA: {
|
||||
mKeySizeRow.setVisibility(View.GONE);
|
||||
mCurveRow.setVisibility(View.VISIBLE);
|
||||
mCustomKeyInfoTextView.setText("");
|
||||
@@ -496,7 +502,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mFlagAuthenticate.setEnabled(true);
|
||||
mFlagAuthenticate.setChecked(false);
|
||||
break;
|
||||
case ECDH:
|
||||
}
|
||||
case ECDH: {
|
||||
mKeySizeRow.setVisibility(View.GONE);
|
||||
mCurveRow.setVisibility(View.VISIBLE);
|
||||
mCustomKeyInfoTextView.setText("");
|
||||
@@ -510,6 +517,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
||||
mFlagAuthenticate.setChecked(false);
|
||||
mFlagAuthenticate.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
keySizeAdapter.notifyDataSetChanged();
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
|
||||
public class ShareLogDialogFragment extends DialogFragment {
|
||||
private static final String ARG_STREAM = "stream";
|
||||
|
||||
public static ShareLogDialogFragment newInstance(Uri stream) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_STREAM, stream);
|
||||
|
||||
ShareLogDialogFragment fragment = new ShareLogDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
|
||||
final Uri stream = getArguments().getParcelable(ARG_STREAM);
|
||||
|
||||
ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity());
|
||||
|
||||
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
|
||||
builder.setTitle(R.string.share_log_dialog_title)
|
||||
.setMessage(R.string.share_log_dialog_message)
|
||||
.setNegativeButton(R.string.share_log_dialog_cancel_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.share_log_dialog_share_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, stream);
|
||||
intent.setType("text/plain");
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
return builder.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LabeledIntent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class ShareHelper {
|
||||
Context mContext;
|
||||
|
||||
public ShareHelper(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude specific activites, e.g., EncryptActivity to prevent encrypting again
|
||||
* <p/>
|
||||
* Put together from some stackoverflow posts...
|
||||
*/
|
||||
public Intent createChooserExcluding(Intent prototype, String title, String[] activityBlacklist) {
|
||||
// Produced an empty list on Huawei U8860 with Android Version 4.0.3
|
||||
// TODO: test on 4.1, 4.2, 4.3, only tested on 4.4
|
||||
// Disabled on 5.0 because using EXTRA_INITIAL_INTENTS prevents the usage based sorting
|
||||
// introduced in 5.0: https://medium.com/@xXxXxXxXxXam/how-lollipops-share-menu-is-organized-d204888f606d
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return Intent.createChooser(prototype, title);
|
||||
}
|
||||
|
||||
List<LabeledIntent> targetedShareIntents = new ArrayList<>();
|
||||
|
||||
List<ResolveInfo> resInfoList = mContext.getPackageManager().queryIntentActivities(prototype, 0);
|
||||
List<ResolveInfo> resInfoListFiltered = new ArrayList<>();
|
||||
if (!resInfoList.isEmpty()) {
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
// do not add blacklisted ones
|
||||
if (resolveInfo.activityInfo == null || Arrays.asList(activityBlacklist).contains(resolveInfo.activityInfo.name))
|
||||
continue;
|
||||
|
||||
resInfoListFiltered.add(resolveInfo);
|
||||
}
|
||||
|
||||
if (!resInfoListFiltered.isEmpty()) {
|
||||
// sorting for nice readability
|
||||
Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
|
||||
@Override
|
||||
public int compare(ResolveInfo first, ResolveInfo second) {
|
||||
String firstName = first.loadLabel(mContext.getPackageManager()).toString();
|
||||
String secondName = second.loadLabel(mContext.getPackageManager()).toString();
|
||||
return firstName.compareToIgnoreCase(secondName);
|
||||
}
|
||||
});
|
||||
|
||||
// create the custom intent list
|
||||
for (ResolveInfo resolveInfo : resInfoListFiltered) {
|
||||
Intent targetedShareIntent = (Intent) prototype.clone();
|
||||
targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
|
||||
targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||
|
||||
LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
|
||||
resolveInfo.activityInfo.packageName,
|
||||
resolveInfo.loadLabel(mContext.getPackageManager()),
|
||||
resolveInfo.activityInfo.icon);
|
||||
targetedShareIntents.add(lIntent);
|
||||
}
|
||||
|
||||
// Create chooser with only one Intent in it
|
||||
Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
|
||||
// append all other Intents
|
||||
// TODO this line looks wrong?!
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
|
||||
return chooserIntent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fallback to Android's default chooser
|
||||
return Intent.createChooser(prototype, title);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user