Example usage for org.bouncycastle.openpgp PGPLiteralData UTF8

List of usage examples for org.bouncycastle.openpgp PGPLiteralData UTF8

Introduction

In this page you can find the example usage for org.bouncycastle.openpgp PGPLiteralData UTF8.

Prototype

char UTF8

To view the source code for org.bouncycastle.openpgp PGPLiteralData UTF8.

Click Source Link

Document

Format tag for UTF-8 encoded textual literal data

Usage

From source file:org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation.java

License:Open Source License

/** Decrypt and/or verify binary or ascii armored pgp data. */
@NonNull//from   w  w w . j  a  v a  2  s. c  o m
private DecryptVerifyResult decryptVerify(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
        InputData inputData, InputStream in, OutputStream out, int indent) throws IOException, PGPException {

    OperationLog log = new OperationLog();

    log.add(LogType.MSG_DC, indent);
    indent += 1;

    updateProgress(R.string.progress_reading_data, 0, 100);

    // parse ASCII Armor headers
    ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent);
    String charset = armorHeaders.charset;
    boolean useBackupCode = false;
    if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 2) {
        useBackupCode = true;
    }

    OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();

    JcaSkipMarkerPGPObjectFactory plainFact;
    Object dataChunk;
    EncryptStreamResult esResult = null;
    { // resolve encrypted (symmetric and asymmetric) packets
        JcaSkipMarkerPGPObjectFactory pgpF = new JcaSkipMarkerPGPObjectFactory(in);
        Object obj = pgpF.nextObject();

        if (obj instanceof PGPEncryptedDataList) {
            esResult = handleEncryptedPacket(input, cryptoInput, (PGPEncryptedDataList) obj, log, indent,
                    useBackupCode);

            // if there is an error, nothing left to do here
            if (esResult.errorResult != null) {
                return esResult.errorResult;
            }

            // if this worked out so far, the data is encrypted
            decryptionResultBuilder.setEncrypted(true);
            if (esResult.sessionKey != null && esResult.decryptedSessionKey != null) {
                decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey);
            }

            if (esResult.insecureEncryptionKey) {
                log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
                decryptionResultBuilder.setInsecure(true);
            }

            // Check for insecure encryption algorithms!
            if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo)) {
                log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
                decryptionResultBuilder.setInsecure(true);
            }

            plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
            dataChunk = plainFact.nextObject();

        } else {
            decryptionResultBuilder.setEncrypted(false);

            plainFact = pgpF;
            dataChunk = obj;
        }

    }

    log.add(LogType.MSG_DC_PREP_STREAMS, indent);

    log.add(LogType.MSG_DC_CLEAR, indent);
    indent += 1;

    // resolve compressed data
    if (dataChunk instanceof PGPCompressedData) {
        log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);

        PGPCompressedData compressedData = (PGPCompressedData) dataChunk;

        JcaSkipMarkerPGPObjectFactory fact = new JcaSkipMarkerPGPObjectFactory(compressedData.getDataStream());
        dataChunk = fact.nextObject();
        plainFact = fact;
    }

    PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper, input.getSenderAddress());
    if (signatureChecker.initializeOnePassSignature(dataChunk, log, indent + 1)) {
        dataChunk = plainFact.nextObject();
    }

    if (dataChunk instanceof PGPSignatureList) {
        // skip
        dataChunk = plainFact.nextObject();
    }

    OpenPgpMetadata metadata;

    if (!(dataChunk instanceof PGPLiteralData)) {

        log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent);
        return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);

    }

    log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
    indent += 2;

    PGPLiteralData literalData = (PGPLiteralData) dataChunk;

    String originalFilename = literalData.getFileName();
    // reject filenames with slashes completely (path traversal issue)
    if (originalFilename.contains("/")) {
        originalFilename = "";
    }
    String mimeType = null;
    if (literalData.getFormat() == PGPLiteralData.TEXT || literalData.getFormat() == PGPLiteralData.UTF8) {
        mimeType = "text/plain";
    } else {
        // try to guess from file ending
        String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
        if (extension != null) {
            MimeTypeMap mime = MimeTypeMap.getSingleton();
            mimeType = mime.getMimeTypeFromExtension(extension);
        }
    }
    if (mimeType == null) {
        mimeType = "application/octet-stream";
    }

    if (!"".equals(originalFilename)) {
        log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
    }
    log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
            new Date(literalData.getModificationTime().getTime()).toString());

    // return here if we want to decrypt the metadata only
    if (input.isDecryptMetadataOnly()) {

        log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);

        // this operation skips the entire stream to find the data length!
        Long originalSize = literalData.findDataLength();

        if (originalSize != null) {
            log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, Long.toString(originalSize));
        } else {
            log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
        }

        metadata = new OpenPgpMetadata(originalFilename, mimeType, literalData.getModificationTime().getTime(),
                originalSize == null ? 0 : originalSize, charset);

        log.add(LogType.MSG_DC_OK_META_ONLY, indent);
        DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
        result.setDecryptionMetadata(metadata);
        return result;
    }

    InputStream dataIn = literalData.getInputStream();

    long opTime, startTime = System.currentTimeMillis();

    long alreadyWritten = 0;
    long wholeSize = inputData.getSize() - inputData.getStreamPosition();
    boolean sizeIsKnown = inputData.getSize() != InputData.UNKNOWN_FILESIZE && wholeSize > 0;
    int length;
    byte[] buffer = new byte[8192];
    byte[] firstBytes = new byte[48];
    CharsetVerifier charsetVerifier = new CharsetVerifier(buffer, mimeType, charset);

    updateProgress(R.string.progress_decrypting, 1, 100);

    long nextProgressTime = 0L;
    int lastReportedProgress = 1;
    while ((length = dataIn.read(buffer)) > 0) {
        // Log.d(Constants.TAG, "read bytes: " + length);
        if (out != null) {
            out.write(buffer, 0, length);
        }

        // update signature buffer if signature is also present
        signatureChecker.updateSignatureData(buffer, 0, length);

        charsetVerifier.readBytesFromBuffer(0, length);

        // note down first couple of bytes for "magic bytes" file type detection
        if (alreadyWritten == 0) {
            System.arraycopy(buffer, 0, firstBytes, 0, length > firstBytes.length ? firstBytes.length : length);
        }

        alreadyWritten += length;
        if (sizeIsKnown && nextProgressTime < System.currentTimeMillis()) {
            long progress = 100 * inputData.getStreamPosition() / wholeSize;
            // stop at 100% for wrong file sizes...
            if (progress > 100) {
                progress = 100;
            }
            if (progress > lastReportedProgress) {
                updateProgress((int) progress, 100);
                lastReportedProgress = (int) progress;
                nextProgressTime = System.currentTimeMillis() + PROGRESS_STRIDE_MILLISECONDS;
            }
        }
    }

    if (signatureChecker.isInitialized()) {

        Object o = plainFact.nextObject();
        boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1);

        if (!signatureCheckOk) {
            return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
        }

    }

    opTime = System.currentTimeMillis() - startTime;
    Log.d(Constants.TAG, "decrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s, for "
            + alreadyWritten + " bytes");

    // special treatment to detect pgp mime types
    // TODO move into CharsetVerifier? seems like that would be a plausible place for this logic
    if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----")
            || matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) {
        mimeType = Constants.MIME_TYPE_KEYS;
    } else if (matchesPrefix(firstBytes, "-----BEGIN PGP MESSAGE-----")) {
        // this is NOT application/pgp-encrypted, see RFC 3156!
        mimeType = Constants.MIME_TYPE_ENCRYPTED_ALTERNATE;
    } else {
        mimeType = charsetVerifier.getGuessedMimeType();
    }
    metadata = new OpenPgpMetadata(originalFilename, mimeType, literalData.getModificationTime().getTime(),
            alreadyWritten, charsetVerifier.getCharset());

    log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);
    Log.d(Constants.TAG, metadata.toString());

    indent -= 1;

    if (esResult != null) {
        if (esResult.encryptedData.isIntegrityProtected()) {
            if (esResult.encryptedData.verify()) {
                log.add(LogType.MSG_DC_INTEGRITY_CHECK_OK, indent);
            } else {
                log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);
                return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
            }
        } else if (!signatureChecker.isInitialized()) {
            // If no signature is present, we *require* an MDC!
            // Handle missing integrity protection like failed integrity protection!
            // The MDC packet can be stripped by an attacker!
            log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
            decryptionResultBuilder.setInsecure(true);
        }
    }

    updateProgress(R.string.progress_done, 100, 100);

    log.add(LogType.MSG_DC_OK, indent);

    // Return a positive result, with metadata and verification info
    DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);

    result.setCachedCryptoInputParcel(cryptoInput);
    result.setSignatureResult(signatureChecker.getSignatureResult());
    result.setDecryptionResult(decryptionResultBuilder.build());
    result.setDecryptionMetadata(metadata);
    result.mOperationTime = opTime;

    return result;

}

