From b78954fc16e79d6910ef9b6c781faf755e89a158 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 10 Sep 2015 21:44:15 +0200 Subject: add support for signed-only data in the backend (#1507) --- .../operations/results/OperationResult.java | 1 + .../keychain/pgp/PgpDecryptVerifyOperation.java | 685 +++++++++++---------- 2 files changed, 369 insertions(+), 317 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain') 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 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 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 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; } -- cgit v1.2.3