aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java')
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java1068
1 files changed, 1068 insertions, 0 deletions
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java
new file mode 100644
index 000000000..823fb1f2e
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpOperation.java
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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.BCPGOutputStream;
+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.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);
+ }
+ }
+
+ /**
+ * Encrypt and Sign data
+ *
+ * @param mContext
+ * @param mProgress
+ * @param mData
+ * @param mOutStream
+ * @param useAsciiArmor
+ * @param compression
+ * @param encryptionKeyIds
+ * @param symmetricEncryptionAlgorithm
+ * @param encryptionPassphrase
+ * @param signatureKeyId
+ * @param signatureHashAlgorithm
+ * @param signatureForceV3
+ * @param signaturePassphrase
+ * @throws IOException
+ * @throws PgpGeneralException
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @throws NoSuchAlgorithmException
+ * @throws SignatureException
+ */
+ public void encryptAndSign(boolean useAsciiArmor, int compression, 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];
+ }
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out = null;
+ OutputStream encryptOut = null;
+ if (useAsciiArmor) {
+ 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 (encryptionKeyIds.length == 0 && encryptionPassphrase == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_noEncryptionKeysOrPassPhrase));
+ }
+
+ if (signatureKeyId != Id.key.none) {
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassphrase == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_noSignaturePassPhrase));
+ }
+
+ updateProgress(R.string.progress_extractingSignatureKey, 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_couldNotExtractPrivateKey));
+ }
+ }
+ updateProgress(R.string.progress_preparingStreams, 5, 100);
+
+ // encrypt and compress input file content
+ 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);
+ }
+ }
+ }
+ encryptOut = cPk.open(out, new byte[1 << 16]);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ if (signatureKeyId != Id.key.none) {
+ updateProgress(R.string.progress_preparingSignature, 10, 100);
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (signatureForceV3) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey);
+
+ String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper
+ .getMasterKey(signingKeyRing));
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ spGen.setSignerUserID(false, userId);
+ signatureGenerator.setHashedSubpackets(spGen.generate());
+ }
+ }
+
+ PGPCompressedDataGenerator compressGen = null;
+ BCPGOutputStream bcpgOut = null;
+ if (compression == Id.choice.compression.none) {
+ bcpgOut = new BCPGOutputStream(encryptOut);
+ } else {
+ compressGen = new PGPCompressedDataGenerator(compression);
+ bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut));
+ }
+ if (signatureKeyId != Id.key.none) {
+ if (signatureForceV3) {
+ signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
+ } else {
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
+ }
+ }
+
+ PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
+ // file name not needed, so empty string
+ OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(),
+ new byte[1 << 16]);
+ updateProgress(R.string.progress_encrypting, 20, 100);
+
+ long done = 0;
+ int n = 0;
+ byte[] buffer = new byte[1 << 16];
+ InputStream in = mData.getInputStream();
+ while ((n = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, n);
+ if (signatureKeyId != Id.key.none) {
+ if (signatureForceV3) {
+ signatureV3Generator.update(buffer, 0, n);
+ } else {
+ signatureGenerator.update(buffer, 0, n);
+ }
+ }
+ done += n;
+ if (mData.getSize() != 0) {
+ updateProgress((int) (20 + (95 - 20) * done / mData.getSize()), 100);
+ }
+ }
+
+ literalGen.close();
+
+ if (signatureKeyId != Id.key.none) {
+ updateProgress(R.string.progress_generatingSignature, 95, 100);
+ if (signatureForceV3) {
+ signatureV3Generator.generate().encode(pOut);
+ } else {
+ signatureGenerator.generate().encode(pOut);
+ }
+ }
+ if (compressGen != null) {
+ compressGen.close();
+ }
+ encryptOut.close();
+ if (useAsciiArmor) {
+ 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 {
+
+ ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
+
+ PGPSecretKey signingKey = null;
+ PGPSecretKeyRing signingKeyRing = null;
+ PGPPrivateKey signaturePrivateKey = null;
+
+ if (signatureKeyId == 0) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_noSignatureKey));
+ }
+
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassphrase == null) {
+ armorOut.close();
+ throw new PgpGeneralException(mContext.getString(R.string.error_noSignaturePassPhrase));
+ }
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
+ signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
+ if (signaturePrivateKey == null) {
+ armorOut.close();
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ updateProgress(R.string.progress_preparingStreams, 0, 100);
+
+ updateProgress(R.string.progress_preparingSignature, 30, 100);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
+ .getPublicKey().getAlgorithm(), signatureHashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ if (forceV3Signature) {
+ signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
+ signatureV3Generator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
+ } else {
+ signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, 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);
+
+ armorOut.beginClearText(signatureHashAlgorithm);
+
+ InputStream inStream = mData.getInputStream();
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
+
+ final byte[] newline = "\r\n".getBytes("UTF-8");
+
+ if (forceV3Signature) {
+ processLine(reader.readLine(), armorOut, signatureV3Generator);
+ } else {
+ processLine(reader.readLine(), armorOut, signatureGenerator);
+ }
+
+ while (true) {
+ final String line = reader.readLine();
+
+ if (line == null) {
+ armorOut.write(newline);
+ break;
+ }
+
+ armorOut.write(newline);
+ if (forceV3Signature) {
+ signatureV3Generator.update(newline);
+ processLine(line, armorOut, signatureV3Generator);
+ } else {
+ signatureGenerator.update(newline);
+ processLine(line, armorOut, signatureGenerator);
+ }
+ }
+
+ armorOut.endClearText();
+
+ BCPGOutputStream bOut = new BCPGOutputStream(armorOut);
+ if (forceV3Signature) {
+ signatureV3Generator.generate().encode(bOut);
+ } else {
+ signatureGenerator.generate().encode(bOut);
+ }
+ armorOut.close();
+
+ updateProgress(R.string.progress_done, 100, 100);
+ }
+
+ public void generateSignature(boolean armored, boolean binary, long signatureKeyId,
+ String signaturePassPhrase, int hashAlgorithm, boolean forceV3Signature)
+ throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException,
+ SignatureException {
+
+ OutputStream out = null;
+
+ // Ascii Armor (Base64)
+ ArmoredOutputStream armorOut = null;
+ if (armored) {
+ 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 (signatureKeyId == 0) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_noSignatureKey));
+ }
+
+ signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId);
+ signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId);
+ if (signingKey == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_signatureFailed));
+ }
+
+ if (signaturePassPhrase == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_noSignaturePassPhrase));
+ }
+
+ 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_couldNotExtractPrivateKey));
+ }
+ updateProgress(R.string.progress_preparingStreams, 0, 100);
+
+ updateProgress(R.string.progress_preparingSignature, 30, 100);
+
+ PGPSignatureGenerator signatureGenerator = null;
+ PGPV3SignatureGenerator signatureV3Generator = null;
+
+ int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
+ if (binary) {
+ type = PGPSignature.BINARY_DOCUMENT;
+ }
+
+ // content signer based on signing key algorithm and choosen hash algorithm
+ JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
+ .getPublicKey().getAlgorithm(), hashAlgorithm)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ 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");
+
+ while (true) {
+ final String line = reader.readLine();
+
+ if (line == null) {
+ break;
+ }
+
+ 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();
+
+ if (mProgress != null)
+ mProgress.setProgress(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_invalidData));
+ }
+
+ 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();
+ InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+ long signatureKeyId = 0;
+
+ int currentProgress = 0;
+ updateProgress(R.string.progress_readingData, 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_invalidData));
+ }
+
+ 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_noSymmetricEncryptionPacket));
+ }
+
+ updateProgress(R.string.progress_preparingStreams, 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_findingKey, 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_noSecretKeyFound));
+ }
+
+ currentProgress += 5;
+ updateProgress(R.string.progress_extractingKey, 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_wrongPassPhrase));
+ }
+ if (privateKey == null) {
+ throw new PgpGeneralException(
+ mContext.getString(R.string.error_couldNotExtractPrivateKey));
+ }
+ currentProgress += 5;
+ updateProgress(R.string.progress_preparingStreams, 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_decompressingData, currentProgress, 100);
+
+ PGPObjectFactory fact = new PGPObjectFactory(
+ ((PGPCompressedData) dataChunk).getDataStream());
+ dataChunk = fact.nextObject();
+ plainFact = fact;
+ currentProgress += 10;
+ }
+
+ if (dataChunk instanceof PGPOnePassSignatureList) {
+ updateProgress(R.string.progress_processingSignature, 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_verifyingSignature, 90, 100);
+
+ PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
+ PGPSignature messageSignature = signatureList.get(signatureIndex);
+ if (signature.verify(messageSignature)) {
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, true);
+ } else {
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
+ }
+ }
+ }
+
+ // TODO: add integrity somewhere
+ if (encryptedData.isIntegrityProtected()) {
+ updateProgress(R.string.progress_verifyingIntegrity, 95, 100);
+
+ if (encryptedData.verify()) {
+ // passed
+ } else {
+ // failed
+ }
+ } else {
+ // no integrity check
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ public Bundle verifyText(boolean lookupUnknownKey) 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 ClearSignedFileProcessor
+ 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_processingSignature, 60, 100);
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+
+ PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
+ if (sigList == null) {
+ throw new PgpGeneralException(mContext.getString(R.string.error_corruptData));
+ }
+ 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 key is not known and we want to lookup unknown ones...
+ if (signatureKey == null && lookupUnknownKey) {
+
+ returnData = new Bundle();
+ returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY, true);
+
+ // return directly now, decrypt will be done again after importing unknown key
+ return returnData;
+ }
+
+ 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);
+ }
+
+ returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, signature.verify());
+
+ updateProgress(R.string.progress_done, 100, 100);
+ return returnData;
+ }
+
+ 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;
+ }
+}