aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
diff options
context:
space:
mode:
authorVincent Breitmoser <valodim@mugenguild.com>2015-02-01 23:14:26 +0100
committerVincent Breitmoser <valodim@mugenguild.com>2015-02-01 23:14:26 +0100
commit7b24ee7b55db99467dd63e631ba55a27d08587d5 (patch)
tree6a548fd15cb77882754e2355cf1f4c72660f4962 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
parent0b6dc65c97b0fb5dae9bf1a06f9c7db0b65ea4ad (diff)
downloadopen-keychain-7b24ee7b55db99467dd63e631ba55a27d08587d5.tar.gz
open-keychain-7b24ee7b55db99467dd63e631ba55a27d08587d5.tar.bz2
open-keychain-7b24ee7b55db99467dd63e631ba55a27d08587d5.zip
rewrite PgpSignEncrypt data flow
- introduce high-level SignEncryptOperation for uri to uri signing/encryption - use SignEncryptParcel for high-level operation parameters - use PgpSignEncryptInput plus streams for low-level operation parameters - get rid of all sign/encrypt logic in KeychainIntentService
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java578
1 files changed, 578 insertions, 0 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
new file mode 100644
index 000000000..2fa01d241
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.pgp;
+
+import android.content.Context;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPLiteralDataGenerator;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.SignatureException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** This class supports a single, low-level, sign/encrypt operation.
+ *
+ * The operation of this class takes an Input- and OutputStream plus a
+ * PgpSignEncryptInput, and signs and/or encrypts the stream as
+ * parametrized in the PgpSignEncryptInput object. It returns its status
+ * and a possible detached signature as a SignEncryptResult.
+ *
+ * For a high-level operation based on URIs, see SignEncryptOperation.
+ *
+ * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
+ * @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
+ * @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
+ *
+ */
+public class PgpSignEncryptOperation extends BaseOperation {
+
+ private static byte[] NEW_LINE;
+
+ static {
+ try {
+ NEW_LINE = "\r\n".getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(Constants.TAG, "UnsupportedEncodingException", e);
+ }
+ }
+
+ public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ /**
+ * Signs and/or encrypts data based on parameters of class
+ */
+ public PgpSignEncryptResult execute(PgpSignEncryptInput input,
+ InputData inputData, OutputStream outputStream) {
+
+ int indent = 0;
+ OperationLog log = new OperationLog();
+
+ log.add(LogType.MSG_PSE, indent);
+ indent += 1;
+
+ boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none;
+ boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0)
+ || input.getSymmetricPassphrase() != null);
+ boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED);
+
+ Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ + "\nenableEncryption:" + enableEncryption
+ + "\nenableCompression:" + enableCompression
+ + "\nenableAsciiArmorOutput:" + input.ismEnableAsciiArmorOutput());
+
+ // add additional key id to encryption ids (mostly to do self-encryption)
+ if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) {
+ input.setEncryptionMasterKeyIds(Arrays.copyOf(input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1));
+ input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] = input.getAdditionalEncryptId();
+ }
+
+ ArmoredOutputStream armorOut = null;
+ OutputStream out;
+ if (input.ismEnableAsciiArmorOutput()) {
+ armorOut = new ArmoredOutputStream(outputStream);
+ if (input.getVersionHeader() != null) {
+ armorOut.setHeader("Version", input.getVersionHeader());
+ }
+ // if we have a charset, put it in the header
+ if (input.getCharset() != null) {
+ armorOut.setHeader("Charset", input.getCharset());
+ }
+ out = armorOut;
+ } else {
+ out = outputStream;
+ }
+
+ /* Get keys for signature generation for later usage */
+ CanonicalizedSecretKey signingKey = null;
+ if (enableSignature) {
+
+ try {
+ // fetch the indicated master key id (the one whose name we sign in)
+ CanonicalizedSecretKeyRing signingKeyRing =
+ mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
+
+ long signKeyId;
+ // use specified signing subkey, or find the one to use
+ if (input.getSignatureSubKeyId() == null) {
+ signKeyId = signingKeyRing.getSecretSignId();
+ } else {
+ signKeyId = input.getSignatureSubKeyId();
+ }
+
+ // fetch the specific subkey to sign with, or just use the master key if none specified
+ signingKey = signingKeyRing.getSecretKey(signKeyId);
+
+ } catch (ProviderHelper.NotFoundException | PgpGeneralException e) {
+ log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+
+ // Make sure we are allowed to sign here!
+ if (!signingKey.canSign()) {
+ log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+
+ // if no passphrase was explicitly set try to get it from the cache service
+ if (input.getSignaturePassphrase() == null) {
+ try {
+ // returns "" if key has no passphrase
+ input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId()));
+ // TODO
+// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
+ } catch (PassphraseCacheInterface.NoSecretKeyException e) {
+ // TODO
+// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+
+ // if passphrase was not cached, return here indicating that a passphrase is missing!
+ if (input.getSignaturePassphrase() == null) {
+ log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
+ PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
+ result.setKeyIdPassphraseNeeded(signingKey.getKeyId());
+ return result;
+ }
+ }
+
+ updateProgress(R.string.progress_extracting_signature_key, 0, 100);
+
+ try {
+ if (!signingKey.unlock(input.getSignaturePassphrase())) {
+ log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+
+ // check if hash algo is supported
+ int requestedAlgorithm = input.getSignatureHashAlgorithm();
+ LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms();
+ if (requestedAlgorithm == 0) {
+ // get most preferred
+ input.setSignatureHashAlgorithm(supported.getLast());
+ } else if (!supported.contains(requestedAlgorithm)) {
+ log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ }
+ updateProgress(R.string.progress_preparing_streams, 2, 100);
+
+ /* Initialize PGPEncryptedDataGenerator for later usage */
+ PGPEncryptedDataGenerator cPk = null;
+ if (enableEncryption) {
+ int algo = input.getSymmetricEncryptionAlgorithm();
+ if (algo == 0) {
+ algo = PGPEncryptedData.AES_128;
+ }
+ // has Integrity packet enabled!
+ JcePGPDataEncryptorBuilder encryptorBuilder =
+ new JcePGPDataEncryptorBuilder(algo)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
+ .setWithIntegrityPacket(true);
+
+ cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
+
+ if (input.getSymmetricPassphrase() != null) {
+ // Symmetric encryption
+ log.add(LogType.MSG_PSE_SYMMETRIC, indent);
+
+ JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
+ new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().toCharArray());
+ cPk.addMethod(symmetricEncryptionGenerator);
+ } else {
+ log.add(LogType.MSG_PSE_ASYMMETRIC, indent);
+
+ // Asymmetric encryption
+ for (long id : input.getEncryptionMasterKeyIds()) {
+ try {
+ CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingUri(id));
+ CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
+ cPk.addMethod(key.getPubKeyEncryptionGenerator());
+ log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(id));
+ } catch (PgpKeyNotFoundException e) {
+ log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(id));
+ if (input.ismFailOnMissingEncryptionKeyIds()) {
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ } catch (ProviderHelper.NotFoundException e) {
+ log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(id));
+ if (input.ismFailOnMissingEncryptionKeyIds()) {
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ }
+ }
+ }
+ }
+
+ /* Initialize signature generator object for later usage */
+ PGPSignatureGenerator signatureGenerator = null;
+ if (enableSignature) {
+ updateProgress(R.string.progress_preparing_signature, 4, 100);
+
+ try {
+ boolean cleartext = input.isCleartextSignature() && input.ismEnableAsciiArmorOutput() && !enableEncryption;
+ signatureGenerator = signingKey.getSignatureGenerator(
+ input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_PSE_ERROR_NFC, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ }
+
+ ProgressScaler progressScaler =
+ new ProgressScaler(mProgressable, 8, 95, 100);
+ PGPCompressedDataGenerator compressGen = null;
+ OutputStream pOut;
+ OutputStream encryptionOut = null;
+ BCPGOutputStream bcpgOut;
+
+ ByteArrayOutputStream detachedByteOut = null;
+ ArmoredOutputStream detachedArmorOut = null;
+ BCPGOutputStream detachedBcpgOut = null;
+
+ try {
+
+ if (enableEncryption) {
+ /* actual encryption */
+ updateProgress(R.string.progress_encrypting, 8, 100);
+ log.add(enableSignature
+ ? LogType.MSG_PSE_SIGCRYPTING
+ : LogType.MSG_PSE_ENCRYPTING,
+ indent
+ );
+ indent += 1;
+
+ encryptionOut = cPk.open(out, new byte[1 << 16]);
+
+ if (enableCompression) {
+ log.add(LogType.MSG_PSE_COMPRESSING, indent);
+ compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+ bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
+ } else {
+ bcpgOut = new BCPGOutputStream(encryptionOut);
+ }
+
+ if (enableSignature) {
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
+ }
+
+ PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
+ char literalDataFormatTag;
+ if (input.isCleartextSignature()) {
+ literalDataFormatTag = PGPLiteralData.UTF8;
+ } else {
+ literalDataFormatTag = PGPLiteralData.BINARY;
+ }
+ pOut = literalGen.open(bcpgOut, literalDataFormatTag,
+ inputData.getOriginalFilename(), new Date(), new byte[1 << 16]);
+
+ long alreadyWritten = 0;
+ int length;
+ byte[] buffer = new byte[1 << 16];
+ InputStream in = inputData.getInputStream();
+ while ((length = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, length);
+
+ // update signature buffer if signature is requested
+ if (enableSignature) {
+ signatureGenerator.update(buffer, 0, length);
+ }
+
+ alreadyWritten += length;
+ if (inputData.getSize() > 0) {
+ long progress = 100 * alreadyWritten / inputData.getSize();
+ progressScaler.setProgress((int) progress, 100);
+ }
+ }
+
+ literalGen.close();
+ indent -= 1;
+
+ } else if (enableSignature && input.isCleartextSignature() && input.ismEnableAsciiArmorOutput()) {
+ /* cleartext signature: sign-only of ascii text */
+
+ updateProgress(R.string.progress_signing, 8, 100);
+ log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent);
+
+ // write -----BEGIN PGP SIGNED MESSAGE-----
+ armorOut.beginClearText(input.getSignatureHashAlgorithm());
+
+ InputStream in = inputData.getInputStream();
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+
+ // update signature buffer with first line
+ processLine(reader.readLine(), armorOut, signatureGenerator);
+
+ // TODO: progress: fake annealing?
+ while (true) {
+ String line = reader.readLine();
+
+ // end cleartext signature with newline, see http://tools.ietf.org/html/rfc4880#section-7
+ if (line == null) {
+ armorOut.write(NEW_LINE);
+ break;
+ }
+
+ armorOut.write(NEW_LINE);
+
+ // update signature buffer with input line
+ signatureGenerator.update(NEW_LINE);
+ processLine(line, armorOut, signatureGenerator);
+ }
+
+ armorOut.endClearText();
+
+ pOut = new BCPGOutputStream(armorOut);
+ } else if (enableSignature && input.isDetachedSignature()) {
+ /* detached signature */
+
+ updateProgress(R.string.progress_signing, 8, 100);
+ log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);
+
+ InputStream in = inputData.getInputStream();
+
+ // handle output stream separately for detached signatures
+ detachedByteOut = new ByteArrayOutputStream();
+ OutputStream detachedOut = detachedByteOut;
+ if (input.ismEnableAsciiArmorOutput()) {
+ detachedArmorOut = new ArmoredOutputStream(detachedOut);
+ if (input.getVersionHeader() != null) {
+ detachedArmorOut.setHeader("Version", input.getVersionHeader());
+ }
+
+ detachedOut = detachedArmorOut;
+ }
+ detachedBcpgOut = new BCPGOutputStream(detachedOut);
+
+ long alreadyWritten = 0;
+ int length;
+ byte[] buffer = new byte[1 << 16];
+ while ((length = in.read(buffer)) > 0) {
+ // no output stream is written, no changed to original data!
+
+ signatureGenerator.update(buffer, 0, length);
+
+ alreadyWritten += length;
+ if (inputData.getSize() > 0) {
+ long progress = 100 * alreadyWritten / inputData.getSize();
+ progressScaler.setProgress((int) progress, 100);
+ }
+ }
+
+ pOut = null;
+ } else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) {
+ /* sign-only binary (files/data stream) */
+
+ updateProgress(R.string.progress_signing, 8, 100);
+ log.add(LogType.MSG_PSE_SIGNING, indent);
+
+ InputStream in = inputData.getInputStream();
+
+ if (enableCompression) {
+ compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+ bcpgOut = new BCPGOutputStream(compressGen.open(out));
+ } else {
+ bcpgOut = new BCPGOutputStream(out);
+ }
+
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
+
+ PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
+ pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY,
+ inputData.getOriginalFilename(), new Date(),
+ new byte[1 << 16]);
+
+ long alreadyWritten = 0;
+ int length;
+ byte[] buffer = new byte[1 << 16];
+ while ((length = in.read(buffer)) > 0) {
+ pOut.write(buffer, 0, length);
+
+ signatureGenerator.update(buffer, 0, length);
+
+ alreadyWritten += length;
+ if (inputData.getSize() > 0) {
+ long progress = 100 * alreadyWritten / inputData.getSize();
+ progressScaler.setProgress((int) progress, 100);
+ }
+ }
+
+ literalGen.close();
+ } else {
+ pOut = null;
+ // TODO: Is this log right?
+ log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent);
+ }
+
+ if (enableSignature) {
+ updateProgress(R.string.progress_generating_signature, 95, 100);
+ try {
+ if (detachedBcpgOut != null) {
+ signatureGenerator.generate().encode(detachedBcpgOut);
+ } else {
+ signatureGenerator.generate().encode(pOut);
+ }
+ } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
+ // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
+ log.add(LogType.MSG_PSE_PENDING_NFC, indent);
+ PgpSignEncryptResult result =
+ new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
+ // Note that the checked key here is the master key, not the signing key
+ // (although these are always the same on Yubikeys)
+ result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
+ Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
+ return result;
+ }
+ }
+
+ // closing outputs
+ // NOTE: closing needs to be done in the correct order!
+ if (encryptionOut != null) {
+ if (compressGen != null) {
+ compressGen.close();
+ }
+
+ encryptionOut.close();
+ }
+ // Note: Closing ArmoredOutputStream does not close the underlying stream
+ if (armorOut != null) {
+ armorOut.close();
+ }
+ // Note: Closing ArmoredOutputStream does not close the underlying stream
+ if (detachedArmorOut != null) {
+ detachedArmorOut.close();
+ }
+ // Also closes detachedBcpgOut
+ if (detachedByteOut != null) {
+ detachedByteOut.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ if (outputStream != null) {
+ outputStream.close();
+ }
+
+ } catch (SignatureException e) {
+ log.add(LogType.MSG_PSE_ERROR_SIG, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ } catch (PGPException e) {
+ log.add(LogType.MSG_PSE_ERROR_PGP, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ } catch (IOException e) {
+ log.add(LogType.MSG_PSE_ERROR_IO, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ log.add(LogType.MSG_PSE_OK, indent);
+ PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
+ if (detachedByteOut != null) {
+ try {
+ detachedByteOut.flush();
+ detachedByteOut.close();
+ } catch (IOException e) {
+ // silently catch
+ }
+ result.setDetachedSignature(detachedByteOut.toByteArray());
+ }
+ return result;
+ }
+
+ /**
+ * Remove whitespaces on line endings
+ */
+ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
+ final PGPSignatureGenerator pSignatureGenerator)
+ throws IOException, SignatureException {
+
+ if (pLine == null) {
+ return;
+ }
+
+ final char[] chars = pLine.toCharArray();
+ int len = chars.length;
+
+ while (len > 0) {
+ if (!Character.isWhitespace(chars[len - 1])) {
+ break;
+ }
+ len--;
+ }
+
+ final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
+
+ if (pArmoredOutput != null) {
+ pArmoredOutput.write(data);
+ }
+ pSignatureGenerator.update(data);
+ }
+
+}