From source file:org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation.java

License:Open Source License

/**
 * Signs and/or encrypts data based on parameters of class
 *///from   w  ww  . ja v a 2  s  . c om
private PgpSignEncryptResult executeInternal(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
        InputData inputData, OutputStream outputStream) {

    int indent = 0;
    OperationLog log = new OperationLog();

    log.add(LogType.MSG_PSE, indent);
    indent += 1;

    PgpSignEncryptData data = input.getData();
    boolean enableSignature = data.getSignatureMasterKeyId() != Constants.key.none;
    boolean enableEncryption = ((data.getEncryptionMasterKeyIds() != null
            && data.getEncryptionMasterKeyIds().length > 0) || data.getSymmetricPassphrase() != null);
    boolean enableCompression = (data.getCompressionAlgorithm() != CompressionAlgorithmTags.UNCOMPRESSED);

    Log.d(Constants.TAG,
            "enableSignature:" + enableSignature + "\nenableEncryption:" + enableEncryption
                    + "\nenableCompression:" + enableCompression + "\nenableAsciiArmorOutput:"
                    + data.isEnableAsciiArmorOutput() + "\nisHiddenRecipients:" + data.isHiddenRecipients());

    // add additional key id to encryption ids (mostly to do self-encryption)
    if (enableEncryption && data.getAdditionalEncryptId() != Constants.key.none) {
        data.setEncryptionMasterKeyIds(
                Arrays.copyOf(data.getEncryptionMasterKeyIds(), data.getEncryptionMasterKeyIds().length + 1));
        data.getEncryptionMasterKeyIds()[data.getEncryptionMasterKeyIds().length - 1] = data
                .getAdditionalEncryptId();
    }

    ArmoredOutputStream armorOut = null;
    OutputStream out;
    if (data.isEnableAsciiArmorOutput()) {
        armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
        if (data.getVersionHeader() != null) {
            armorOut.setHeader("Version", data.getVersionHeader());
        }
        // if we have a charset, put it in the header
        if (data.getCharset() != null) {
            armorOut.setHeader("Charset", data.getCharset());
        }
        // add proprietary header to indicate that this is a key backup
        if (data.isAddBackupHeader()) {
            armorOut.setHeader("BackupVersion", "2");
        }
        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 {
            long signingMasterKeyId = data.getSignatureMasterKeyId();
            long signingSubKeyId = data.getSignatureSubKeyId();

            CanonicalizedSecretKeyRing signingKeyRing = mProviderHelper
                    .getCanonicalizedSecretKeyRing(signingMasterKeyId);
            signingKey = signingKeyRing.getSecretKey(data.getSignatureSubKeyId());

            // Make sure key is not expired or revoked
            if (signingKeyRing.isExpired() || signingKeyRing.isRevoked() || signingKey.isExpired()
                    || signingKey.isRevoked()) {
                log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, 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);
            }

            switch (mProviderHelper.getCachedPublicKeyRing(signingMasterKeyId)
                    .getSecretKeyType(signingSubKeyId)) {
            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(signingMasterKeyId, signingKey.getKeyId());
                    } catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
                    }
                }
                if (localPassphrase == null) {
                    log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
                    return new PgpSignEncryptResult(log,
                            RequiredInputParcel.createRequiredSignPassphrase(signingMasterKeyId,
                                    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 requested hash algo
        int requestedAlgorithm = data.getSignatureHashAlgorithm();
        if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
            data.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM);
        }
    }
    updateProgress(R.string.progress_preparing_streams, 2, 100);

    /* Initialize PGPEncryptedDataGenerator for later usage */
    PGPEncryptedDataGenerator cPk = null;
    if (enableEncryption) {

        // Use requested encryption algo
        int algo = data.getSymmetricEncryptionAlgorithm();
        if (algo == PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
            algo = PgpSecurityConstants.DEFAULT_SYMMETRIC_ALGORITHM;
        }
        JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(algo)
                .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
                .setWithIntegrityPacket(data.isIntegrityProtected());

        cPk = new PGPEncryptedDataGenerator(encryptorBuilder);

        if (data.getSymmetricPassphrase() != null) {
            // Symmetric encryption
            log.add(LogType.MSG_PSE_SYMMETRIC, indent);

            JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = new JcePBEKeyEncryptionMethodGenerator(
                    data.getSymmetricPassphrase().getCharArray());
            cPk.addMethod(symmetricEncryptionGenerator);
        } else {
            log.add(LogType.MSG_PSE_ASYMMETRIC, indent);

            // Asymmetric encryption
            for (long id : data.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(data.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));
                        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                    }
                    // Make sure key is not expired or revoked
                    if (keyRing.isExpired() || keyRing.isRevoked()) {
                        log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent);
                        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
                    }
                } catch (ProviderHelper.NotFoundException e) {
                    log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id));
                    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 = data.isCleartextSignature() && data.isEnableAsciiArmorOutput()
                    && !enableEncryption;
            signatureGenerator = signingKey.getDataSignatureGenerator(data.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;

    long opTime, startTime = System.currentTimeMillis();

    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);

                // Use preferred compression algo
                int algo = data.getCompressionAlgorithm();
                if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
                    algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
                }
                compressGen = new PGPCompressedDataGenerator(algo);
                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 (data.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 = new BufferedInputStream(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 && data.isCleartextSignature() && data.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(data.getSignatureHashAlgorithm());

            InputStream in = new BufferedInputStream(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 && data.isDetachedSignature()) {
            /* detached signature */

            updateProgress(R.string.progress_signing, 8, 100);
            log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);

            InputStream in = new BufferedInputStream(inputData.getInputStream());

            // handle output stream separately for detached signatures
            detachedByteOut = new ByteArrayOutputStream();
            OutputStream detachedOut = detachedByteOut;
            if (data.isEnableAsciiArmorOutput()) {
                detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
                if (data.getVersionHeader() != null) {
                    detachedArmorOut.setHeader("Version", data.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 && !data.isCleartextSignature() && !data.isDetachedSignature()) {
            /* sign-only binary (files/data stream) */

            updateProgress(R.string.progress_signing, 8, 100);
            log.add(LogType.MSG_PSE_SIGNING, indent);

            InputStream in = new BufferedInputStream(inputData.getInputStream());

            if (enableCompression) {
                // Use preferred compression algo
                int algo = data.getCompressionAlgorithm();
                if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
                    algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
                }

                compressGen = new PGPCompressedDataGenerator(algo);
                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 {
            throw new AssertionError("cannot clearsign in non-ascii armored text, this is a bug!");
        }

        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.createSecurityTokenSignOperation(
                                signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), e.hashToSign,
                                e.hashAlgo, cryptoInput.getSignatureTime()),
                        cryptoInput);
            }
        }

        opTime = System.currentTimeMillis() - startTime;
        Log.d(Constants.TAG, "sign/encrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s");

        // 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);
    result.mOperationTime = opTime;
    if (detachedByteOut != null) {
        try {
            detachedByteOut.flush();
            detachedByteOut.close();
        } catch (IOException e) {
            // silently catch
        }
        result.setDetachedSignature(detachedByteOut.toByteArray());
        try {
            String digestName = PGPUtil.getDigestName(data.getSignatureHashAlgorithm());
            // construct micalg parameter according to https://tools.ietf.org/html/rfc3156#section-5
            result.setMicAlgDigestName("pgp-" + digestName.toLowerCase());
        } catch (PGPException e) {
            Log.e(Constants.TAG, "error setting micalg parameter!", e);
        }
    }
    return result;
}