diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2015-09-10 21:44:15 +0200 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2015-09-10 21:44:15 +0200 |
commit | b78954fc16e79d6910ef9b6c781faf755e89a158 (patch) | |
tree | f6e849487cfdcaf9d46f0f449c5940a34aecb754 | |
parent | cb067f748b0e07e7e96e5e04fc3d8e1ff74ceb45 (diff) | |
download | open-keychain-b78954fc16e79d6910ef9b6c781faf755e89a158.tar.gz open-keychain-b78954fc16e79d6910ef9b6c781faf755e89a158.tar.bz2 open-keychain-b78954fc16e79d6910ef9b6c781faf755e89a158.zip |
add support for signed-only data in the backend (#1507)
3 files changed, 370 insertions, 317 deletions
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 f213b1aad..46852d783 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 @@ -631,6 +631,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_TRAIL_SYM (LogLevel.DEBUG, R.string.msg_dc_trail_sym), MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown), MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking), + MSG_DC_INSECURE_ENCRYPTION_KEY (LogLevel.WARN, R.string.msg_dc_insecure_encryption_key), MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO(LogLevel.WARN, R.string.msg_dc_insecure_symmetric_encryption_algo), MSG_DC_INSECURE_HASH_ALGO(LogLevel.ERROR, R.string.msg_dc_insecure_hash_algo), MSG_DC_INSECURE_MDC_MISSING(LogLevel.ERROR, R.string.msg_dc_insecure_mdc_missing), 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 dd30156f9..a538c9bd1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -313,6 +313,27 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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,328 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp } } + 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, there is nothing left to do here + if (esResult.errorResult != null) { + return esResult.errorResult; + } + + 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(); + 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 && 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 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; + + } + + 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 +733,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 +764,23 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 +817,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 +846,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp } } - 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 +858,16 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 +876,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp 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 +895,41 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp && !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); + return result.with(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(); - } - - 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/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 52632c58d..e6d607591 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1187,6 +1187,7 @@ <string name="msg_dc_trail_sym">"Encountered trailing, symmetrically encrypted data"</string> <string name="msg_dc_trail_unknown">"Encountered trailing data of unknown type"</string> <string name="msg_dc_unlocking">"Unlocking secret key"</string> + <string name="msg_dc_insecure_encryption_key">"Insecure encryption key was used! This can happen because the key is old, or from an attack."</string> <string name="msg_dc_insecure_symmetric_encryption_algo">"Insecure encryption algorithm has been used! This can happen because the application is out of date, or from an attack."</string> <string name="msg_dc_insecure_hash_algo">"Insecure hash algorithm has been used! This can happen because the application is out of date, or from an attack."</string> <string name="msg_dc_insecure_mdc_missing">"Missing the Modification Detection Code (MDC) packet! This can happen because the encrypting application is out of date, or from a downgrade attack."</string> |