diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2015-09-14 16:21:04 +0200 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2015-09-14 16:21:04 +0200 |
commit | 3814ae7d53a22ba89f1e39d7a4661016f76cf8c8 (patch) | |
tree | d83d41d3c424a784da144515665fb41ef650aa52 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp | |
parent | dbaf7070ead596f5c70ad48fc55aada2f77a856a (diff) | |
parent | d5dd6a49c8156a699b3fbbbeef06658e1c232c16 (diff) | |
download | open-keychain-3814ae7d53a22ba89f1e39d7a4661016f76cf8c8.tar.gz open-keychain-3814ae7d53a22ba89f1e39d7a4661016f76cf8c8.tar.bz2 open-keychain-3814ae7d53a22ba89f1e39d7a4661016f76cf8c8.zip |
Merge branch 'master' into mime4j
Conflicts:
OpenKeychain/build.gradle
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
OpenKeychain/src/main/res/values/strings.xml
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java | 12 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java) | 693 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java | 32 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java | 74 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java | 11 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java | 1 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java | 8 |
7 files changed, 444 insertions, 387 deletions
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 770e8de91..18a27dd96 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import java.io.IOException; @@ -28,6 +29,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; @@ -152,4 +154,14 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getRing().getEncoded(); } + public boolean containsSubkey(String expectedFingerprint) { + for (CanonicalizedPublicKey key : publicKeyIterator()) { + if (KeyFormattingUtils.convertFingerprintToHex( + key.getFingerprint()).equalsIgnoreCase(expectedFingerprint)) { + return true; + } + } + return false; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 1e51403fc..dda15f382 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -79,9 +79,9 @@ import java.security.SignatureException; import java.util.Date; import java.util.Iterator; -public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> { +public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> { - public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) { + public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); } @@ -313,6 +313,27 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return result; } + private static class EncryptStreamResult { + + // this is non-null iff an error occured, return directly + DecryptVerifyResult errorResult; + + // for verification + PGPEncryptedData encryptedData; + InputStream cleartextStream; + + int symmetricEncryptionAlgo = 0; + + boolean skippedDisallowedKey = false; + boolean insecureEncryptionKey = false; + + // convenience method to return with error + public EncryptStreamResult with(DecryptVerifyResult result) { + errorResult = result; + return this; + } + + } /** Decrypt and/or verify binary or ascii armored pgp data. */ @NonNull @@ -320,42 +341,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput, InputStream in, OutputStream out, int indent) throws IOException, PGPException { - OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); - OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder(); OperationLog log = new OperationLog(); log.add(LogType.MSG_DC, indent); indent += 1; - JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - int currentProgress = 0; updateProgress(R.string.progress_reading_data, currentProgress, 100); - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); - } - - InputStream clear; - PGPEncryptedData encryptedData; - - PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; - PGPPBEEncryptedData encryptedDataSymmetric = null; - CanonicalizedSecretKey secretEncryptionKey = null; - Iterator<?> it = enc.getEncryptedDataObjects(); - boolean asymmetricPacketFound = false; - boolean symmetricPacketFound = false; - boolean anyPacketFound = false; - // If the input stream is armored, and there is a charset specified, take a note for later // https://tools.ietf.org/html/rfc4880#page56 String charset = null; @@ -375,8 +368,332 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } + OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder(); + + JcaPGPObjectFactory plainFact; + Object dataChunk; + EncryptStreamResult esResult = null; + { // resolve encrypted (symmetric and asymmetric) packets + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); + Object obj = pgpF.nextObject(); + + if (obj instanceof PGPEncryptedDataList) { + esResult = handleEncryptedPacket( + input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, currentProgress); + + // if there is an error, nothing left to do here + if (esResult.errorResult != null) { + return esResult.errorResult; + } + + // if this worked out so far, the data is encrypted + decryptionResultBuilder.setEncrypted(true); + + if (esResult.insecureEncryptionKey) { + log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1); + decryptionResultBuilder.setInsecure(true); + } + + // Check for insecure encryption algorithms! + if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo)) { + log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1); + decryptionResultBuilder.setInsecure(true); + } + + plainFact = new JcaPGPObjectFactory(esResult.cleartextStream); + dataChunk = plainFact.nextObject(); + + } else { + decryptionResultBuilder.setEncrypted(false); + + plainFact = pgpF; + dataChunk = obj; + } + + } + + 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; + + // resolve compressed data + if (dataChunk instanceof PGPCompressedData) { + log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); + currentProgress += 2; + updateProgress(R.string.progress_decompressing_data, currentProgress, 100); + + PGPCompressedData compressedData = (PGPCompressedData) dataChunk; + + JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream()); + dataChunk = fact.nextObject(); + 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); + } + + dataChunk = plainFact.nextObject(); + } + + if (dataChunk instanceof PGPSignatureList) { + // skip + dataChunk = plainFact.nextObject(); + } + + OpenPgpMetadata metadata; + + if ( ! (dataChunk instanceof PGPLiteralData)) { + + log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + + } + + log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); + indent += 2; + currentProgress += 4; + updateProgress(R.string.progress_decrypting, currentProgress, 100); + + PGPLiteralData literalData = (PGPLiteralData) dataChunk; + + String originalFilename = literalData.getFileName(); + if (originalFilename.contains("/")) { + originalFilename = originalFilename.substring(originalFilename.lastIndexOf('/')); + } + String mimeType = null; + if (literalData.getFormat() == PGPLiteralData.TEXT + || literalData.getFormat() == PGPLiteralData.UTF8) { + mimeType = "text/plain"; + } else { + // try to guess from file ending + String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename); + if (extension != null) { + MimeTypeMap mime = MimeTypeMap.getSingleton(); + mimeType = mime.getMimeTypeFromExtension(extension); + } + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + } + + if (!"".equals(originalFilename)) { + log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); + } + log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, + mimeType); + log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, + new Date(literalData.getModificationTime().getTime()).toString()); + + // return here if we want to decrypt the metadata only + if (input.isDecryptMetadataOnly()) { + + // this operation skips the entire stream to find the data length! + Long originalSize = literalData.findDataLength(); + + if (originalSize != null) { + log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, + Long.toString(originalSize)); + } else { + log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); + } + + metadata = new OpenPgpMetadata( + originalFilename, + mimeType, + literalData.getModificationTime().getTime(), + originalSize == null ? 0 : originalSize); + + log.add(LogType.MSG_DC_OK_META_ONLY, indent); + DecryptVerifyResult result = + new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setCharset(charset); + result.setDecryptionMetadata(metadata); + 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); + + InputStream dataIn = literalData.getInputStream(); + + long alreadyWritten = 0; + long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition(); + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = dataIn.read(buffer)) > 0) { + // Log.d(Constants.TAG, "read bytes: " + length); + if (out != null) { + out.write(buffer, 0, length); + } + + // update signature buffer if signature is also present + if (signature != null) { + signature.update(buffer, 0, length); + } + + alreadyWritten += length; + if (wholeSize > 0) { + long progress = 100 * alreadyWritten / wholeSize; + // stop at 100% for wrong file sizes... + if (progress > 100) { + progress = 100; + } + progressScaler.setProgress((int) progress, 100); + } + // TODO: slow annealing to fake a progress? + } + + metadata = new OpenPgpMetadata( + originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten); + + 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); + + // 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); + } + + // 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; + + if (esResult != null) { + if (esResult.encryptedData.isIntegrityProtected()) { + updateProgress(R.string.progress_verifying_integrity, 95, 100); + + if (esResult.encryptedData.verify()) { + log.add(LogType.MSG_DC_INTEGRITY_CHECK_OK, indent); + } else { + log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + } else if (signature == null) { + // 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! + log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent); + decryptionResultBuilder.setInsecure(true); + } + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_DC_OK, indent); + + // Return a positive result, with metadata and verification info + DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + + result.setCachedCryptoInputParcel(cryptoInput); + result.setSignatureResult(signatureResultBuilder.build()); + result.setCharset(charset); + result.setDecryptionResult(decryptionResultBuilder.build()); + result.setDecryptionMetadata(metadata); + + return result; + + } + + 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; + boolean symmetricPacketFound = false; + boolean anyPacketFound = false; + + PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; + PGPPBEEncryptedData encryptedDataSymmetric = null; + CanonicalizedSecretKey secretEncryptionKey = null; + Passphrase passphrase = null; - boolean skippedDisallowedKey = false; + + Iterator<?> it = enc.getEncryptedDataObjects(); // go through all objects and find one we can decrypt while (it.hasNext()) { @@ -420,7 +737,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> if (!input.getAllowedKeyIds().contains(masterKeyId)) { // this key is in our db, but NOT allowed! // continue with the next packet in the while loop - skippedDisallowedKey = true; + result.skippedDisallowedKey = true; log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1); continue; } @@ -451,23 +768,23 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); } catch (PassphraseCacheInterface.NoSecretKeyException e) { log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } // if passphrase was not cached, return here indicating that a passphrase is missing! if (passphrase == null) { log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); - return new DecryptVerifyResult(log, + return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createRequiredDecryptPassphrase( - secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()), - cryptoInput); + secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()), + cryptoInput)); } } // check for insecure encryption key if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) { log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1); - decryptionResultBuilder.setInsecure(true); + result.insecureEncryptionKey = true; } // break out of while, only decrypt the first packet where we have a key @@ -504,9 +821,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> if (passphrase == null) { log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); - return new DecryptVerifyResult(log, + return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createRequiredSymmetricPassphrase(), - cryptoInput); + cryptoInput)); } } else { @@ -533,10 +850,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } - log.add(LogType.MSG_DC_PREP_STREAMS, indent); - // we made sure above one of these two would be true - int symmetricEncryptionAlgo; if (symmetricPacketFound) { currentProgress += 2; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); @@ -548,16 +862,16 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> passphrase.getCharArray()); try { - clear = encryptedDataSymmetric.getDataStream(decryptorFactory); + result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory); } catch (PGPDataValidationException e) { - log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent +1); - return new DecryptVerifyResult(log, - RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput); + log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1); + return result.with(new DecryptVerifyResult(log, + RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput)); } - encryptedData = encryptedDataSymmetric; + result.encryptedData = encryptedDataSymmetric; - symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory); + result.symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory); } else if (asymmetricPacketFound) { currentProgress += 2; updateProgress(R.string.progress_extracting_key, currentProgress, 100); @@ -566,11 +880,11 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> log.add(LogType.MSG_DC_UNLOCKING, indent + 1); if (!secretEncryptionKey.unlock(passphrase)) { log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } } catch (PgpGeneralException e) { log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } currentProgress += 2; @@ -585,300 +899,41 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) { log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); - return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation( + return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation( secretEncryptionKey.getRing().getMasterKeyId(), secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0] - ), - cryptoInput); + ), cryptoInput)); } try { - clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); + result.cleartextStream = encryptedDataAsymmetric.getDataStream(decryptorFactory); } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) { log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } - symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); + result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); + result.encryptedData = encryptedDataAsymmetric; cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys()); - encryptedData = encryptedDataAsymmetric; } else { // there wasn't even any useful data if (!anyPacketFound) { log.add(LogType.MSG_DC_ERROR_NO_DATA, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log)); } // there was data but key wasn't allowed - if (skippedDisallowedKey) { + if (result.skippedDisallowedKey) { log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log)); } // no packet has been found where we have the corresponding secret key in our db log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); - } - decryptionResultBuilder.setEncrypted(true); - - // Check for insecure encryption algorithms! - if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(symmetricEncryptionAlgo)) { - log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1); - decryptionResultBuilder.setInsecure(true); - } - - JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); - Object dataChunk = plainFact.nextObject(); - int signatureIndex = -1; - CanonicalizedPublicKeyRing signingRing = null; - CanonicalizedPublicKey signingKey = null; - - log.add(LogType.MSG_DC_CLEAR, indent); - indent += 1; - - if (dataChunk instanceof PGPCompressedData) { - log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); - currentProgress += 2; - updateProgress(R.string.progress_decompressing_data, currentProgress, 100); - - PGPCompressedData compressedData = (PGPCompressedData) dataChunk; - - JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream()); - dataChunk = fact.nextObject(); - plainFact = fact; - } - - 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); - } - - dataChunk = plainFact.nextObject(); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } - if (dataChunk instanceof PGPSignatureList) { - // skip - dataChunk = plainFact.nextObject(); - } - - OpenPgpMetadata metadata; - - if (dataChunk instanceof PGPLiteralData) { - log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); - indent += 2; - currentProgress += 4; - updateProgress(R.string.progress_decrypting, currentProgress, 100); - - PGPLiteralData literalData = (PGPLiteralData) dataChunk; - - String originalFilename = literalData.getFileName(); - String mimeType = null; - if (literalData.getFormat() == PGPLiteralData.TEXT - || literalData.getFormat() == PGPLiteralData.UTF8) { - mimeType = "text/plain"; - } else { - // try to guess from file ending - String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename); - if (extension != null) { - MimeTypeMap mime = MimeTypeMap.getSingleton(); - mimeType = mime.getMimeTypeFromExtension(extension); - } - if (mimeType == null) { - mimeType = "application/octet-stream"; - } - } - - if (!"".equals(originalFilename)) { - log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); - } - log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, - mimeType); - log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, - new Date(literalData.getModificationTime().getTime()).toString()); - - // return here if we want to decrypt the metadata only - if (input.isDecryptMetadataOnly()) { - - // this operation skips the entire stream to find the data length! - Long originalSize = literalData.findDataLength(); - - if (originalSize != null) { - log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, - Long.toString(originalSize)); - } else { - log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); - } - - metadata = new OpenPgpMetadata( - originalFilename, - mimeType, - literalData.getModificationTime().getTime(), - originalSize == null ? 0 : originalSize); - - log.add(LogType.MSG_DC_OK_META_ONLY, indent); - DecryptVerifyResult result = - new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); - result.setCharset(charset); - result.setDecryptionMetadata(metadata); - return result; - } - - int endProgress; - if (signature != null) { - endProgress = 90; - } else if (encryptedData.isIntegrityProtected()) { - endProgress = 95; - } else { - endProgress = 100; - } - ProgressScaler progressScaler = - new ProgressScaler(mProgressable, currentProgress, endProgress, 100); - - InputStream dataIn = literalData.getInputStream(); - - long alreadyWritten = 0; - long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition(); - int length; - byte[] buffer = new byte[1 << 16]; - while ((length = dataIn.read(buffer)) > 0) { - // Log.d(Constants.TAG, "read bytes: " + length); - if (out != null) { - out.write(buffer, 0, length); - } - - // update signature buffer if signature is also present - if (signature != null) { - signature.update(buffer, 0, length); - } - - alreadyWritten += length; - if (wholeSize > 0) { - long progress = 100 * alreadyWritten / wholeSize; - // stop at 100% for wrong file sizes... - if (progress > 100) { - progress = 100; - } - progressScaler.setProgress((int) progress, 100); - } - // TODO: slow annealing to fake a progress? - } - - metadata = new OpenPgpMetadata( - originalFilename, - mimeType, - literalData.getModificationTime().getTime(), - alreadyWritten); - - 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); - - // TODO: what about binary signatures? - - // 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); - } - - // 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; - } else { - // If there is no literalData, we don't have any metadata - metadata = null; - } - - if (encryptedData.isIntegrityProtected()) { - updateProgress(R.string.progress_verifying_integrity, 95, 100); - - if (encryptedData.verify()) { - log.add(LogType.MSG_DC_INTEGRITY_CHECK_OK, indent); - } else { - log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent); - return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); - } - } else { - // If no valid signature is present: - // Handle missing integrity protection like failed integrity protection! - // The MDC packet can be stripped by an attacker! - Log.d(Constants.TAG, "MDC fail"); - if (!signatureResultBuilder.isValidSignature()) { - log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent); - decryptionResultBuilder.setInsecure(true); - } - } - - updateProgress(R.string.progress_done, 100, 100); - - log.add(LogType.MSG_DC_OK, indent); - - // Return a positive result, with metadata and verification info - DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); - result.setCachedCryptoInputParcel(cryptoInput); - result.setSignatureResult(signatureResultBuilder.build()); - result.setCharset(charset); - result.setDecryptionResult(decryptionResultBuilder.build()); - result.setDecryptionMetadata(metadata); return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 32718fb4e..e8d1d3111 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.support.annotation.NonNull; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; @@ -116,32 +117,27 @@ public class PgpHelper { } } - public static String getPgpContent(CharSequence input) { - // only decrypt if clipboard content is available and a pgp message or cleartext signature - if (!TextUtils.isEmpty(input)) { - Log.dEscaped(Constants.TAG, "input: " + input); + public static String getPgpContent(@NonNull CharSequence input) { + Log.dEscaped(Constants.TAG, "input: " + input); + + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); + if (matcher.matches()) { + String text = matcher.group(1); + text = fixPgpMessage(text); - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input); + Log.dEscaped(Constants.TAG, "input fixed: " + text); + return text; + } else { + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); if (matcher.matches()) { String text = matcher.group(1); - text = fixPgpMessage(text); + text = fixPgpCleartextSignature(text); Log.dEscaped(Constants.TAG, "input fixed: " + text); return text; } else { - matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input); - if (matcher.matches()) { - String text = matcher.group(1); - text = fixPgpCleartextSignature(text); - - Log.dEscaped(Constants.TAG, "input fixed: " + text); - return text; - } else { - return null; - } + return null; } - } else { - return null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java index 3fa549946..cbd8ce47a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java @@ -23,6 +23,7 @@ import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import java.util.Arrays; import java.util.HashSet; /** @@ -42,24 +43,23 @@ public class PgpSecurityConstants { * Whitelist of accepted symmetric encryption algorithms * all other algorithms are rejected with OpenPgpDecryptionResult.RESULT_INSECURE */ - private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>(); - static { - // General remarks: We try to keep the whitelist short to reduce attack surface - // TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors) - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.IDEA); - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TRIPLE_DES); // a MUST in RFC - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.CAST5); // default in many gpg, pgp versions, 128 bit key - // BLOWFISH: Twofish is the successor - // SAFER: not used widely - // DES: < 128 bit security - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_128); - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_192); - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_256); - sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TWOFISH); // 128 bit - // CAMELLIA_128: not used widely - // CAMELLIA_192: not used widely - // CAMELLIA_256: not used widely - } + private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>(Arrays.asList( + // General remarks: We try to keep the whitelist short to reduce attack surface + // TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors) + SymmetricKeyAlgorithmTags.IDEA, + SymmetricKeyAlgorithmTags.TRIPLE_DES, // a MUST in RFC + SymmetricKeyAlgorithmTags.CAST5, // default in many gpg, pgp versions, 128 bit key + // BLOWFISH: Twofish is the successor + // SAFER: not used widely + // DES: < 128 bit security + SymmetricKeyAlgorithmTags.AES_128, + SymmetricKeyAlgorithmTags.AES_192, + SymmetricKeyAlgorithmTags.AES_256, + SymmetricKeyAlgorithmTags.TWOFISH // 128 bit + // CAMELLIA_128: not used widely + // CAMELLIA_192: not used widely + // CAMELLIA_256: not used widely + )); public static boolean isSecureSymmetricAlgorithm(int id) { return sSymmetricAlgorithmsWhitelist.contains(id); @@ -77,20 +77,19 @@ public class PgpSecurityConstants { * ((collision resistance of 112-bits)) * Implementations SHOULD NOT sign SHA-256 hashes. They MUST NOT default to signing SHA-256 hashes. */ - private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>(); - static { - // MD5: broken - // SHA1: broken - // RIPEMD160: same security properties as SHA1 - // DOUBLE_SHA: not used widely - // MD2: not used widely - // TIGER_192: not used widely - // HAVAL_5_160: not used widely - sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA256); // compatibility for old Mailvelope versions - sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA384); - sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA512); - // SHA224: Not used widely, Yahoo argues against it - } + private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>(Arrays.asList( + // MD5: broken + // SHA1: broken + // RIPEMD160: same security properties as SHA1 + // DOUBLE_SHA: not used widely + // MD2: not used widely + // TIGER_192: not used widely + // HAVAL_5_160: not used widely + HashAlgorithmTags.SHA256, // compatibility for old Mailvelope versions + HashAlgorithmTags.SHA384, + HashAlgorithmTags.SHA512 + // SHA224: Not used widely, Yahoo argues against it + )); public static boolean isSecureHashAlgorithm(int id) { return sHashAlgorithmsWhitelist.contains(id); @@ -106,12 +105,11 @@ public class PgpSecurityConstants { * bitlength less than 1023 bits. * Implementations MUST NOT accept any RSA keys with bitlength less than 2047 bits after January 1, 2016. */ - private static HashSet<String> sCurveWhitelist = new HashSet<>(); - static { - sCurveWhitelist.add(NISTNamedCurves.getOID("P-256").getId()); - sCurveWhitelist.add(NISTNamedCurves.getOID("P-384").getId()); - sCurveWhitelist.add(NISTNamedCurves.getOID("P-521").getId()); - } + private static HashSet<String> sCurveWhitelist = new HashSet<>(Arrays.asList( + NISTNamedCurves.getOID("P-256").getId(), + NISTNamedCurves.getOID("P-384").getId(), + NISTNamedCurves.getOID("P-521").getId() + )); public static boolean isSecureKey(CanonicalizedPublicKey key) { switch (key.getAlgorithm()) { 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 a7baddf8b..ca98882d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -216,17 +216,6 @@ public class UncachedKeyRing implements Serializable { } - public boolean containsSubkey(String expectedFingerprint) { - Iterator<PGPPublicKey> it = mRing.getPublicKeys(); - while (it.hasNext()) { - if (KeyFormattingUtils.convertFingerprintToHex( - it.next().getFingerprint()).equalsIgnoreCase(expectedFingerprint)) { - return true; - } - } - return false; - } - public interface IteratorWithIOThrow<E> { public boolean hasNext() throws IOException; public E next() throws IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 26f046372..013a6bf14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -368,4 +368,5 @@ public class UncachedPublicKey { return calendar.getTime(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java index 2c7f0187a..535314607 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -1,4 +1,8 @@ /* +<<<<<<< HEAD + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> +======= +>>>>>>> development * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify @@ -37,6 +41,7 @@ public class WrappedUserAttribute implements Serializable { public static final int UAT_NONE = 0; public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE; + public static final int UAT_URI_ATTRIBUTE = 101; private PGPUserAttributeSubpacketVector mVector; @@ -77,7 +82,7 @@ public class WrappedUserAttribute implements Serializable { public static WrappedUserAttribute fromData (byte[] data) throws IOException { UserAttributeSubpacketInputStream in = new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data)); - ArrayList<UserAttributeSubpacket> list = new ArrayList<UserAttributeSubpacket>(); + ArrayList<UserAttributeSubpacket> list = new ArrayList<>(); while (in.available() > 0) { list.add(in.readPacket()); } @@ -121,6 +126,7 @@ public class WrappedUserAttribute implements Serializable { private void readObjectNoData() throws ObjectStreamException { } + @SuppressWarnings("SimplifiableIfStatement") @Override public boolean equals(Object o) { if (!WrappedUserAttribute.class.isInstance(o)) { |