From 7b24ee7b55db99467dd63e631ba55a27d08587d5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 1 Feb 2015 23:14:26 +0100 Subject: rewrite PgpSignEncrypt data flow - introduce high-level SignEncryptOperation for uri to uri signing/encryption - use SignEncryptParcel for high-level operation parameters - use PgpSignEncryptInput plus streams for low-level operation parameters - get rid of all sign/encrypt logic in KeychainIntentService --- .../keychain/pgp/PgpDecryptVerify.java | 8 +- .../sufficientlysecure/keychain/pgp/PgpHelper.java | 2 +- .../keychain/pgp/PgpSignEncrypt.java | 724 --------------------- .../keychain/pgp/PgpSignEncryptInput.java | 176 +++++ .../keychain/pgp/PgpSignEncryptOperation.java | 578 ++++++++++++++++ .../keychain/pgp/SignEncryptParcel.java | 135 ++++ .../keychain/pgp/UncachedKeyRing.java | 2 +- .../keychain/pgp/WrappedUserAttribute.java | 1 - 8 files changed, 895 insertions(+), 731 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index a69c5fe36..ad9b1900e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -39,7 +39,6 @@ import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; @@ -47,12 +46,12 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -616,6 +615,7 @@ public class PgpDecryptVerify extends BaseOperation { int length; byte[] buffer = new byte[1 << 16]; while ((length = dataIn.read(buffer)) > 0) { + Log.d(Constants.TAG, "read bytes: " + length); if (mOutStream != null) { mOutStream.write(buffer, 0, length); } 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 0a15fd98f..12de2f637 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -24,8 +24,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.File; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java deleted file mode 100644 index 060db96b4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ /dev/null @@ -1,724 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * Copyright (C) 2014 Vincent Breitmoser - * - * 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 . - */ - -package org.sufficientlysecure.keychain.pgp; - -import android.content.Context; - -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.BCPGOutputStream; -import org.spongycastle.bcpg.CompressionAlgorithmTags; -import org.spongycastle.openpgp.PGPCompressedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedDataGenerator; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPLiteralDataGenerator; -import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; -import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.BaseOperation; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ProgressScaler; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.SignatureException; -import java.util.Arrays; -import java.util.Date; -import java.util.LinkedList; - -/** - * This class uses a Builder pattern! - */ -public class PgpSignEncrypt extends BaseOperation { - private String mVersionHeader; - private InputData mData; - private OutputStream mOutStream; - - private boolean mEnableAsciiArmorOutput; - private int mCompressionId; - private long[] mEncryptionMasterKeyIds; - private String mSymmetricPassphrase; - private int mSymmetricEncryptionAlgorithm; - private long mSignatureMasterKeyId; - private Long mSignatureSubKeyId; - private int mSignatureHashAlgorithm; - private String mSignaturePassphrase; - private long mAdditionalEncryptId; - private boolean mCleartextSignature; - private boolean mDetachedSignature; - private String mOriginalFilename; - private boolean mFailOnMissingEncryptionKeyIds; - private String mCharset; - - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; - - private static byte[] NEW_LINE; - - static { - try { - NEW_LINE = "\r\n".getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(Constants.TAG, "UnsupportedEncodingException", e); - } - } - - protected PgpSignEncrypt(Builder builder) { - super(builder.mContext, builder.mProviderHelper, builder.mProgressable); - - // private Constructor can only be called from Builder - this.mVersionHeader = builder.mVersionHeader; - this.mData = builder.mData; - this.mOutStream = builder.mOutStream; - - this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput; - this.mCompressionId = builder.mCompressionId; - this.mEncryptionMasterKeyIds = builder.mEncryptionMasterKeyIds; - this.mSymmetricPassphrase = builder.mSymmetricPassphrase; - this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm; - this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; - this.mSignatureSubKeyId = builder.mSignatureSubKeyId; - this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; - this.mSignaturePassphrase = builder.mSignaturePassphrase; - this.mAdditionalEncryptId = builder.mAdditionalEncryptId; - this.mCleartextSignature = builder.mCleartextSignature; - this.mDetachedSignature = builder.mDetachedSignature; - this.mNfcSignedHash = builder.mNfcSignedHash; - this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; - this.mOriginalFilename = builder.mOriginalFilename; - this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds; - this.mCharset = builder.mCharset; - } - - public static class Builder { - // mandatory parameter - private Context mContext; - private ProviderHelper mProviderHelper; - private Progressable mProgressable; - private InputData mData; - private OutputStream mOutStream; - - // optional - private String mVersionHeader = null; - private boolean mEnableAsciiArmorOutput = false; - private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; - private long[] mEncryptionMasterKeyIds = null; - private String mSymmetricPassphrase = null; - private int mSymmetricEncryptionAlgorithm = 0; - private long mSignatureMasterKeyId = Constants.key.none; - private Long mSignatureSubKeyId = null; - private int mSignatureHashAlgorithm = 0; - private String mSignaturePassphrase = null; - private long mAdditionalEncryptId = Constants.key.none; - private boolean mCleartextSignature = false; - private boolean mDetachedSignature = false; - private String mOriginalFilename = ""; - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; - private boolean mFailOnMissingEncryptionKeyIds = false; - private String mCharset = null; - - public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, - InputData data, OutputStream outStream) { - mContext = context; - mProviderHelper = providerHelper; - mProgressable = progressable; - - mData = data; - mOutStream = outStream; - } - - public Builder setVersionHeader(String versionHeader) { - mVersionHeader = versionHeader; - return this; - } - - public Builder setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { - mEnableAsciiArmorOutput = enableAsciiArmorOutput; - return this; - } - - public Builder setCompressionId(int compressionId) { - mCompressionId = compressionId; - return this; - } - - public Builder setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { - mEncryptionMasterKeyIds = encryptionMasterKeyIds; - return this; - } - - public Builder setSymmetricPassphrase(String symmetricPassphrase) { - mSymmetricPassphrase = symmetricPassphrase; - return this; - } - - public Builder setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { - mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; - return this; - } - - public Builder setSignatureMasterKeyId(long signatureMasterKeyId) { - mSignatureMasterKeyId = signatureMasterKeyId; - return this; - } - - public Builder setSignatureSubKeyId(long signatureSubKeyId) { - mSignatureSubKeyId = signatureSubKeyId; - return this; - } - - public Builder setSignatureHashAlgorithm(int signatureHashAlgorithm) { - mSignatureHashAlgorithm = signatureHashAlgorithm; - return this; - } - - public Builder setSignaturePassphrase(String signaturePassphrase) { - mSignaturePassphrase = signaturePassphrase; - return this; - } - - public Builder setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { - mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; - return this; - } - - public Builder setCharset(String charset) { - mCharset = charset; - return this; - } - - /** - * Also encrypt with the signing keyring - * - * @param additionalEncryptId - * @return - */ - public Builder setAdditionalEncryptId(long additionalEncryptId) { - mAdditionalEncryptId = additionalEncryptId; - return this; - } - - public Builder setCleartextSignature(boolean cleartextSignature) { - mCleartextSignature = cleartextSignature; - return this; - } - - public Builder setDetachedSignature(boolean detachedSignature) { - mDetachedSignature = detachedSignature; - return this; - } - - public Builder setOriginalFilename(String originalFilename) { - mOriginalFilename = originalFilename; - return this; - } - - public Builder setNfcState(byte[] signedHash, Date creationTimestamp) { - mNfcSignedHash = signedHash; - mNfcCreationTimestamp = creationTimestamp; - return this; - } - - public PgpSignEncrypt build() { - return new PgpSignEncrypt(this); - } - } - - /** - * Signs and/or encrypts data based on parameters of class - */ - public SignEncryptResult execute() { - - int indent = 0; - OperationLog log = new OperationLog(); - - log.add(LogType.MSG_SE, indent); - indent += 1; - - boolean enableSignature = mSignatureMasterKeyId != Constants.key.none; - boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0) - || mSymmetricPassphrase != null); - boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED); - - Log.d(Constants.TAG, "enableSignature:" + enableSignature - + "\nenableEncryption:" + enableEncryption - + "\nenableCompression:" + enableCompression - + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput); - - // add additional key id to encryption ids (mostly to do self-encryption) - if (enableEncryption && mAdditionalEncryptId != Constants.key.none) { - mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1); - mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId; - } - - ArmoredOutputStream armorOut = null; - OutputStream out; - if (mEnableAsciiArmorOutput) { - armorOut = new ArmoredOutputStream(mOutStream); - if (mVersionHeader != null) { - armorOut.setHeader("Version", mVersionHeader); - } - // if we have a charset, put it in the header - if (mCharset != null) { - armorOut.setHeader("Charset", mCharset); - } - out = armorOut; - } else { - out = mOutStream; - } - - /* Get keys for signature generation for later usage */ - CanonicalizedSecretKey signingKey = null; - long signKeyId; - if (enableSignature) { - - try { - // fetch the indicated master key id (the one whose name we sign in) - CanonicalizedSecretKeyRing signingKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId); - // fetch the specific subkey to sign with, or just use the master key if none specified - signKeyId = mSignatureSubKeyId != null ? mSignatureSubKeyId : mSignatureMasterKeyId; - signingKey = signingKeyRing.getSecretKey(signKeyId); - // make sure it's a signing key alright! - } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_ERROR_SIGN_KEY, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - - // Make sure we are allowed to sign here! - if (!signingKey.canSign()) { - log.add(LogType.MSG_SE_ERROR_KEY_SIGN, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - - // if no passphrase was explicitly set try to get it from the cache service - if (mSignaturePassphrase == null) { - try { - // returns "" if key has no passphrase - mSignaturePassphrase = getCachedPassphrase(signKeyId); - // TODO -// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); - } catch (PassphraseCacheInterface.NoSecretKeyException e) { - // TODO -// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - - // if passphrase was not cached, return here indicating that a passphrase is missing! - if (mSignaturePassphrase == null) { - log.add(LogType.MSG_SE_PENDING_PASSPHRASE, indent + 1); - SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_PENDING_PASSPHRASE, log); - result.setKeyIdPassphraseNeeded(signKeyId); - return result; - } - } - - updateProgress(R.string.progress_extracting_signature_key, 0, 100); - - try { - if (!signingKey.unlock(mSignaturePassphrase)) { - log.add(LogType.MSG_SE_ERROR_BAD_PASSPHRASE, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_UNLOCK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - - // check if hash algo is supported - LinkedList supported = signingKey.getSupportedHashAlgorithms(); - if (!supported.contains(mSignatureHashAlgorithm)) { - // get most preferred - mSignatureHashAlgorithm = supported.getLast(); - } - } - updateProgress(R.string.progress_preparing_streams, 2, 100); - - /* Initialize PGPEncryptedDataGenerator for later usage */ - PGPEncryptedDataGenerator cPk = null; - if (enableEncryption) { - // has Integrity packet enabled! - JcePGPDataEncryptorBuilder encryptorBuilder = - new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) - .setWithIntegrityPacket(true); - - cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - - if (mSymmetricPassphrase != null) { - // Symmetric encryption - log.add(LogType.MSG_SE_SYMMETRIC, indent); - - JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = - new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray()); - cPk.addMethod(symmetricEncryptionGenerator); - } else { - log.add(LogType.MSG_SE_ASYMMETRIC, indent); - - // Asymmetric encryption - for (long id : mEncryptionMasterKeyIds) { - try { - CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(id)); - CanonicalizedPublicKey key = keyRing.getEncryptionSubKey(); - cPk.addMethod(key.getPubKeyEncryptionGenerator()); - log.add(LogType.MSG_SE_KEY_OK, indent + 1, - KeyFormattingUtils.convertKeyIdToHex(id)); - } catch (PgpKeyNotFoundException e) { - log.add(LogType.MSG_SE_KEY_WARN, indent + 1, - KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_KEY_UNKNOWN, indent + 1, - KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - } - } - } - } - - /* Initialize signature generator object for later usage */ - PGPSignatureGenerator signatureGenerator = null; - if (enableSignature) { - updateProgress(R.string.progress_preparing_signature, 4, 100); - - try { - boolean cleartext = mCleartextSignature && mEnableAsciiArmorOutput && !enableEncryption; - signatureGenerator = signingKey.getSignatureGenerator( - mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); - } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_NFC, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - } - - ProgressScaler progressScaler = - new ProgressScaler(mProgressable, 8, 95, 100); - PGPCompressedDataGenerator compressGen = null; - OutputStream pOut; - OutputStream encryptionOut = null; - BCPGOutputStream bcpgOut; - - ByteArrayOutputStream detachedByteOut = null; - ArmoredOutputStream detachedArmorOut = null; - BCPGOutputStream detachedBcpgOut = null; - - try { - - if (enableEncryption) { - /* actual encryption */ - updateProgress(R.string.progress_encrypting, 8, 100); - log.add(enableSignature - ? LogType.MSG_SE_SIGCRYPTING - : LogType.MSG_SE_ENCRYPTING, - indent - ); - indent += 1; - - encryptionOut = cPk.open(out, new byte[1 << 16]); - - if (enableCompression) { - log.add(LogType.MSG_SE_COMPRESSING, indent); - compressGen = new PGPCompressedDataGenerator(mCompressionId); - bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); - } else { - bcpgOut = new BCPGOutputStream(encryptionOut); - } - - if (enableSignature) { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - char literalDataFormatTag; - if (mCleartextSignature) { - literalDataFormatTag = PGPLiteralData.UTF8; - } else { - literalDataFormatTag = PGPLiteralData.BINARY; - } - pOut = literalGen.open(bcpgOut, literalDataFormatTag, mOriginalFilename, new Date(), - new byte[1 << 16]); - - long alreadyWritten = 0; - int length; - byte[] buffer = new byte[1 << 16]; - InputStream in = mData.getInputStream(); - while ((length = in.read(buffer)) > 0) { - pOut.write(buffer, 0, length); - - // update signature buffer if signature is requested - if (enableSignature) { - signatureGenerator.update(buffer, 0, length); - } - - alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); - progressScaler.setProgress((int) progress, 100); - } - } - - literalGen.close(); - indent -= 1; - - } else if (enableSignature && mCleartextSignature && mEnableAsciiArmorOutput) { - /* cleartext signature: sign-only of ascii text */ - - updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_SIGNING, indent); - - // write -----BEGIN PGP SIGNED MESSAGE----- - armorOut.beginClearText(mSignatureHashAlgorithm); - - InputStream in = mData.getInputStream(); - final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - - // update signature buffer with first line - processLine(reader.readLine(), armorOut, signatureGenerator); - - // TODO: progress: fake annealing? - while (true) { - String line = reader.readLine(); - - // end cleartext signature with newline, see http://tools.ietf.org/html/rfc4880#section-7 - if (line == null) { - armorOut.write(NEW_LINE); - break; - } - - armorOut.write(NEW_LINE); - - // update signature buffer with input line - signatureGenerator.update(NEW_LINE); - processLine(line, armorOut, signatureGenerator); - } - - armorOut.endClearText(); - - pOut = new BCPGOutputStream(armorOut); - } else if (enableSignature && mDetachedSignature) { - /* detached signature */ - - updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_SIGNING, indent); - - InputStream in = mData.getInputStream(); - - // handle output stream separately for detached signatures - detachedByteOut = new ByteArrayOutputStream(); - OutputStream detachedOut = detachedByteOut; - if (mEnableAsciiArmorOutput) { - detachedArmorOut = new ArmoredOutputStream(detachedOut); - if (mVersionHeader != null) { - detachedArmorOut.setHeader("Version", mVersionHeader); - } - - detachedOut = detachedArmorOut; - } - detachedBcpgOut = new BCPGOutputStream(detachedOut); - - long alreadyWritten = 0; - int length; - byte[] buffer = new byte[1 << 16]; - while ((length = in.read(buffer)) > 0) { - // no output stream is written, no changed to original data! - - signatureGenerator.update(buffer, 0, length); - - alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); - progressScaler.setProgress((int) progress, 100); - } - } - - pOut = null; - } else if (enableSignature && !mCleartextSignature && !mDetachedSignature) { - /* sign-only binary (files/data stream) */ - - updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_SIGNING, indent); - - InputStream in = mData.getInputStream(); - - if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(mCompressionId); - bcpgOut = new BCPGOutputStream(compressGen.open(out)); - } else { - bcpgOut = new BCPGOutputStream(out); - } - - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(), - new byte[1 << 16]); - - long alreadyWritten = 0; - int length; - byte[] buffer = new byte[1 << 16]; - while ((length = in.read(buffer)) > 0) { - pOut.write(buffer, 0, length); - - signatureGenerator.update(buffer, 0, length); - - alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); - progressScaler.setProgress((int) progress, 100); - } - } - - literalGen.close(); - } else { - pOut = null; - // TODO: Is this log right? - log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent); - } - - if (enableSignature) { - updateProgress(R.string.progress_generating_signature, 95, 100); - try { - if (detachedBcpgOut != null) { - signatureGenerator.generate().encode(detachedBcpgOut); - } else { - signatureGenerator.generate().encode(pOut); - } - } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { - // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed - log.add(LogType.MSG_SE_PENDING_NFC, indent); - SignEncryptResult result = - new SignEncryptResult(SignEncryptResult.RESULT_PENDING_NFC, log); - // Note that the checked key here is the master key, not the signing key - // (although these are always the same on Yubikeys) - result.setNfcData(mSignatureSubKeyId, e.hashToSign, e.hashAlgo, e.creationTimestamp, mSignaturePassphrase); - Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign)); - return result; - } - } - - // closing outputs - // NOTE: closing needs to be done in the correct order! - if (encryptionOut != null) { - if (compressGen != null) { - compressGen.close(); - } - - encryptionOut.close(); - } - // Note: Closing ArmoredOutputStream does not close the underlying stream - if (armorOut != null) { - armorOut.close(); - } - // Note: Closing ArmoredOutputStream does not close the underlying stream - if (detachedArmorOut != null) { - detachedArmorOut.close(); - } - // Also closes detachedBcpgOut - if (detachedByteOut != null) { - detachedByteOut.close(); - } - if (out != null) { - out.close(); - } - if (mOutStream != null) { - mOutStream.close(); - } - - } catch (SignatureException e) { - log.add(LogType.MSG_SE_ERROR_SIG, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } catch (PGPException e) { - log.add(LogType.MSG_SE_ERROR_PGP, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } catch (IOException e) { - log.add(LogType.MSG_SE_ERROR_IO, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); - } - - updateProgress(R.string.progress_done, 100, 100); - - log.add(LogType.MSG_SE_OK, indent); - SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_OK, log); - if (detachedByteOut != null) { - try { - detachedByteOut.flush(); - detachedByteOut.close(); - } catch (IOException e) { - // silently catch - } - result.setDetachedSignature(detachedByteOut.toByteArray()); - } - return result; - } - - /** - * Remove whitespaces on line endings - */ - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPSignatureGenerator pSignatureGenerator) - throws IOException, SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java new file mode 100644 index 000000000..9318be006 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java @@ -0,0 +1,176 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.Constants; + +import java.util.Date; + +public class PgpSignEncryptInput { + + protected String mVersionHeader = null; + protected boolean mEnableAsciiArmorOutput = false; + protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; + protected long[] mEncryptionMasterKeyIds = null; + protected String mSymmetricPassphrase = null; + protected int mSymmetricEncryptionAlgorithm = 0; + protected long mSignatureMasterKeyId = Constants.key.none; + protected Long mSignatureSubKeyId = null; + protected int mSignatureHashAlgorithm = 0; + protected String mSignaturePassphrase = null; + protected long mAdditionalEncryptId = Constants.key.none; + protected byte[] mNfcSignedHash = null; + protected Date mNfcCreationTimestamp = null; + protected boolean mFailOnMissingEncryptionKeyIds = false; + protected String mCharset; + protected boolean mCleartextSignature; + protected boolean mDetachedSignature; + + public String getCharset() { + return mCharset; + } + + public void setCharset(String mCharset) { + this.mCharset = mCharset; + } + + public boolean ismFailOnMissingEncryptionKeyIds() { + return mFailOnMissingEncryptionKeyIds; + } + + public Date getNfcCreationTimestamp() { + return mNfcCreationTimestamp; + } + + public byte[] getNfcSignedHash() { + return mNfcSignedHash; + } + + public long getAdditionalEncryptId() { + return mAdditionalEncryptId; + } + + public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) { + mAdditionalEncryptId = additionalEncryptId; + return this; + } + + public String getSignaturePassphrase() { + return mSignaturePassphrase; + } + + public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) { + mSignaturePassphrase = signaturePassphrase; + return this; + } + + public int getSignatureHashAlgorithm() { + return mSignatureHashAlgorithm; + } + + public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { + mSignatureHashAlgorithm = signatureHashAlgorithm; + return this; + } + + public Long getSignatureSubKeyId() { + return mSignatureSubKeyId; + } + + public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { + mSignatureSubKeyId = signatureSubKeyId; + return this; + } + + public long getSignatureMasterKeyId() { + return mSignatureMasterKeyId; + } + + public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { + mSignatureMasterKeyId = signatureMasterKeyId; + return this; + } + + public int getSymmetricEncryptionAlgorithm() { + return mSymmetricEncryptionAlgorithm; + } + + public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; + return this; + } + + public String getSymmetricPassphrase() { + return mSymmetricPassphrase; + } + + public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) { + mSymmetricPassphrase = symmetricPassphrase; + return this; + } + + public long[] getEncryptionMasterKeyIds() { + return mEncryptionMasterKeyIds; + } + + public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { + mEncryptionMasterKeyIds = encryptionMasterKeyIds; + return this; + } + + public int getCompressionId() { + return mCompressionId; + } + + public PgpSignEncryptInput setCompressionId(int compressionId) { + mCompressionId = compressionId; + return this; + } + + public boolean ismEnableAsciiArmorOutput() { + return mEnableAsciiArmorOutput; + } + + public String getVersionHeader() { + return mVersionHeader; + } + + public PgpSignEncryptInput setVersionHeader(String versionHeader) { + mVersionHeader = versionHeader; + return this; + } + + public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + mEnableAsciiArmorOutput = enableAsciiArmorOutput; + return this; + } + + public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { + mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; + return this; + } + + public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) { + mNfcSignedHash = signedHash; + mNfcCreationTimestamp = creationTimestamp; + return this; + } + + public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) { + this.mCleartextSignature = cleartextSignature; + return this; + } + + public boolean isCleartextSignature() { + return mCleartextSignature; + } + + public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { + this.mDetachedSignature = detachedSignature; + return this; + } + + public boolean isDetachedSignature() { + return mDetachedSignature; + } +} + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java new file mode 100644 index 000000000..2fa01d241 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010-2014 Thialfihar + * Copyright (C) 2014 Vincent Breitmoser + * + * 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 . + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.content.Context; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; + +/** This class supports a single, low-level, sign/encrypt operation. + * + * The operation of this class takes an Input- and OutputStream plus a + * PgpSignEncryptInput, and signs and/or encrypts the stream as + * parametrized in the PgpSignEncryptInput object. It returns its status + * and a possible detached signature as a SignEncryptResult. + * + * For a high-level operation based on URIs, see SignEncryptOperation. + * + * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput + * @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult + * @see org.sufficientlysecure.keychain.operations.SignEncryptOperation + * + */ +public class PgpSignEncryptOperation extends BaseOperation { + + private static byte[] NEW_LINE; + + static { + try { + NEW_LINE = "\r\n".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.e(Constants.TAG, "UnsupportedEncodingException", e); + } + } + + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); + } + + /** + * Signs and/or encrypts data based on parameters of class + */ + public PgpSignEncryptResult execute(PgpSignEncryptInput input, + InputData inputData, OutputStream outputStream) { + + int indent = 0; + OperationLog log = new OperationLog(); + + log.add(LogType.MSG_PSE, indent); + indent += 1; + + boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none; + boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0) + || input.getSymmetricPassphrase() != null); + boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED); + + Log.d(Constants.TAG, "enableSignature:" + enableSignature + + "\nenableEncryption:" + enableEncryption + + "\nenableCompression:" + enableCompression + + "\nenableAsciiArmorOutput:" + input.ismEnableAsciiArmorOutput()); + + // add additional key id to encryption ids (mostly to do self-encryption) + if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) { + input.setEncryptionMasterKeyIds(Arrays.copyOf(input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1)); + input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] = input.getAdditionalEncryptId(); + } + + ArmoredOutputStream armorOut = null; + OutputStream out; + if (input.ismEnableAsciiArmorOutput()) { + armorOut = new ArmoredOutputStream(outputStream); + if (input.getVersionHeader() != null) { + armorOut.setHeader("Version", input.getVersionHeader()); + } + // if we have a charset, put it in the header + if (input.getCharset() != null) { + armorOut.setHeader("Charset", input.getCharset()); + } + out = armorOut; + } else { + out = outputStream; + } + + /* Get keys for signature generation for later usage */ + CanonicalizedSecretKey signingKey = null; + if (enableSignature) { + + try { + // fetch the indicated master key id (the one whose name we sign in) + CanonicalizedSecretKeyRing signingKeyRing = + mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId()); + + long signKeyId; + // use specified signing subkey, or find the one to use + if (input.getSignatureSubKeyId() == null) { + signKeyId = signingKeyRing.getSecretSignId(); + } else { + signKeyId = input.getSignatureSubKeyId(); + } + + // fetch the specific subkey to sign with, or just use the master key if none specified + signingKey = signingKeyRing.getSecretKey(signKeyId); + + } catch (ProviderHelper.NotFoundException | PgpGeneralException e) { + log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + + // Make sure we are allowed to sign here! + if (!signingKey.canSign()) { + log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + + // if no passphrase was explicitly set try to get it from the cache service + if (input.getSignaturePassphrase() == null) { + try { + // returns "" if key has no passphrase + input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId())); + // TODO +// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); + } catch (PassphraseCacheInterface.NoSecretKeyException e) { + // TODO +// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + + // if passphrase was not cached, return here indicating that a passphrase is missing! + if (input.getSignaturePassphrase() == null) { + log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log); + result.setKeyIdPassphraseNeeded(signingKey.getKeyId()); + return result; + } + } + + updateProgress(R.string.progress_extracting_signature_key, 0, 100); + + try { + if (!signingKey.unlock(input.getSignaturePassphrase())) { + log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + + // check if hash algo is supported + int requestedAlgorithm = input.getSignatureHashAlgorithm(); + LinkedList supported = signingKey.getSupportedHashAlgorithms(); + if (requestedAlgorithm == 0) { + // get most preferred + input.setSignatureHashAlgorithm(supported.getLast()); + } else if (!supported.contains(requestedAlgorithm)) { + log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } + updateProgress(R.string.progress_preparing_streams, 2, 100); + + /* Initialize PGPEncryptedDataGenerator for later usage */ + PGPEncryptedDataGenerator cPk = null; + if (enableEncryption) { + int algo = input.getSymmetricEncryptionAlgorithm(); + if (algo == 0) { + algo = PGPEncryptedData.AES_128; + } + // has Integrity packet enabled! + JcePGPDataEncryptorBuilder encryptorBuilder = + new JcePGPDataEncryptorBuilder(algo) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) + .setWithIntegrityPacket(true); + + cPk = new PGPEncryptedDataGenerator(encryptorBuilder); + + if (input.getSymmetricPassphrase() != null) { + // Symmetric encryption + log.add(LogType.MSG_PSE_SYMMETRIC, indent); + + JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = + new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().toCharArray()); + cPk.addMethod(symmetricEncryptionGenerator); + } else { + log.add(LogType.MSG_PSE_ASYMMETRIC, indent); + + // Asymmetric encryption + for (long id : input.getEncryptionMasterKeyIds()) { + try { + CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingUri(id)); + CanonicalizedPublicKey key = keyRing.getEncryptionSubKey(); + cPk.addMethod(key.getPubKeyEncryptionGenerator()); + log.add(LogType.MSG_PSE_KEY_OK, indent + 1, + KeyFormattingUtils.convertKeyIdToHex(id)); + } catch (PgpKeyNotFoundException e) { + log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, + KeyFormattingUtils.convertKeyIdToHex(id)); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } catch (ProviderHelper.NotFoundException e) { + log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, + KeyFormattingUtils.convertKeyIdToHex(id)); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } + } + } + } + + /* Initialize signature generator object for later usage */ + PGPSignatureGenerator signatureGenerator = null; + if (enableSignature) { + updateProgress(R.string.progress_preparing_signature, 4, 100); + + try { + boolean cleartext = input.isCleartextSignature() && input.ismEnableAsciiArmorOutput() && !enableEncryption; + signatureGenerator = signingKey.getSignatureGenerator( + input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); + } catch (PgpGeneralException e) { + log.add(LogType.MSG_PSE_ERROR_NFC, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } + + ProgressScaler progressScaler = + new ProgressScaler(mProgressable, 8, 95, 100); + PGPCompressedDataGenerator compressGen = null; + OutputStream pOut; + OutputStream encryptionOut = null; + BCPGOutputStream bcpgOut; + + ByteArrayOutputStream detachedByteOut = null; + ArmoredOutputStream detachedArmorOut = null; + BCPGOutputStream detachedBcpgOut = null; + + try { + + if (enableEncryption) { + /* actual encryption */ + updateProgress(R.string.progress_encrypting, 8, 100); + log.add(enableSignature + ? LogType.MSG_PSE_SIGCRYPTING + : LogType.MSG_PSE_ENCRYPTING, + indent + ); + indent += 1; + + encryptionOut = cPk.open(out, new byte[1 << 16]); + + if (enableCompression) { + log.add(LogType.MSG_PSE_COMPRESSING, indent); + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); + bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); + } else { + bcpgOut = new BCPGOutputStream(encryptionOut); + } + + if (enableSignature) { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + char literalDataFormatTag; + if (input.isCleartextSignature()) { + literalDataFormatTag = PGPLiteralData.UTF8; + } else { + literalDataFormatTag = PGPLiteralData.BINARY; + } + pOut = literalGen.open(bcpgOut, literalDataFormatTag, + inputData.getOriginalFilename(), new Date(), new byte[1 << 16]); + + long alreadyWritten = 0; + int length; + byte[] buffer = new byte[1 << 16]; + InputStream in = inputData.getInputStream(); + while ((length = in.read(buffer)) > 0) { + pOut.write(buffer, 0, length); + + // update signature buffer if signature is requested + if (enableSignature) { + signatureGenerator.update(buffer, 0, length); + } + + alreadyWritten += length; + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + literalGen.close(); + indent -= 1; + + } else if (enableSignature && input.isCleartextSignature() && input.ismEnableAsciiArmorOutput()) { + /* cleartext signature: sign-only of ascii text */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent); + + // write -----BEGIN PGP SIGNED MESSAGE----- + armorOut.beginClearText(input.getSignatureHashAlgorithm()); + + InputStream in = inputData.getInputStream(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // update signature buffer with first line + processLine(reader.readLine(), armorOut, signatureGenerator); + + // TODO: progress: fake annealing? + while (true) { + String line = reader.readLine(); + + // end cleartext signature with newline, see http://tools.ietf.org/html/rfc4880#section-7 + if (line == null) { + armorOut.write(NEW_LINE); + break; + } + + armorOut.write(NEW_LINE); + + // update signature buffer with input line + signatureGenerator.update(NEW_LINE); + processLine(line, armorOut, signatureGenerator); + } + + armorOut.endClearText(); + + pOut = new BCPGOutputStream(armorOut); + } else if (enableSignature && input.isDetachedSignature()) { + /* detached signature */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent); + + InputStream in = inputData.getInputStream(); + + // handle output stream separately for detached signatures + detachedByteOut = new ByteArrayOutputStream(); + OutputStream detachedOut = detachedByteOut; + if (input.ismEnableAsciiArmorOutput()) { + detachedArmorOut = new ArmoredOutputStream(detachedOut); + if (input.getVersionHeader() != null) { + detachedArmorOut.setHeader("Version", input.getVersionHeader()); + } + + detachedOut = detachedArmorOut; + } + detachedBcpgOut = new BCPGOutputStream(detachedOut); + + long alreadyWritten = 0; + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + // no output stream is written, no changed to original data! + + signatureGenerator.update(buffer, 0, length); + + alreadyWritten += length; + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + pOut = null; + } else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) { + /* sign-only binary (files/data stream) */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_PSE_SIGNING, indent); + + InputStream in = inputData.getInputStream(); + + if (enableCompression) { + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); + bcpgOut = new BCPGOutputStream(compressGen.open(out)); + } else { + bcpgOut = new BCPGOutputStream(out); + } + + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, + inputData.getOriginalFilename(), new Date(), + new byte[1 << 16]); + + long alreadyWritten = 0; + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + pOut.write(buffer, 0, length); + + signatureGenerator.update(buffer, 0, length); + + alreadyWritten += length; + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + literalGen.close(); + } else { + pOut = null; + // TODO: Is this log right? + log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent); + } + + if (enableSignature) { + updateProgress(R.string.progress_generating_signature, 95, 100); + try { + if (detachedBcpgOut != null) { + signatureGenerator.generate().encode(detachedBcpgOut); + } else { + signatureGenerator.generate().encode(pOut); + } + } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { + // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed + log.add(LogType.MSG_PSE_PENDING_NFC, indent); + PgpSignEncryptResult result = + new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log); + // Note that the checked key here is the master key, not the signing key + // (although these are always the same on Yubikeys) + result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase()); + Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign)); + return result; + } + } + + // closing outputs + // NOTE: closing needs to be done in the correct order! + if (encryptionOut != null) { + if (compressGen != null) { + compressGen.close(); + } + + encryptionOut.close(); + } + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (armorOut != null) { + armorOut.close(); + } + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (detachedArmorOut != null) { + detachedArmorOut.close(); + } + // Also closes detachedBcpgOut + if (detachedByteOut != null) { + detachedByteOut.close(); + } + if (out != null) { + out.close(); + } + if (outputStream != null) { + outputStream.close(); + } + + } catch (SignatureException e) { + log.add(LogType.MSG_PSE_ERROR_SIG, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } catch (PGPException e) { + log.add(LogType.MSG_PSE_ERROR_PGP, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } catch (IOException e) { + log.add(LogType.MSG_PSE_ERROR_IO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_PSE_OK, indent); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log); + if (detachedByteOut != null) { + try { + detachedByteOut.flush(); + detachedByteOut.close(); + } catch (IOException e) { + // silently catch + } + result.setDetachedSignature(detachedByteOut.toByteArray()); + } + return result; + } + + /** + * Remove whitespaces on line endings + */ + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPSignatureGenerator pSignatureGenerator) + throws IOException, SignatureException { + + if (pLine == null) { + return; + } + + final char[] chars = pLine.toCharArray(); + int len = chars.length; + + while (len > 0) { + if (!Character.isWhitespace(chars[len - 1])) { + break; + } + len--; + } + + final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); + + if (pArmoredOutput != null) { + pArmoredOutput.write(data); + } + pSignatureGenerator.update(data); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java new file mode 100644 index 000000000..a4ed33397 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java @@ -0,0 +1,135 @@ +package org.sufficientlysecure.keychain.pgp; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** This parcel stores the input of one or more PgpSignEncrypt operations. + * All operations will use the same general paramters, differing only in + * input and output. Each input/output set depends on the paramters: + * + * - Each input uri is individually encrypted/signed + * - If a byte array is supplied, it is treated as an input before uris are processed + * - The number of output uris must match the number of input uris, plus one more + * if there is a byte array present. + * - Once the output uris are empty, there must be exactly one input (uri xor bytes) + * left, which will be returned in a byte array as part of the result parcel. + * + */ +public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable { + + public ArrayList mInputUris = new ArrayList<>(); + public ArrayList mOutputUris = new ArrayList<>(); + public byte[] mBytes; + + public SignEncryptParcel() { + super(); + } + + public SignEncryptParcel(Parcel src) { + + // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable + mVersionHeader = src.readString(); + mEnableAsciiArmorOutput = src.readInt() == 1; + mCompressionId = src.readInt(); + mEncryptionMasterKeyIds = src.createLongArray(); + mSymmetricPassphrase = src.readString(); + mSymmetricEncryptionAlgorithm = src.readInt(); + mSignatureMasterKeyId = src.readLong(); + mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null; + mSignatureHashAlgorithm = src.readInt(); + mSignaturePassphrase = src.readString(); + mAdditionalEncryptId = src.readLong(); + mNfcSignedHash = src.createByteArray(); + mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null; + mFailOnMissingEncryptionKeyIds = src.readInt() == 1; + mCharset = src.readString(); + mCleartextSignature = src.readInt() == 1; + mDetachedSignature = src.readInt() == 1; + + mInputUris = src.createTypedArrayList(Uri.CREATOR); + mOutputUris = src.createTypedArrayList(Uri.CREATOR); + mBytes = src.createByteArray(); + + } + + public byte[] getBytes() { + return mBytes; + } + + public void setBytes(byte[] bytes) { + mBytes = bytes; + } + + public List getInputUris() { + return Collections.unmodifiableList(mInputUris); + } + + public void addInputUris(Collection inputUris) { + mInputUris.addAll(inputUris); + } + + public List getOutputUris() { + return Collections.unmodifiableList(mOutputUris); + } + + public void addOutputUris(ArrayList outputUris) { + mOutputUris.addAll(outputUris); + } + + @Override + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mVersionHeader); + dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); + dest.writeInt(mCompressionId); + dest.writeLongArray(mEncryptionMasterKeyIds); + dest.writeString(mSymmetricPassphrase); + dest.writeInt(mSymmetricEncryptionAlgorithm); + dest.writeLong(mSignatureMasterKeyId); + if (mSignatureSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSignatureSubKeyId); + } else { + dest.writeInt(0); + } + dest.writeInt(mSignatureHashAlgorithm); + dest.writeString(mSignaturePassphrase); + dest.writeLong(mAdditionalEncryptId); + dest.writeByteArray(mNfcSignedHash); + if (mNfcCreationTimestamp != null) { + dest.writeInt(1); + dest.writeLong(mNfcCreationTimestamp.getTime()); + } else { + dest.writeInt(0); + } + dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); + dest.writeString(mCharset); + dest.writeInt(mCleartextSignature ? 1 : 0); + dest.writeInt(mDetachedSignature ? 1 : 0); + + dest.writeTypedList(mInputUris); + dest.writeTypedList(mOutputUris); + dest.writeByteArray(mBytes); + } + + public static final Creator CREATOR = new Creator() { + public SignEncryptParcel createFromParcel(final Parcel source) { + return new SignEncryptParcel(source); + } + + public SignEncryptParcel[] newArray(final int size) { + return new SignEncryptParcel[size]; + } + }; + +} 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 af85bd878..681aff56d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -35,9 +35,9 @@ import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; 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 da6d8b287..8e23d36d9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; public class WrappedUserAttribute implements Serializable { -- cgit v1.2.3