From 1710f4416f8363a43fb7174c54b61c6c18248eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 19 Feb 2014 00:18:52 +0100 Subject: Use builder pattern for sign and encrypt --- .../keychain/pgp/PgpOperation.java | 1105 -------------------- .../keychain/pgp/PgpOperationIncoming.java | 683 ++++++++++++ .../keychain/pgp/PgpOperationOutgoing.java | 584 +++++++++++ 3 files changed, 1267 insertions(+), 1105 deletions(-) delete mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationIncoming.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationOutgoing.java (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp') diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java deleted file mode 100644 index 50db814e3..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java +++ /dev/null @@ -1,1105 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.pgp; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.util.Date; -import java.util.Iterator; - -import org.spongycastle.bcpg.ArmoredInputStream; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.BCPGInputStream; -import org.spongycastle.bcpg.BCPGOutputStream; - -import org.spongycastle.bcpg.SignaturePacket; - -import org.spongycastle.bcpg.SignatureSubpacket; -import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.openpgp.PGPCompressedData; -import org.spongycastle.openpgp.PGPCompressedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedData; -import org.spongycastle.openpgp.PGPEncryptedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedDataList; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPLiteralDataGenerator; -import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPOnePassSignature; -import org.spongycastle.openpgp.PGPOnePassSignatureList; -import org.spongycastle.openpgp.PGPPBEEncryptedData; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.PGPSignatureList; -import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.PGPUtil; -import org.spongycastle.openpgp.PGPV3SignatureGenerator; -import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; - -import android.content.Context; -import android.os.Bundle; - -public class PgpOperation { - private Context mContext; - private ProgressDialogUpdater mProgress; - private InputData mData; - private OutputStream mOutStream; - - public PgpOperation(Context context, ProgressDialogUpdater progress, InputData data, - OutputStream outStream) { - super(); - this.mContext = context; - this.mProgress = progress; - this.mData = data; - this.mOutStream = outStream; - } - - public void updateProgress(int message, int current, int total) { - if (mProgress != null) { - mProgress.setProgress(message, current, total); - } - } - - public void updateProgress(int current, int total) { - if (mProgress != null) { - mProgress.setProgress(current, total); - } - } - - public void signAndEncrypt(boolean enableAsciiArmorOutput, int compressionId, long[] encryptionKeyIds, - String encryptionPassphrase, int symmetricEncryptionAlgorithm, - long signatureKeyId, int signatureHashAlgorithm, - boolean signatureForceV3, String signaturePassphrase) - throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, - NoSuchAlgorithmException, SignatureException { - - if (encryptionKeyIds == null) { - encryptionKeyIds = new long[0]; - } - - boolean enableSignature = signatureKeyId != Id.key.none; - boolean enableCompression = compressionId == Id.choice.compression.none; - boolean enableEncryption = encryptionKeyIds.length != 0 || encryptionPassphrase != null; - - int signatureType; - // TODO: disable when encrypting??? - if (enableAsciiArmorOutput && enableSignature && !enableEncryption) { - signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; - } else { - signatureType = PGPSignature.BINARY_DOCUMENT; - } - - ArmoredOutputStream armorOut = null; - OutputStream out; - OutputStream encryptionOut = null; - if (enableAsciiArmorOutput) { - armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext)); - out = armorOut; - } else { - out = mOutStream; - } - - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - if (enableSignature) { - signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId); - signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId); - if (signingKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - if (signaturePassphrase == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_signature_passphrase)); - } - - updateProgress(R.string.progress_extracting_signature_key, 0, 100); - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); - signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - } - updateProgress(R.string.progress_preparing_streams, 5, 100); - - // encrypt and compress input file content - if (enableEncryption) { - // has Integrity packet enabled! - JcePGPDataEncryptorBuilder encryptorBuilder = - new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) - .setWithIntegrityPacket(true); - - PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - - if (encryptionKeyIds.length == 0) { - // Symmetric encryption - Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); - - JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = - new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray()); - cPk.addMethod(symmetricEncryptionGenerator); - } else { - // Asymmetric encryption - for (long id : encryptionKeyIds) { - PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id); - if (key != null) { - - JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = - new JcePublicKeyKeyEncryptionMethodGenerator(key); - cPk.addMethod(pubKeyEncryptionGenerator); - } - } - } - encryptionOut = cPk.open(out, new byte[1 << 16]); - } - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - if (enableSignature) { - updateProgress(R.string.progress_preparing_signature, 10, 100); - - // content signer based on signing key algorithm and chosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( - signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - if (signatureForceV3) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(signatureType, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(signatureType, signaturePrivateKey); - - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - } - - PGPCompressedDataGenerator compressGen = null; - OutputStream pOut; - if (enableEncryption) { - BCPGOutputStream bcpgOut; - if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(compressionId); - bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); - } else { - bcpgOut = new BCPGOutputStream(encryptionOut); - } - - if (enableSignature) { - if (signatureForceV3) { - signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); - } else { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } - } - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), - new byte[1 << 16]); - updateProgress(R.string.progress_encrypting, 20, 100); - - long progress = 0; - int n; - byte[] buffer = new byte[1 << 16]; - InputStream in = mData.getInputStream(); - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); - - // update signature buffer if signature is requested - if (enableSignature) { - if (signatureForceV3) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - - progress += n; - if (mData.getSize() != 0) { - updateProgress((int) (20 + (95 - 20) * progress / mData.getSize()), 100); - } - } - - literalGen.close(); - } else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { - /* sign-only of ascii text */ - - updateProgress(R.string.progress_signing, 40, 100); - - // write directly on armor output stream - armorOut.beginClearText(signatureHashAlgorithm); - - InputStream in = mData.getInputStream(); - final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - - final byte[] newline = "\r\n".getBytes("UTF-8"); - - if (signatureForceV3) { - processLine(reader.readLine(), armorOut, signatureV3Generator); - } else { - processLine(reader.readLine(), armorOut, signatureGenerator); - } - - while (true) { - String line = reader.readLine(); - - if (line == null) { - armorOut.write(newline); - break; - } - - armorOut.write(newline); - if (signatureForceV3) { - signatureV3Generator.update(newline); - processLine(line, armorOut, signatureV3Generator); - } else { - signatureGenerator.update(newline); - processLine(line, armorOut, signatureGenerator); - } - } - - armorOut.endClearText(); - - pOut = new BCPGOutputStream(armorOut); - } else { - // TODO: implement sign-only for files! - pOut = null; - Log.e(Constants.TAG, "not supported!"); - } - - if (enableSignature) { - updateProgress(R.string.progress_generating_signature, 95, 100); - if (signatureForceV3) { - signatureV3Generator.generate().encode(pOut); - } else { - signatureGenerator.generate().encode(pOut); - } - } - - // closing outputs... - if (enableEncryption) { - encryptionOut.close(); - - if (enableCompression) { - compressGen.close(); - } - } - if (enableAsciiArmorOutput) { - armorOut.close(); - } - - updateProgress(R.string.progress_done, 100, 100); - } - - public void signText(long signatureKeyId, String signaturePassphrase, - int signatureHashAlgorithm, boolean forceV3Signature) - throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, - SignatureException { - - try { - signAndEncrypt(true, 0, null, null, 0, signatureKeyId, signatureHashAlgorithm, forceV3Signature, signaturePassphrase); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - } - } - - // TODO: merge this into signAndEncrypt method! - public void generateSignature(boolean armored, boolean binary, long signatureKeyId, - String signaturePassPhrase, int hashAlgorithm, boolean forceV3Signature) - throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, - SignatureException { - - OutputStream out; - if (armored) { - // Ascii Armor (Radix-64) - ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext)); - out = armorOut; - } else { - out = mOutStream; - } - - if (signatureKeyId == 0) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key)); - } - - PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId); - PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId); - if (signingKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - if (signaturePassPhrase == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase)); - } - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.toCharArray()); - PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - updateProgress(R.string.progress_preparing_streams, 0, 100); - - updateProgress(R.string.progress_preparing_signature, 30, 100); - - int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; - if (binary) { - type = PGPSignature.BINARY_DOCUMENT; - } - - // content signer based on signing key algorithm and chosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey - .getPublicKey().getAlgorithm(), hashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(type, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(type, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - updateProgress(R.string.progress_signing, 40, 100); - - InputStream inStream = mData.getInputStream(); - if (binary) { - byte[] buffer = new byte[1 << 16]; - int n = 0; - while ((n = inStream.read(buffer)) > 0) { - if (forceV3Signature) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - } else { - final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); - final byte[] newline = "\r\n".getBytes("UTF-8"); - - String line; - while ((line = reader.readLine()) != null) { - if (forceV3Signature) { - processLine(line, null, signatureV3Generator); - signatureV3Generator.update(newline); - } else { - processLine(line, null, signatureGenerator); - signatureGenerator.update(newline); - } - } - } - - BCPGOutputStream bOut = new BCPGOutputStream(out); - if (forceV3Signature) { - signatureV3Generator.generate().encode(bOut); - } else { - signatureGenerator.generate().encode(bOut); - } - out.close(); - mOutStream.close(); - - updateProgress(R.string.progress_done, 100, 100); - } - - public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) - throws PgpGeneralException, IOException { - InputStream in = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); - } - - Iterator it = enc.getEncryptedDataObjects(); - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - return true; - } - } - - return false; - } - - public Bundle decryptAndVerify(String passphrase, boolean assumeSymmetric) throws IOException, - PgpGeneralException, PGPException, SignatureException { - if (passphrase == null) { - passphrase = ""; - } - - Bundle returnData = new Bundle(); - // automatically works with ascii armor input and binary - InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(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) { - throw new PgpGeneralException(mContext.getString(R.string.error_invalid_data)); - } - - InputStream clear = null; - PGPEncryptedData encryptedData = null; - - currentProgress += 5; - - // TODO: currently we always only look at the first known key or symmetric encryption, - // there might be more... - if (assumeSymmetric) { - PGPPBEEncryptedData pbe = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - pbe = (PGPPBEEncryptedData) obj; - break; - } - } - - if (pbe == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_symmetric_encryption_packet)); - } - - updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - - PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); - PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( - digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.toCharArray()); - - clear = pbe.getDataStream(decryptorFactory); - - encryptedData = pbe; - currentProgress += 5; - } else { - updateProgress(R.string.progress_finding_key, currentProgress, 100); - - PGPPublicKeyEncryptedData pbe = null; - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID()); - if (secretKey != null) { - pbe = encData; - break; - } - } - } - - if (secretKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); - } - - currentProgress += 5; - updateProgress(R.string.progress_extracting_key, currentProgress, 100); - PGPPrivateKey privateKey = null; - try { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.toCharArray()); - privateKey = secretKey.extractPrivateKey(keyDecryptor); - } catch (PGPException e) { - throw new PGPException(mContext.getString(R.string.error_wrong_passphrase)); - } - if (privateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - currentProgress += 5; - updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - - PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); - - clear = pbe.getDataStream(decryptorFactory); - - encryptedData = pbe; - currentProgress += 5; - } - - PGPObjectFactory plainFact = new PGPObjectFactory(clear); - Object dataChunk = plainFact.nextObject(); - PGPOnePassSignature signature = null; - PGPPublicKey signatureKey = null; - int signatureIndex = -1; - - if (dataChunk instanceof PGPCompressedData) { - updateProgress(R.string.progress_decompressing_data, currentProgress, 100); - - PGPObjectFactory fact = new PGPObjectFactory( - ((PGPCompressedData) dataChunk).getDataStream()); - dataChunk = fact.nextObject(); - plainFact = fact; - currentProgress += 10; - } - - long signatureKeyId = 0; - if (dataChunk instanceof PGPOnePassSignatureList) { - updateProgress(R.string.progress_processing_signature, currentProgress, 100); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); - PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = ProviderHelper - .getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - signature = null; - } else { - signatureIndex = i; - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( - mContext, signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature != null) { - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - signature.init(contentVerifierBuilderProvider, signatureKey); - } else { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); - } - - dataChunk = plainFact.nextObject(); - currentProgress += 10; - } - - if (dataChunk instanceof PGPSignatureList) { - dataChunk = plainFact.nextObject(); - } - - if (dataChunk instanceof PGPLiteralData) { - updateProgress(R.string.progress_decrypting, currentProgress, 100); - - PGPLiteralData literalData = (PGPLiteralData) dataChunk; - OutputStream out = mOutStream; - - byte[] buffer = new byte[1 << 16]; - InputStream dataIn = literalData.getInputStream(); - - int startProgress = currentProgress; - int endProgress = 100; - if (signature != null) { - endProgress = 90; - } else if (encryptedData.isIntegrityProtected()) { - endProgress = 95; - } - int n = 0; - int done = 0; - long startPos = mData.getStreamPosition(); - while ((n = dataIn.read(buffer)) > 0) { - out.write(buffer, 0, n); - done += n; - if (signature != null) { - try { - signature.update(buffer, 0, n); - } catch (SignatureException e) { - returnData - .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); - signature = null; - } - } - // unknown size, but try to at least have a moving, slowing down progress bar - currentProgress = startProgress + (endProgress - startProgress) * done - / (done + 100000); - if (mData.getSize() - startPos == 0) { - currentProgress = endProgress; - } else { - currentProgress = (int) (startProgress + (endProgress - startProgress) - * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos)); - } - updateProgress(currentProgress, 100); - } - - if (signature != null) { - updateProgress(R.string.progress_verifying_signature, 90, 100); - - PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); - PGPSignature messageSignature = signatureList.get(signatureIndex); - - //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(mContext, messageSignature, signatureKey); - boolean sig_isok = signature.verify(messageSignature); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok); - } - } - - // TODO: test if this integrity really check works! - if (encryptedData.isIntegrityProtected()) { - updateProgress(R.string.progress_verifying_integrity, 95, 100); - - if (encryptedData.verify()) { - // passed - } else { - // failed - throw new PgpGeneralException(mContext.getString(R.string.error_integrity_check_failed)); - } - } else { - // no integrity check - Log.e(Constants.TAG, "No integrity check!"); - } - - updateProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public Bundle verifyText() throws IOException, PgpGeneralException, - PGPException, SignatureException { - Bundle returnData = new Bundle(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArmoredInputStream aIn = new ArmoredInputStream(mData.getInputStream()); - - updateProgress(R.string.progress_done, 0, 100); - - // mostly taken from pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, aIn); - byte[] lineSep = getLineSeparator(); - - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - - while (lookAhead != -1 && aIn.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, aIn); - line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - } - - out.close(); - - byte[] clearText = out.toByteArray(); - mOutStream.write(clearText); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); - - updateProgress(R.string.progress_processing_signature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); - - PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); - if (sigList == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_corrupt_data)); - } - PGPSignature signature = null; - long signatureKeyId = 0; - PGPPublicKey signatureKey = null; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, - signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature == null) { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); - if (mProgress != null) - mProgress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - signature.init(contentVerifierBuilderProvider, signatureKey); - - InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); - - lookAhead = readInputLine(lineOut, sigIn); - - processLine(signature, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, sigIn); - - signature.update((byte) '\r'); - signature.update((byte) '\n'); - - processLine(signature, lineOut.toByteArray()); - } while (lookAhead != -1); - } - - boolean sig_isok = signature.verify(); - - //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(mContext, signature, signatureKey); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok); - - updateProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public boolean verifyKeyBinding(Context mContext, PGPSignature signature, PGPPublicKey signatureKey) { - long signatureKeyId = signature.getKeyID(); - boolean keyBinding_isok = false; - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, - signatureKeyId); - PGPPublicKey mKey = null; - if (signKeyRing != null) { - mKey = PgpKeyHelper.getMasterKey(signKeyRing); - } - if (signature.getKeyID() != mKey.getKeyID()) { - keyBinding_isok = verifyKeyBinding(mKey, signatureKey); - } else { //if the key used to make the signature was the master key, no need to check binding sigs - keyBinding_isok = true; - } - return keyBinding_isok; - } - - public boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { - boolean subkeyBinding_isok = false; - boolean tmp_subkeyBinding_isok = false; - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - Iterator itr = signingPublicKey.getSignatures(); - - subkeyBinding_isok = false; - tmp_subkeyBinding_isok = false; - primkeyBinding_isok = false; - while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? - //gpg has an invalid subkey binding error on key import I think, but doesn't shout - //about keys without subkey signing. Can't get it to import a slightly broken one - //either, so we will err on bad subkey binding here. - PGPSignature sig = itr.next(); - if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { - //check and if ok, check primary key binding. - try { - sig.init(contentVerifierBuilderProvider, masterPublicKey); - tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey); - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - - if (tmp_subkeyBinding_isok) - subkeyBinding_isok = true; - if (tmp_subkeyBinding_isok) { - primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - } - } - } - return (subkeyBinding_isok & primkeyBinding_isok); - } - - private boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureList eSigList; - - if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { - try { - eSigList = Pkts.getEmbeddedSignatures(); - } catch (IOException e) { - return false; - } catch (PGPException e) { - return false; - } - for (int j = 0; j < eSigList.size(); ++j) { - PGPSignature emSig = eSigList.get(j); - if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { - try { - emSig.init(contentVerifierBuilderProvider, signingPublicKey); - primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - } - } - } - return primkeyBinding_isok; - } - - 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); - } - - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPV3SignatureGenerator 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); - } - - // taken from ClearSignedFileProcessor in BC - private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, - IOException { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sig.update(line, 0, length); - } - } - - private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - private static int getLengthWithoutSeparator(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isLineEnding(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static int getLengthWithoutWhiteSpace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isWhiteSpace(byte b) { - return b == '\r' || b == '\n' || b == '\t' || b == ' '; - } - - private static byte[] getLineSeparator() { - String nl = System.getProperty("line.separator"); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; - } -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationIncoming.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationIncoming.java new file mode 100644 index 000000000..c57691f8d --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationIncoming.java @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.content.Context; +import android.os.Bundle; + +import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SignatureException; +import java.util.Iterator; + +/** + * TODO: make builder pattern like in PgpOperationOutgoing + */ +public class PgpOperationIncoming { + private Context mContext; + private ProgressDialogUpdater mProgress; + private InputData mData; + private OutputStream mOutStream; + + public PgpOperationIncoming(Context context, ProgressDialogUpdater progress, InputData data, + OutputStream outStream) { + super(); + this.mContext = context; + this.mProgress = progress; + this.mData = data; + this.mOutStream = outStream; + } + + public void updateProgress(int message, int current, int total) { + if (mProgress != null) { + mProgress.setProgress(message, current, total); + } + } + + public void updateProgress(int current, int total) { + if (mProgress != null) { + mProgress.setProgress(current, total); + } + } + + public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) + throws PgpGeneralException, IOException { + InputStream in = PGPUtil.getDecoderStream(inputStream); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); + } + + Iterator it = enc.getEncryptedDataObjects(); + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + return true; + } + } + + return false; + } + + public Bundle decryptAndVerify(String passphrase, boolean assumeSymmetric) throws IOException, + PgpGeneralException, PGPException, SignatureException { + if (passphrase == null) { + passphrase = ""; + } + + Bundle returnData = new Bundle(); + // automatically works with ascii armor input and binary + InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(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) { + throw new PgpGeneralException(mContext.getString(R.string.error_invalid_data)); + } + + InputStream clear; + PGPEncryptedData encryptedData; + + currentProgress += 5; + + // TODO: currently we always only look at the first known key or symmetric encryption, + // there might be more... + if (assumeSymmetric) { + PGPPBEEncryptedData pbe = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + pbe = (PGPPBEEncryptedData) obj; + break; + } + } + + if (pbe == null) { + throw new PgpGeneralException( + mContext.getString(R.string.error_no_symmetric_encryption_packet)); + } + + updateProgress(R.string.progress_preparing_streams, currentProgress, 100); + + PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); + PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( + digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + passphrase.toCharArray()); + + clear = pbe.getDataStream(decryptorFactory); + + encryptedData = pbe; + currentProgress += 5; + } else { + updateProgress(R.string.progress_finding_key, currentProgress, 100); + + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; + secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID()); + if (secretKey != null) { + pbe = encData; + break; + } + } + } + + if (secretKey == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); + } + + currentProgress += 5; + updateProgress(R.string.progress_extracting_key, currentProgress, 100); + PGPPrivateKey privateKey = null; + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + passphrase.toCharArray()); + privateKey = secretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + throw new PGPException(mContext.getString(R.string.error_wrong_passphrase)); + } + if (privateKey == null) { + throw new PgpGeneralException( + mContext.getString(R.string.error_could_not_extract_private_key)); + } + currentProgress += 5; + updateProgress(R.string.progress_preparing_streams, currentProgress, 100); + + PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); + + clear = pbe.getDataStream(decryptorFactory); + + encryptedData = pbe; + currentProgress += 5; + } + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + Object dataChunk = plainFact.nextObject(); + PGPOnePassSignature signature = null; + PGPPublicKey signatureKey = null; + int signatureIndex = -1; + + if (dataChunk instanceof PGPCompressedData) { + updateProgress(R.string.progress_decompressing_data, currentProgress, 100); + + PGPObjectFactory fact = new PGPObjectFactory( + ((PGPCompressedData) dataChunk).getDataStream()); + dataChunk = fact.nextObject(); + plainFact = fact; + currentProgress += 10; + } + + long signatureKeyId = 0; + if (dataChunk instanceof PGPOnePassSignatureList) { + updateProgress(R.string.progress_processing_signature, currentProgress, 100); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = ProviderHelper + .getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureIndex = i; + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( + mContext, signatureKeyId); + if (signKeyRing != null) { + userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + } + returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature != null) { + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signature.init(contentVerifierBuilderProvider, signatureKey); + } else { + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + } + + dataChunk = plainFact.nextObject(); + currentProgress += 10; + } + + if (dataChunk instanceof PGPSignatureList) { + dataChunk = plainFact.nextObject(); + } + + if (dataChunk instanceof PGPLiteralData) { + updateProgress(R.string.progress_decrypting, currentProgress, 100); + + PGPLiteralData literalData = (PGPLiteralData) dataChunk; + OutputStream out = mOutStream; + + byte[] buffer = new byte[1 << 16]; + InputStream dataIn = literalData.getInputStream(); + + int startProgress = currentProgress; + int endProgress = 100; + if (signature != null) { + endProgress = 90; + } else if (encryptedData.isIntegrityProtected()) { + endProgress = 95; + } + + int n; +// int progress = 0; + long startPos = mData.getStreamPosition(); + while ((n = dataIn.read(buffer)) > 0) { + out.write(buffer, 0, n); +// progress += n; + if (signature != null) { + try { + signature.update(buffer, 0, n); + } catch (SignatureException e) { + returnData + .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); + signature = null; + } + } + // TODO: dead code?! + // unknown size, but try to at least have a moving, slowing down progress bar +// currentProgress = startProgress + (endProgress - startProgress) * progress +// / (progress + 100000); + if (mData.getSize() - startPos == 0) { + currentProgress = endProgress; + } else { + currentProgress = (int) (startProgress + (endProgress - startProgress) + * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos)); + } + updateProgress(currentProgress, 100); + } + + if (signature != null) { + updateProgress(R.string.progress_verifying_signature, 90, 100); + + PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); + PGPSignature messageSignature = signatureList.get(signatureIndex); + + //Now check binding signatures + boolean keyBinding_isok = verifyKeyBinding(mContext, messageSignature, signatureKey); + boolean sig_isok = signature.verify(messageSignature); + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok); + } + } + + // TODO: test if this integrity really check works! + if (encryptedData.isIntegrityProtected()) { + updateProgress(R.string.progress_verifying_integrity, 95, 100); + + if (encryptedData.verify()) { + // passed + Log.d(Constants.TAG, "Integrity verification: success!"); + } else { + // failed + Log.d(Constants.TAG, "Integrity verification: failed!"); + throw new PgpGeneralException(mContext.getString(R.string.error_integrity_check_failed)); + } + } else { + // no integrity check + Log.e(Constants.TAG, "Encrypted data was not integrity protected!"); + } + + updateProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public Bundle verifyText() throws IOException, PgpGeneralException, + PGPException, SignatureException { + Bundle returnData = new Bundle(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArmoredInputStream aIn = new ArmoredInputStream(mData.getInputStream()); + + updateProgress(R.string.progress_done, 0, 100); + + // mostly taken from pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, aIn); + byte[] lineSep = getLineSeparator(); + + byte[] line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + + while (lookAhead != -1 && aIn.isClearText()) { + lookAhead = readInputLine(lineOut, lookAhead, aIn); + line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + } + + out.close(); + + byte[] clearText = out.toByteArray(); + mOutStream.write(clearText); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); + + updateProgress(R.string.progress_processing_signature, 60, 100); + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + + PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); + if (sigList == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_corrupt_data)); + } + PGPSignature signature = null; + long signatureKeyId = 0; + PGPPublicKey signatureKey = null; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + + if (signatureKey == null) { + signature = null; + } else { + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, + signatureKeyId); + if (signKeyRing != null) { + userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + } + returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature == null) { + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + if (mProgress != null) + mProgress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signature.init(contentVerifierBuilderProvider, signatureKey); + + InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); + + lookAhead = readInputLine(lineOut, sigIn); + + processLine(signature, lineOut.toByteArray()); + + if (lookAhead != -1) { + do { + lookAhead = readInputLine(lineOut, lookAhead, sigIn); + + signature.update((byte) '\r'); + signature.update((byte) '\n'); + + processLine(signature, lineOut.toByteArray()); + } while (lookAhead != -1); + } + + boolean sig_isok = signature.verify(); + + //Now check binding signatures + boolean keyBinding_isok = verifyKeyBinding(mContext, signature, signatureKey); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok); + + updateProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public boolean verifyKeyBinding(Context mContext, PGPSignature signature, PGPPublicKey signatureKey) { + long signatureKeyId = signature.getKeyID(); + boolean keyBinding_isok = false; + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, + signatureKeyId); + PGPPublicKey mKey = null; + if (signKeyRing != null) { + mKey = PgpKeyHelper.getMasterKey(signKeyRing); + } + if (signature.getKeyID() != mKey.getKeyID()) { + keyBinding_isok = verifyKeyBinding(mKey, signatureKey); + } else { //if the key used to make the signature was the master key, no need to check binding sigs + keyBinding_isok = true; + } + return keyBinding_isok; + } + + public boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + boolean subkeyBinding_isok = false; + boolean tmp_subkeyBinding_isok = false; + boolean primkeyBinding_isok = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + Iterator itr = signingPublicKey.getSignatures(); + + subkeyBinding_isok = false; + tmp_subkeyBinding_isok = false; + primkeyBinding_isok = false; + while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? + //gpg has an invalid subkey binding error on key import I think, but doesn't shout + //about keys without subkey signing. Can't get it to import a slightly broken one + //either, so we will err on bad subkey binding here. + PGPSignature sig = itr.next(); + if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { + //check and if ok, check primary key binding. + try { + sig.init(contentVerifierBuilderProvider, masterPublicKey); + tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey); + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + + if (tmp_subkeyBinding_isok) + subkeyBinding_isok = true; + if (tmp_subkeyBinding_isok) { + primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + } + } + } + return (subkeyBinding_isok & primkeyBinding_isok); + } + + private boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + boolean primkeyBinding_isok = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureList eSigList; + + if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { + try { + eSigList = Pkts.getEmbeddedSignatures(); + } catch (IOException e) { + return false; + } catch (PGPException e) { + return false; + } + for (int j = 0; j < eSigList.size(); ++j) { + PGPSignature emSig = eSigList.get(j); + if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { + try { + emSig.init(contentVerifierBuilderProvider, signingPublicKey); + primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + } + } + } + return primkeyBinding_isok; + } + + + // taken from ClearSignedFileProcessor in BC + private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, + IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sig.update(line, 0, length); + } + } + + private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) + throws IOException { + bOut.reset(); + + int lookAhead = -1; + int ch; + + while ((ch = fIn.read()) >= 0) { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + + return lookAhead; + } + + private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) + throws IOException { + bOut.reset(); + + int ch = lookAhead; + + do { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } while ((ch = fIn.read()) >= 0); + + if (ch < 0) { + lookAhead = -1; + } + + return lookAhead; + } + + private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + throws IOException { + int lookAhead = fIn.read(); + + if (lastCh == '\r' && lookAhead == '\n') { + bOut.write(lookAhead); + lookAhead = fIn.read(); + } + + return lookAhead; + } + + private static int getLengthWithoutSeparator(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isLineEnding(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isLineEnding(byte b) { + return b == '\r' || b == '\n'; + } + + private static int getLengthWithoutWhiteSpace(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isWhiteSpace(byte b) { + return b == '\r' || b == '\n' || b == '\t' || b == ' '; + } + + private static byte[] getLineSeparator() { + String nl = System.getProperty("line.separator"); + byte[] nlBytes = new byte[nl.length()]; + + for (int i = 0; i != nlBytes.length; i++) { + nlBytes[i] = (byte) nl.charAt(i); + } + + return nlBytes; + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationOutgoing.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationOutgoing.java new file mode 100644 index 000000000..346638fa7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperationOutgoing.java @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.content.Context; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +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.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPV3SignatureGenerator; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Date; + +/** + * This class uses a Builder pattern! + */ +public class PgpOperationOutgoing { + private Context context; + private InputData data; + private OutputStream outStream; + + private ProgressDialogUpdater progress; + private boolean enableAsciiArmorOutput; + private int compressionId; + private long[] encryptionKeyIds; + private String encryptionPassphrase; + private int symmetricEncryptionAlgorithm; + private long signatureKeyId; + private int signatureHashAlgorithm; + private boolean signatureForceV3; + private String signaturePassphrase; + + private PgpOperationOutgoing(Builder builder) { + // private Constructor can only be called from Builder + this.context = builder.context; + this.data = builder.data; + this.outStream = builder.outStream; + + this.progress = builder.progress; + this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput; + this.compressionId = builder.compressionId; + this.encryptionKeyIds = builder.encryptionKeyIds; + this.encryptionPassphrase = builder.encryptionPassphrase; + this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm; + this.signatureKeyId = builder.signatureKeyId; + this.signatureHashAlgorithm = builder.signatureHashAlgorithm; + this.signatureForceV3 = builder.signatureForceV3; + this.signaturePassphrase = builder.signaturePassphrase; + } + + public static class Builder { + // mandatory parameter + private Context context; + private InputData data; + private OutputStream outStream; + + // optional + private ProgressDialogUpdater progress = null; + private boolean enableAsciiArmorOutput = false; + private int compressionId = Id.choice.compression.none; + private long[] encryptionKeyIds = new long[0]; + private String encryptionPassphrase = null; + private int symmetricEncryptionAlgorithm = 0; + private long signatureKeyId = Id.key.none; + private int signatureHashAlgorithm = 0; + private boolean signatureForceV3 = false; + private String signaturePassphrase = null; + + public Builder(Context context, InputData data, OutputStream outStream) { + this.context = context; + this.data = data; + this.outStream = outStream; + } + + public Builder progress(ProgressDialogUpdater progress) { + this.progress = progress; + return this; + } + + public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + this.enableAsciiArmorOutput = enableAsciiArmorOutput; + return this; + } + + public Builder compressionId(int compressionId) { + this.compressionId = compressionId; + return this; + } + + public Builder encryptionKeyIds(long[] encryptionKeyIds) { + this.encryptionKeyIds = encryptionKeyIds; + return this; + } + + public Builder encryptionPassphrase(String encryptionPassphrase) { + this.encryptionPassphrase = encryptionPassphrase; + return this; + } + + public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; + return this; + } + + public Builder signatureKeyId(long signatureKeyId) { + this.signatureKeyId = signatureKeyId; + return this; + } + + public Builder signatureHashAlgorithm(int signatureHashAlgorithm) { + this.signatureHashAlgorithm = signatureHashAlgorithm; + return this; + } + + public Builder signatureForceV3(boolean signatureForceV3) { + this.signatureForceV3 = signatureForceV3; + return this; + } + + public Builder signaturePassphrase(String signaturePassphrase) { + this.signaturePassphrase = signaturePassphrase; + return this; + } + + public PgpOperationOutgoing build() { + return new PgpOperationOutgoing(this); + } + } + + public void updateProgress(int message, int current, int total) { + if (progress != null) { + progress.setProgress(message, current, total); + } + } + + public void updateProgress(int current, int total) { + if (progress != null) { + progress.setProgress(current, total); + } + } + + public void signAndEncrypt() + throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, + NoSuchAlgorithmException, SignatureException { + + if (encryptionKeyIds == null) { + encryptionKeyIds = new long[0]; + } + + boolean enableSignature = signatureKeyId != Id.key.none; + boolean enableCompression = compressionId != Id.choice.compression.none; + boolean enableEncryption = encryptionKeyIds.length != 0 || encryptionPassphrase != null; + + int signatureType; + if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { + signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; + } else { + signatureType = PGPSignature.BINARY_DOCUMENT; + } + + ArmoredOutputStream armorOut = null; + OutputStream out; + OutputStream encryptionOut = null; + if (enableAsciiArmorOutput) { + armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + + + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + if (enableSignature) { + signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); + signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); + if (signingKey == null) { + throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); + } + + if (signaturePassphrase == null) { + throw new PgpGeneralException( + context.getString(R.string.error_no_signature_passphrase)); + } + + updateProgress(R.string.progress_extracting_signature_key, 0, 100); + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); + signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralException( + context.getString(R.string.error_could_not_extract_private_key)); + } + } + updateProgress(R.string.progress_preparing_streams, 5, 100); + + // encrypt and compress input file content + if (enableEncryption) { + // has Integrity packet enabled! + JcePGPDataEncryptorBuilder encryptorBuilder = + new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) + .setWithIntegrityPacket(true); + + PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(encryptorBuilder); + + if (encryptionKeyIds.length == 0) { + // Symmetric encryption + Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); + + JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = + new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray()); + cPk.addMethod(symmetricEncryptionGenerator); + } else { + // Asymmetric encryption + for (long id : encryptionKeyIds) { + PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id); + if (key != null) { + + JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = + new JcePublicKeyKeyEncryptionMethodGenerator(key); + cPk.addMethod(pubKeyEncryptionGenerator); + } + } + } + encryptionOut = cPk.open(out, new byte[1 << 16]); + } + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (enableSignature) { + updateProgress(R.string.progress_preparing_signature, 10, 100); + + // content signer based on signing key algorithm and chosen hash algorithm + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + if (signatureForceV3) { + signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); + signatureV3Generator.init(signatureType, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(signatureType, signaturePrivateKey); + + String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + } + + PGPCompressedDataGenerator compressGen = null; + OutputStream pOut; + if (enableEncryption) { + BCPGOutputStream bcpgOut; + if (enableCompression) { + compressGen = new PGPCompressedDataGenerator(compressionId); + bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); + } else { + bcpgOut = new BCPGOutputStream(encryptionOut); + } + + if (enableSignature) { + if (signatureForceV3) { + signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); + } else { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } + } + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + // file name not needed, so empty string + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), + new byte[1 << 16]); + updateProgress(R.string.progress_encrypting, 20, 100); + + long progress = 0; + int n; + byte[] buffer = new byte[1 << 16]; + InputStream in = data.getInputStream(); + while ((n = in.read(buffer)) > 0) { + pOut.write(buffer, 0, n); + + // update signature buffer if signature is requested + if (enableSignature) { + if (signatureForceV3) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } + } + + progress += n; + if (data.getSize() != 0) { + updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100); + } + } + + literalGen.close(); + } else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { + /* sign-only of ascii text */ + + updateProgress(R.string.progress_signing, 40, 100); + + // write directly on armor output stream + armorOut.beginClearText(signatureHashAlgorithm); + + InputStream in = data.getInputStream(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + final byte[] newline = "\r\n".getBytes("UTF-8"); + + if (signatureForceV3) { + processLine(reader.readLine(), armorOut, signatureV3Generator); + } else { + processLine(reader.readLine(), armorOut, signatureGenerator); + } + + while (true) { + String line = reader.readLine(); + + if (line == null) { + armorOut.write(newline); + break; + } + + armorOut.write(newline); + if (signatureForceV3) { + signatureV3Generator.update(newline); + processLine(line, armorOut, signatureV3Generator); + } else { + signatureGenerator.update(newline); + processLine(line, armorOut, signatureGenerator); + } + } + + armorOut.endClearText(); + + pOut = new BCPGOutputStream(armorOut); + } else { + // TODO: implement sign-only for files! + pOut = null; + Log.e(Constants.TAG, "not supported!"); + } + + if (enableSignature) { + updateProgress(R.string.progress_generating_signature, 95, 100); + if (signatureForceV3) { + signatureV3Generator.generate().encode(pOut); + } else { + signatureGenerator.generate().encode(pOut); + } + } + + // closing outputs... + if (enableEncryption) { + encryptionOut.close(); + + if (enableCompression) { + compressGen.close(); + } + } + if (enableAsciiArmorOutput) { + armorOut.close(); + } + + updateProgress(R.string.progress_done, 100, 100); + } + + // TODO: merge this into signAndEncrypt method! + public void generateSignature() + throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, + SignatureException { + + OutputStream out; + if (enableAsciiArmorOutput) { + // Ascii Armor (Radix-64) + ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + + if (signatureKeyId == 0) { + throw new PgpGeneralException(context.getString(R.string.error_no_signature_key)); + } + + PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); + PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); + if (signingKey == null) { + throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); + } + + if (signaturePassphrase == null) { + throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase)); + } + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); + PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralException( + context.getString(R.string.error_could_not_extract_private_key)); + } + updateProgress(R.string.progress_preparing_streams, 0, 100); + + updateProgress(R.string.progress_preparing_signature, 30, 100); + + int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; +// if (binary) { +// type = PGPSignature.BINARY_DOCUMENT; +// } + + // content signer based on signing key algorithm and chosen hash algorithm + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey + .getPublicKey().getAlgorithm(), signatureHashAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (signatureForceV3) { + signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); + signatureV3Generator.init(type, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(type, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + updateProgress(R.string.progress_signing, 40, 100); + + InputStream inStream = data.getInputStream(); +// if (binary) { +// byte[] buffer = new byte[1 << 16]; +// int n = 0; +// while ((n = inStream.read(buffer)) > 0) { +// if (signatureForceV3) { +// signatureV3Generator.update(buffer, 0, n); +// } else { +// signatureGenerator.update(buffer, 0, n); +// } +// } +// } else { + final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); + final byte[] newline = "\r\n".getBytes("UTF-8"); + + String line; + while ((line = reader.readLine()) != null) { + if (signatureForceV3) { + processLine(line, null, signatureV3Generator); + signatureV3Generator.update(newline); + } else { + processLine(line, null, signatureGenerator); + signatureGenerator.update(newline); + } + } +// } + + BCPGOutputStream bOut = new BCPGOutputStream(out); + if (signatureForceV3) { + signatureV3Generator.generate().encode(bOut); + } else { + signatureGenerator.generate().encode(bOut); + } + out.close(); + outStream.close(); + + updateProgress(R.string.progress_done, 100, 100); + } + + + 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); + } + + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPV3SignatureGenerator 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); + } + + private static boolean isWhiteSpace(byte b) { + return b == '\r' || b == '\n' || b == '\t' || b == ' '; + } + +} -- cgit v1.2.3