@NonNull @Override public InputDataResult execute(InputDataParcel input, final CryptoInputParcel cryptoInput) { final OperationLog log = new OperationLog(); log.add(LogType.MSG_DATA, 0); Uri currentInputUri; DecryptVerifyResult decryptResult = null; PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput(); if (!input.getMimeDecode() && decryptInput == null) { throw new AssertionError("no decryption or mime decoding, this is probably a bug"); } if (decryptInput != null) { log.add(LogType.MSG_DATA_OPENPGP, 1); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); decryptInput.setInputUri(input.getInputUri()); currentInputUri = TemporaryFileProvider.createFile(mContext); decryptInput.setOutputUri(currentInputUri); decryptResult = op.execute(decryptInput, cryptoInput); if (decryptResult.isPending()) { return new InputDataResult(log, decryptResult); } log.addByMerge(decryptResult, 1); if (!decryptResult.success()) { return new InputDataResult(InputDataResult.RESULT_ERROR, log); } // inform the storage provider about the mime type for this uri if (decryptResult.getDecryptionMetadata() != null) { OpenPgpMetadata meta = decryptResult.getDecryptionMetadata(); TemporaryFileProvider.setName(mContext, currentInputUri, meta.getFilename()); TemporaryFileProvider.setMimeType(mContext, currentInputUri, meta.getMimeType()); } } else { currentInputUri = input.getInputUri(); } // don't even attempt if we know the data isn't suitable for mime content, or if we have a // filename boolean skipMimeParsing = false; if (decryptResult != null && decryptResult.getDecryptionMetadata() != null) { OpenPgpMetadata metadata = decryptResult.getDecryptionMetadata(); String fileName = metadata.getFilename(); String contentType = metadata.getMimeType(); if (!TextUtils.isEmpty(fileName) || contentType != null && !contentType.startsWith("multipart/") && !contentType.startsWith("text/") && !"application/octet-stream".equals(contentType)) { skipMimeParsing = true; } } // If we aren't supposed to attempt mime decode after decryption, we are done here if (skipMimeParsing || !input.getMimeDecode()) { log.add(LogType.MSG_DATA_SKIP_MIME, 1); ArrayList<Uri> uris = new ArrayList<>(); uris.add(currentInputUri); ArrayList<OpenPgpMetadata> metadatas = new ArrayList<>(); metadatas.add(decryptResult.getDecryptionMetadata()); log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, uris, metadatas); } final MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); final ArrayList<Uri> outputUris = new ArrayList<>(); final ArrayList<OpenPgpMetadata> metadatas = new ArrayList<>(); parser.setContentDecoding(true); parser.setRecurse(); parser.setContentHandler( new AbstractContentHandler() { private boolean mFoundContentTypeHeader = false; private Uri uncheckedSignedDataUri; String mFilename; @Override public void startMultipart(BodyDescriptor bd) throws MimeException { if ("signed".equals(bd.getSubType())) { if (mSignedDataUri != null) { // recursive signed data is not supported, and will just be parsed as-is log.add(LogType.MSG_DATA_DETACHED_NESTED, 2); return; } log.add(LogType.MSG_DATA_DETACHED, 2); if (!outputUris.isEmpty()) { // we can't have previous data if we parse a detached signature! log.add(LogType.MSG_DATA_DETACHED_CLEAR, 3); outputUris.clear(); metadatas.clear(); } // this is signed data, we require the next part raw parser.setRaw(); } } @Override public void raw(InputStream is) throws MimeException, IOException { if (uncheckedSignedDataUri != null) { throw new AssertionError( "raw parts must only be received as first part of multipart/signed!"); } log.add(LogType.MSG_DATA_DETACHED_RAW, 3); uncheckedSignedDataUri = TemporaryFileProvider.createFile(mContext, mFilename, "text/plain"); OutputStream out = mContext.getContentResolver().openOutputStream(uncheckedSignedDataUri, "w"); if (out == null) { throw new IOException("Error getting file for writing!"); } int len; while ((len = is.read(buf)) > 0) { out.write(buf, 0, len); } out.close(); // continue to next body part the usual way parser.setFlat(); } @Override public void startHeader() throws MimeException { mFilename = null; } @Override public void endHeader() throws MimeException { if (!mFoundContentTypeHeader) { parser.stop(); } } @Override public void field(Field field) throws MimeException { field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT); if (field instanceof ContentDispositionField) { mFilename = ((ContentDispositionField) field).getFilename(); } if (field instanceof ContentTypeField) { mFoundContentTypeHeader = true; } } private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException { if (!"application/pgp-signature".equals(bd.getMimeType())) { log.add(LogType.MSG_DATA_DETACHED_UNSUPPORTED, 3); uncheckedSignedDataUri = null; parser.setRecurse(); return; } log.add(LogType.MSG_DATA_DETACHED_SIG, 3); ByteArrayOutputStream detachedSig = new ByteArrayOutputStream(); int len, totalLength = 0; while ((len = is.read(buf)) > 0) { totalLength += len; detachedSig.write(buf, 0, len); if (totalLength > 4096) { throw new IOException("detached signature is unreasonably large!"); } } detachedSig.close(); PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel(); decryptInput.setInputUri(uncheckedSignedDataUri); decryptInput.setDetachedSignature(detachedSig.toByteArray()); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); DecryptVerifyResult verifyResult = op.execute(decryptInput, cryptoInput); log.addByMerge(verifyResult, 4); mSignedDataUri = uncheckedSignedDataUri; mSignedDataResult = verifyResult; // reset parser state uncheckedSignedDataUri = null; parser.setRecurse(); } @Override public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException { // if we have signed data waiting, we expect a signature for checking if (uncheckedSignedDataUri != null) { bodySignature(bd, is); return; } // we read first, no need to create an output file if nothing was read! int len = is.read(buf); if (len < 0) { return; } // If mSignedDataUri is non-null, we already parsed a signature. If mSignedDataResult is // non-null // too, we are still in the same parsing stage, so this is trailing data - skip it! if (mSignedDataUri != null && mSignedDataResult != null) { log.add(LogType.MSG_DATA_DETACHED_TRAILING, 2); return; } log.add(LogType.MSG_DATA_MIME_PART, 2); String mimeType = bd.getMimeType(); if (mFilename != null) { log.add(LogType.MSG_DATA_MIME_FILENAME, 3, mFilename); boolean isGenericMimeType = ClipDescription.compareMimeTypes(mimeType, "application/octet-stream") || ClipDescription.compareMimeTypes(mimeType, "application/x-download"); if (isGenericMimeType) { String extension = MimeTypeMap.getFileExtensionFromUrl(mFilename); String extMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (extMimeType != null) { mimeType = extMimeType; log.add(LogType.MSG_DATA_MIME_FROM_EXTENSION, 3); } } } log.add(LogType.MSG_DATA_MIME_TYPE, 3, mimeType); Uri uri = TemporaryFileProvider.createFile(mContext, mFilename, mimeType); OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w"); if (out == null) { throw new IOException("Error getting file for writing!"); } // If this data looks like text, we pipe the incoming data into a charset // decoder, to see if the data is legal for the assumed charset. String charset = bd.getCharset(); CharsetVerifier charsetVerifier = new CharsetVerifier(buf, mimeType, charset); int totalLength = 0; do { totalLength += len; out.write(buf, 0, len); charsetVerifier.readBytesFromBuffer(0, len); } while ((len = is.read(buf)) > 0); log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength)); OpenPgpMetadata metadata; if (charsetVerifier.isDefinitelyBinary()) { metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength); } else { if (charsetVerifier.isCharsetFaulty() && charsetVerifier.isCharsetGuessed()) { log.add( LogType.MSG_DATA_MIME_CHARSET_UNKNOWN, 3, charsetVerifier.getMaybeFaultyCharset()); } else if (charsetVerifier.isCharsetFaulty()) { log.add(LogType.MSG_DATA_MIME_CHARSET_FAULTY, 3, charsetVerifier.getCharset()); } else if (charsetVerifier.isCharsetGuessed()) { log.add(LogType.MSG_DATA_MIME_CHARSET_GUESS, 3, charsetVerifier.getCharset()); } else { log.add(LogType.MSG_DATA_MIME_CHARSET, 3, charsetVerifier.getCharset()); } metadata = new OpenPgpMetadata( mFilename, charsetVerifier.getGuessedMimeType(), 0L, totalLength, charsetVerifier.getCharset()); } out.close(); outputUris.add(uri); metadatas.add(metadata); } }); try { log.add(LogType.MSG_DATA_MIME, 1); try { // open current uri for input InputStream in = mContext.getContentResolver().openInputStream(currentInputUri); parser.parse(in); if (mSignedDataUri != null) { if (decryptResult != null) { decryptResult.setSignatureResult(mSignedDataResult.getSignatureResult()); } else { decryptResult = mSignedDataResult; } // the actual content is the signed data now (and will be passed verbatim, if parsing // fails) currentInputUri = mSignedDataUri; in = mContext.getContentResolver().openInputStream(currentInputUri); // reset signed data result, to indicate to the parser that it is in the inner part mSignedDataResult = null; parser.parse(in); } } catch (MimeException e) { // a mime error likely means that this wasn't mime data, after all e.printStackTrace(); log.add(LogType.MSG_DATA_MIME_BAD, 2); } // if we found data, return success if (!outputUris.isEmpty()) { log.add(LogType.MSG_DATA_MIME_OK, 2); log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult( InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); } // if no mime data parsed, just return the raw data as fallback log.add(LogType.MSG_DATA_MIME_NONE, 2); OpenPgpMetadata metadata; if (decryptResult != null) { metadata = decryptResult.getDecryptionMetadata(); } else { // if we neither decrypted nor mime-decoded, should this be treated as an error? // either way, we know nothing about the data metadata = new OpenPgpMetadata(); } outputUris.add(currentInputUri); metadatas.add(metadata); log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult( InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); } catch (FileNotFoundException e) { log.add(LogType.MSG_DATA_ERROR_IO, 2); return new InputDataResult(InputDataResult.RESULT_ERROR, log); } catch (IOException e) { e.printStackTrace(); log.add(LogType.MSG_DATA_ERROR_IO, 2); return new InputDataResult(InputDataResult.RESULT_ERROR, log); } }
/** Signs and/or encrypts data based on parameters of class */ public PgpSignEncryptResult execute( PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput, 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.isEnableAsciiArmorOutput() + "\nisHiddenRecipients:" + input.isHiddenRecipients()); // 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.isEnableAsciiArmorOutput()) { armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16)); 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) { updateProgress(R.string.progress_extracting_signature_key, 0, 100); try { // fetch the indicated master key id (the one whose name we sign in) CanonicalizedSecretKeyRing signingKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId()); // fetch the specific subkey to sign with, or just use the master key if none specified signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId()); // 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); } switch (signingKey.getSecretKeyType()) { case DIVERT_TO_CARD: case PASSPHRASE_EMPTY: { if (!signingKey.unlock(new Passphrase())) { throw new AssertionError( "PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase." + " This is a programming error!"); } break; } case PIN: case PATTERN: case PASSPHRASE: { Passphrase localPassphrase = cryptoInput.getPassphrase(); if (localPassphrase == null) { try { localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId()); } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { } } if (localPassphrase == null) { log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); return new PgpSignEncryptResult( log, RequiredInputParcel.createRequiredSignPassphrase( signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), cryptoInput.getSignatureTime()), cryptoInput); } if (!signingKey.unlock(localPassphrase)) { log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } break; } case GNU_DUMMY: { log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } default: { throw new AssertionError("Unhandled SecretKeyType! (should not happen)"); } } } catch (ProviderHelper.NotFoundException e) { log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, 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); } // Use preferred hash algo int requestedAlgorithm = input.getSignatureHashAlgorithm(); ArrayList<Integer> supported = signingKey.getSupportedHashAlgorithms(); if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) { // get most preferred input.setSignatureHashAlgorithm(supported.get(0)); } 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) { // Use preferred encryption algo int algo = input.getSymmetricEncryptionAlgorithm(); if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) { // get most preferred // TODO: get from recipients algo = PgpConstants.sPreferredSymmetricAlgorithms.get(0); } // 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().getCharArray()); 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)); Set<Long> encryptSubKeyIds = keyRing.getEncryptIds(); for (Long subKeyId : encryptSubKeyIds) { CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId); cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients())); log.add( LogType.MSG_PSE_KEY_OK, indent + 1, KeyFormattingUtils.convertKeyIdToHex(subKeyId)); } if (encryptSubKeyIds.isEmpty()) { log.add( LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); if (input.isFailOnMissingEncryptionKeyIds()) { 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.isFailOnMissingEncryptionKeyIds()) { 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.isEnableAsciiArmorOutput() && !enableEncryption; signatureGenerator = signingKey.getDataSignatureGenerator( input.getSignatureHashAlgorithm(), cleartext, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime()); } 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.isEnableAsciiArmorOutput()) { /* 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.isEnableAsciiArmorOutput()) { detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16)); 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); return new PgpSignEncryptResult( log, RequiredInputParcel.createNfcSignOperation( signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput); } } // 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; }