diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
20 files changed, 548 insertions, 627 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index aaff0a07c..f3ceac681 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -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()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index fc72a9ac2..24d1215d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -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), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 18a27dd96..6f1e78ce6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 412468a48..476b4e59c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -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()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 9d059b58f..2dd1e2dde 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -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 { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java index 3eef7759c..bc9b54cd6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 055f84b4d..2d28f70e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -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); - - PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); - PGPSignature messageSignature = signatureList.get(signatureIndex); + if (signatureChecker.isInitialized()) { - // 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); - } + Object o = plainFact.nextObject(); + boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, 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); + if (!signatureCheckOk) { + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - 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(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] clearText; + { // read cleartext + 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); - } - out.close(); + 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(); + } - 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); - } - - // check for insecure hash algorithms - if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) { - log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); - signatureResultBuilder.setInsecure(true); - } + signatureChecker.verifySignature(log, indent); - 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(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 6f156c201..59b840054 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -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; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 0879181a7..ecc190d97 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -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) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java new file mode 100644 index 000000000..4067372a1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java @@ -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 == ' '; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index c8cdbd59d..c967a5abc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index a6823d3ac..375775ff1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -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 { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java index f7925b7f4..648c5f962 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java @@ -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); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index dc06e9115..1060714f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -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: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index dab451537..3d1629ffb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index ab676285e..0513a6495 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 43c8d2643..e29cd0204 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -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"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index b51d081e1..cd5281c7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -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(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java new file mode 100644 index 000000000..d4783fad2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java @@ -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(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java deleted file mode 100644 index 0297d149c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java +++ /dev/null @@ -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); - } -} |