diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java deleted file mode 100644 index 66ad1edd..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ /dev/null @@ -1,1139 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.Stack; -import javax.annotation.Nonnull; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.bcpg.BCPGInputStream; -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.bcpg.UnsupportedPacketVersionException; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPPBEEncryptedData; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.bouncycastle.util.io.TeeInputStream; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.OpenPgpPacket; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil; -import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; -import org.pgpainless.decryption_verification.syntax_check.InputSymbol; -import org.pgpainless.decryption_verification.syntax_check.PDA; -import org.pgpainless.decryption_verification.syntax_check.StackSymbol; -import org.pgpainless.exception.MalformedOpenPgpMessageException; -import org.pgpainless.exception.MessageNotIntegrityProtectedException; -import org.pgpainless.exception.MissingDecryptionMethodException; -import org.pgpainless.exception.MissingPassphraseException; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.exception.UnacceptableAlgorithmException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.key.util.KeyIdUtil; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.consumer.CertificateValidator; -import org.pgpainless.signature.consumer.OnePassSignatureCheck; -import org.pgpainless.signature.consumer.SignatureCheck; -import org.pgpainless.signature.consumer.SignatureValidator; -import org.pgpainless.util.ArmoredInputStreamFactory; -import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SessionKey; -import org.pgpainless.util.Tuple; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenPgpMessageInputStream extends DecryptionStream { - - private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class); - - // Options to consume the data - protected final ConsumerOptions options; - - private final Policy policy; - // Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message - protected final PDA syntaxVerifier = new PDA(); - // InputStream of OpenPGP packets - protected TeeBCPGInputStream packetInputStream; - // InputStream of a data packet containing nested data - protected InputStream nestedInputStream; - - private boolean closed = false; - - private final Signatures signatures; - private final MessageMetadata.Layer metadata; - - /** - * Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of - * OpenPGP messages and signatures. - * This constructor will use the global PGPainless {@link Policy}. - * - * @param inputStream underlying input stream - * @param options options for consuming the stream - * @return input stream that consumes OpenPGP messages - * - * @throws IOException in case of an IO error - * @throws PGPException in case of an OpenPGP error - */ - public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options) - throws IOException, PGPException { - return create(inputStream, options, PGPainless.getPolicy()); - } - - /** - * Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of - * OpenPGP messages and signatures. - * This factory method takes a custom {@link Policy} instead of using the global policy object. - * - * @param inputStream underlying input stream containing the OpenPGP message - * @param options options for consuming the message - * @param policy policy for acceptable algorithms etc. - * @return input stream that consumes OpenPGP messages - * - * @throws PGPException in case of an OpenPGP error - * @throws IOException in case of an IO error - */ - public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull Policy policy) - throws PGPException, IOException { - return create(inputStream, options, new MessageMetadata.Message(), policy); - } - - protected static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) - throws IOException, PGPException { - OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream); - openPgpIn.reset(); - - if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) { - return new OpenPgpMessageInputStream(Type.non_openpgp, - openPgpIn, options, metadata, policy); - } - - if (openPgpIn.isBinaryOpenPgp()) { - // Simply consume OpenPGP message - return new OpenPgpMessageInputStream(Type.standard, - openPgpIn, options, metadata, policy); - } - - if (openPgpIn.isAsciiArmored()) { - ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn); - if (armorIn.isClearText()) { - ((MessageMetadata.Message) metadata).cleartextSigned = true; - return new OpenPgpMessageInputStream(Type.cleartext_signed, - armorIn, options, metadata, policy); - } else { - // Simply consume dearmored OpenPGP message - return new OpenPgpMessageInputStream(Type.standard, - armorIn, options, metadata, policy); - } - } else { - throw new AssertionError("Cannot deduce type of data."); - } - } - - protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) - throws PGPException, IOException { - super(); - - this.policy = policy; - this.options = options; - this.metadata = metadata; - this.signatures = new Signatures(options); - - // Add detached signatures only on the outermost OpenPgpMessageInputStream - if (metadata instanceof MessageMetadata.Message) { - this.signatures.addDetachedSignatures(options.getDetachedSignatures()); - } - - // tee out packet bytes for signature verification - packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures); - - // *omnomnom* - consumePackets(); - } - - enum Type { - standard, - cleartext_signed, - non_openpgp - } - - protected OpenPgpMessageInputStream(@Nonnull Type type, - @Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) throws PGPException, IOException { - super(); - this.policy = policy; - this.options = options; - this.metadata = metadata; - this.signatures = new Signatures(options); - - if (metadata instanceof MessageMetadata.Message) { - this.signatures.addDetachedSignatures(options.getDetachedSignatures()); - } - - switch (type) { - - // Binary OpenPGP Message - case standard: - // tee out packet bytes for signature verification - packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), this.signatures); - - // *omnomnom* - consumePackets(); - break; - - // Cleartext Signature Framework (probably signed message) - case cleartext_signed: - MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy(); - PGPSignatureList detachedSignatures = ClearsignedMessageUtil - .detachSignaturesFromInbandClearsignedMessage( - inputStream, multiPassStrategy.getMessageOutputStream()); - - for (PGPSignature signature : detachedSignatures) { - signatures.addDetachedSignature(signature); - } - - options.forceNonOpenPgpData(); - nestedInputStream = new TeeInputStream(multiPassStrategy.getMessageInputStream(), this.signatures); - break; - - // Non-OpenPGP Data (e.g. detached signature verification) - case non_openpgp: - packetInputStream = null; - nestedInputStream = new TeeInputStream(inputStream, this.signatures); - break; - } - } - - /** - * Consume OpenPGP packets from the current {@link BCPGInputStream}. - * Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached, - * set
nestedInputStream
to the nested stream and breaks the loop. - * The nested stream is either a simple {@link InputStream} (in case of Literal Data), or another - * {@link OpenPgpMessageInputStream} in case of Compressed and Encrypted Data. - * Once the nested data is processed, this method is called again to consume the remainder - * of packets following the nested data packet. - * - * @throws IOException in case of an IO error - * @throws PGPException in case of an OpenPGP error - * @throws MissingDecryptionMethodException if there is an encrypted data packet which cannot be decrypted - * due to missing decryption methods (no key, no password, no sessionkey) - * @throws MalformedOpenPgpMessageException if the message is made of an invalid packet sequence which - * does not follow the packet syntax of RFC4880. - */ - private void consumePackets() - throws IOException, PGPException { - OpenPgpPacket nextPacket; - if (packetInputStream == null) { - return; - } - - loop: // we break this when we enter nested packets and later resume - while ((nextPacket = packetInputStream.nextPacketTag()) != null) { - signatures.nextPacket(nextPacket); - switch (nextPacket) { - - // Literal Data - the literal data content is the new input stream - case LIT: - processLiteralData(); - break loop; - - // Compressed Data - the content contains another OpenPGP message - case COMP: - processCompressedData(); - break loop; - - // One Pass Signature - case OPS: - processOnePassSignature(); - break; - - // Signature - either prepended to the message, or corresponding to a One Pass Signature - case SIG: - processSignature(); - break; - - // Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC) - case PKESK: - case SKESK: - case SED: - case SEIPD: - if (processEncryptedData()) { - // Successfully decrypted, enter nested content - break loop; - } - - throw new MissingDecryptionMethodException("No working decryption method found."); - - // Marker Packets need to be skipped and ignored - case MARKER: - LOGGER.debug("Skipping Marker Packet"); - packetInputStream.readMarker(); - break; - - // Key Packets are illegal in this context - case SK: - case PK: - case SSK: - case PSK: - case TRUST: - case UID: - case UATTR: - throw new MalformedOpenPgpMessageException("Illegal Packet in Stream: " + nextPacket); - - // MDC packet is usually processed by PGPEncryptedDataList, so it is very likely we encounter this - // packet out of order - case MDC: - throw new MalformedOpenPgpMessageException("Unexpected Packet in Stream: " + nextPacket); - - // Experimental Packets are not supported - case EXP_1: - case EXP_2: - case EXP_3: - case EXP_4: - throw new MalformedOpenPgpMessageException("Unsupported Packet in Stream: " + nextPacket); - } - } - } - - private void processLiteralData() throws IOException { - LOGGER.debug("Literal Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.LITERAL_DATA); - PGPLiteralData literalData = packetInputStream.readLiteralData(); - // Extract Metadata - this.metadata.setChild(new MessageMetadata.LiteralData( - literalData.getFileName(), - literalData.getModificationTime(), - StreamEncoding.requireFromCode(literalData.getFormat()))); - - nestedInputStream = literalData.getDataStream(); - } - - private void processCompressedData() throws IOException, PGPException { - syntaxVerifier.next(InputSymbol.COMPRESSED_DATA); - signatures.enterNesting(); - PGPCompressedData compressedData = packetInputStream.readCompressedData(); - // Extract Metadata - MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData( - CompressionAlgorithm.requireFromId(compressedData.getAlgorithm()), - metadata.depth + 1); - - LOGGER.debug("Compressed Data Packet (" + compressionLayer.algorithm + ") at depth " + metadata.depth + " encountered"); - InputStream decompressed = compressedData.getDataStream(); - nestedInputStream = new OpenPgpMessageInputStream(decompressed, options, compressionLayer, policy); - } - - private void processOnePassSignature() throws PGPException, IOException { - syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE); - PGPOnePassSignature onePassSignature = packetInputStream.readOnePassSignature(); - LOGGER.debug("One-Pass-Signature Packet by key " + KeyIdUtil.formatKeyId(onePassSignature.getKeyID()) + - " at depth " + metadata.depth + " encountered"); - signatures.addOnePassSignature(onePassSignature); - } - - private void processSignature() throws PGPException, IOException { - // true if Signature corresponds to OnePassSignature - boolean isSigForOPS = syntaxVerifier.peekStack() == StackSymbol.OPS; - syntaxVerifier.next(InputSymbol.SIGNATURE); - PGPSignature signature; - try { - signature = packetInputStream.readSignature(); - } catch (UnsupportedPacketVersionException e) { - LOGGER.debug("Unsupported Signature at depth " + metadata.depth + " encountered.", e); - return; - } - - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (isSigForOPS) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key " + - KeyIdUtil.formatKeyId(keyId) + - " at depth " + metadata.depth + " encountered"); - signatures.leaveNesting(); // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with - signatures.addCorrespondingOnePassSignature(signature, metadata, policy); - } else { - LOGGER.debug("Prepended Signature Packet by key " + - KeyIdUtil.formatKeyId(keyId) + - " at depth " + metadata.depth + " encountered"); - signatures.addPrependedSignature(signature); - } - } - - private boolean processEncryptedData() throws IOException, PGPException { - LOGGER.debug("Symmetrically Encrypted Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA); - PGPEncryptedDataList encDataList = packetInputStream.readEncryptedDataList(); - - if (!encDataList.isIntegrityProtected()) { - LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected."); - if (!options.isIgnoreMDCErrors()) { - throw new MessageNotIntegrityProtectedException(); - } - } - - SortedESKs esks = new SortedESKs(encDataList); - LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has " + - esks.skesks.size() + " SKESK(s) and " + - (esks.pkesks.size() + esks.anonPkesks.size()) + " PKESK(s) from which " + - esks.anonPkesks.size() + " PKESK(s) have an anonymous recipient"); - - // Try custom decryptor factories - for (SubkeyIdentifier subkeyIdentifier : options.getCustomDecryptorFactories().keySet()) { - LOGGER.debug("Attempt decryption with custom decryptor factory with key " + subkeyIdentifier); - PublicKeyDataDecryptorFactory decryptorFactory = options.getCustomDecryptorFactories().get(subkeyIdentifier); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - // find matching PKESK - if (pkesk.getKeyID() != subkeyIdentifier.getSubkeyId()) { - continue; - } - - // attempt decryption - if (decryptPKESKAndStream(esks, subkeyIdentifier, decryptorFactory, pkesk)) { - return true; - } - } - } - - // Try provided session key - if (options.getSessionKey() != null) { - LOGGER.debug("Attempt decryption with provided session key"); - SessionKey sessionKey = options.getSessionKey(); - throwIfUnacceptable(sessionKey.getAlgorithm()); - - SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getSessionKeyDataDecryptorFactory(sessionKey); - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - sessionKey.getAlgorithm(), metadata.depth + 1); - - PGPSessionKeyEncryptedData sessionKeyEncryptedData = encDataList.extractSessionKeyEncryptedData(); - try { - InputStream decrypted = sessionKeyEncryptedData.getDataStream(decryptorFactory); - encryptedData.sessionKey = sessionKey; - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, sessionKeyEncryptedData, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - LOGGER.debug("Successfully decrypted data with provided session key"); - return true; - } catch (PGPException e) { - // Session key mismatch? - LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e); - } - } - - // Try passwords - for (Passphrase passphrase : options.getDecryptionPassphrases()) { - for (PGPPBEEncryptedData skesk : esks.skesks) { - LOGGER.debug("Attempt decryption with provided passphrase"); - SymmetricKeyAlgorithm encapsulationAlgorithm = SymmetricKeyAlgorithm.requireFromId(skesk.getAlgorithm()); - try { - throwIfUnacceptable(encapsulationAlgorithm); - } catch (UnacceptableAlgorithmException e) { - LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm", e); - continue; - } - - PBEDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getPBEDataDecryptorFactory(passphrase); - if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { - return true; - } - } - } - - List> postponedDueToMissingPassphrase = new ArrayList<>(); - - // Try (known) secret keys - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - long keyId = pkesk.getKeyID(); - LOGGER.debug("Encountered PKESK for recipient " + KeyIdUtil.formatKeyId(keyId)); - PGPSecretKeyRing decryptionKeys = getDecryptionKey(keyId); - if (decryptionKeys == null) { - LOGGER.debug("Skipping PKESK because no matching key " + KeyIdUtil.formatKeyId(keyId) + " was provided"); - continue; - } - PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKeys, secretKey.getKeyID()); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - LOGGER.debug("Attempt decryption using secret key " + decryptionKeyId); - - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeys); - // Postpone keys with missing passphrase - if (!protector.hasPassphraseFor(keyId)) { - LOGGER.debug("Missing passphrase for key " + decryptionKeyId + ". Postponing decryption until all other keys were tried"); - postponedDueToMissingPassphrase.add(new Tuple<>(secretKey, pkesk)); - continue; - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - - // try anonymous secret keys - for (PGPPublicKeyEncryptedData pkesk : esks.anonPkesks) { - for (Tuple decryptionKeyCandidate : findPotentialDecryptionKeys(pkesk)) { - PGPSecretKeyRing decryptionKeys = decryptionKeyCandidate.getA(); - PGPSecretKey secretKey = decryptionKeyCandidate.getB(); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKeys, secretKey.getKeyID()); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - LOGGER.debug("Attempt decryption of anonymous PKESK with key " + decryptionKeyId); - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeyCandidate.getA()); - if (!protector.hasPassphraseFor(secretKey.getKeyID())) { - LOGGER.debug("Missing passphrase for key " + decryptionKeyId + ". Postponing decryption until all other keys were tried."); - postponedDueToMissingPassphrase.add(new Tuple<>(secretKey, pkesk)); - continue; - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - } - - if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { - // Non-interactive mode: Throw an exception with all locked decryption keys - Set keyIds = new HashSet<>(); - for (Tuple k : postponedDueToMissingPassphrase) { - PGPSecretKey key = k.getA(); - PGPSecretKeyRing keys = getDecryptionKey(key.getKeyID()); - assert (keys != null); - keyIds.add(new SubkeyIdentifier(keys, key.getKeyID())); - } - if (!keyIds.isEmpty()) { - throw new MissingPassphraseException(keyIds); - } - } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - // Interactive mode: Fire protector callbacks to get passphrases interactively - for (Tuple missingPassphrases : postponedDueToMissingPassphrase) { - PGPSecretKey secretKey = missingPassphrases.getA(); - long keyId = secretKey.getKeyID(); - PGPSecretKeyRing decryptionKey = getDecryptionKey(keyId); - assert (decryptionKey != null); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKey, keyId); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - - LOGGER.debug("Attempt decryption with key " + decryptionKeyId + " while interactively requesting its passphrase"); - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKey); - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - } - } else { - throw new IllegalStateException("Invalid PostponedKeysStrategy set in consumer options."); - } - - // we did not yet succeed in decrypting any session key :/ - - LOGGER.debug("Failed to decrypt encrypted data packet"); - return false; - } - - private boolean decryptWithPrivateKey(SortedESKs esks, - PGPPrivateKey privateKey, - SubkeyIdentifier decryptionKeyId, - PGPPublicKeyEncryptedData pkesk) - throws PGPException, IOException { - PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getPublicKeyDataDecryptorFactory(privateKey); - return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk); - } - - private static boolean hasUnsupportedS2KSpecifier(PGPSecretKey secretKey, SubkeyIdentifier decryptionKeyId) { - S2K s2K = secretKey.getS2K(); - if (s2K != null) { - int s2kType = s2K.getType(); - if (s2kType >= 100 && s2kType <= 110) { - LOGGER.debug("Skipping PKESK because key " + decryptionKeyId + " has unsupported private S2K specifier " + s2kType); - return true; - } - } - return false; - } - - private boolean decryptSKESKAndStream(SortedESKs esks, - PGPPBEEncryptedData symEsk, - PBEDataDecryptorFactory decryptorFactory) - throws IOException, UnacceptableAlgorithmException { - try { - InputStream decrypted = symEsk.getDataStream(decryptorFactory); - SessionKey sessionKey = new SessionKey(symEsk.getSessionKey(decryptorFactory)); - throwIfUnacceptable(sessionKey.getAlgorithm()); - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - sessionKey.getAlgorithm(), metadata.depth + 1); - encryptedData.sessionKey = sessionKey; - encryptedData.recipients = new ArrayList<>(); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - LOGGER.debug("Successfully decrypted data with passphrase"); - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, symEsk, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - return true; - } catch (UnacceptableAlgorithmException e) { - throw e; - } catch (PGPException e) { - LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e); - } - return false; - } - - private boolean decryptPKESKAndStream(SortedESKs esks, - SubkeyIdentifier decryptionKeyId, - PublicKeyDataDecryptorFactory decryptorFactory, - PGPPublicKeyEncryptedData asymEsk) - throws IOException, UnacceptableAlgorithmException { - try { - InputStream decrypted = asymEsk.getDataStream(decryptorFactory); - SessionKey sessionKey = new SessionKey(asymEsk.getSessionKey(decryptorFactory)); - throwIfUnacceptable(sessionKey.getAlgorithm()); - - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - SymmetricKeyAlgorithm.requireFromId(asymEsk.getSymmetricAlgorithm(decryptorFactory)), - metadata.depth + 1); - encryptedData.decryptionKey = decryptionKeyId; - encryptedData.sessionKey = sessionKey; - encryptedData.recipients = new ArrayList<>(); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - for (PGPPublicKeyEncryptedData pkesk : esks.anonPkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - - LOGGER.debug("Successfully decrypted data with key " + decryptionKeyId); - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, asymEsk, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - return true; - } catch (UnacceptableAlgorithmException e) { - throw e; - } catch (PGPException e) { - LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e); - } - return false; - } - - private void throwIfUnacceptable(SymmetricKeyAlgorithm algorithm) - throws UnacceptableAlgorithmException { - if (!policy.getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) { - throw new UnacceptableAlgorithmException("Symmetric-Key algorithm " + algorithm + " is not acceptable for message decryption."); - } - } - - private List> findPotentialDecryptionKeys(PGPPublicKeyEncryptedData pkesk) { - int algorithm = pkesk.getAlgorithm(); - List> decryptionKeyCandidates = new ArrayList<>(); - - for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) { - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - for (PGPPublicKey publicKey : info.getDecryptionSubkeys()) { - if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) { - PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID()); - decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate)); - } - } - } - return decryptionKeyCandidates; - } - - private PGPSecretKeyRing getDecryptionKey(long keyID) { - for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) { - PGPSecretKey decryptionKey = secretKeys.getSecretKey(keyID); - if (decryptionKey == null) { - continue; - } - - KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date()); - List encryptionKeys = info.getDecryptionSubkeys(); - for (PGPPublicKey key : encryptionKeys) { - if (key.getKeyID() == keyID) { - return secretKeys; - } - } - - LOGGER.debug("Subkey " + Long.toHexString(keyID) + " cannot be used for decryption."); - } - return null; - } - - @Override - public int read() throws IOException { - if (nestedInputStream == null) { - if (packetInputStream != null) { - syntaxVerifier.assertValid(); - } - return -1; - } - - int r; - try { - r = nestedInputStream.read(); - } catch (IOException e) { - r = -1; - } - boolean eos = r == -1; - if (!eos) { - byte b = (byte) r; - signatures.updateLiteral(b); - } else { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - - if (packetInputStream != null) { - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - signatures.finish(metadata, policy); - } - return r; - } - - @Override - public int read(@Nonnull byte[] b, int off, int len) - throws IOException { - if (nestedInputStream == null) { - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - } - return -1; - } - - int r = nestedInputStream.read(b, off, len); - if (r != -1) { - signatures.updateLiteral(b, off, r); - } else { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - - if (packetInputStream != null) { - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - signatures.finish(metadata, policy); - } - return r; - } - - @Override - public void close() throws IOException { - super.close(); - if (closed) { - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - } - return; - } - - if (nestedInputStream != null) { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - } - - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - packetInputStream.close(); - } - closed = true; - } - - private void collectMetadata() { - if (nestedInputStream instanceof OpenPgpMessageInputStream) { - OpenPgpMessageInputStream child = (OpenPgpMessageInputStream) nestedInputStream; - this.metadata.setChild((MessageMetadata.Nested) child.metadata); - } - } - - public MessageMetadata getMetadata() { - if (!closed) { - throw new IllegalStateException("Stream must be closed before access to metadata can be granted."); - } - - return new MessageMetadata((MessageMetadata.Message) metadata); - } - - private static class SortedESKs { - - private final List skesks = new ArrayList<>(); - private final List pkesks = new ArrayList<>(); - private final List anonPkesks = new ArrayList<>(); - - SortedESKs(PGPEncryptedDataList esks) { - for (PGPEncryptedData esk : esks) { - if (esk instanceof PGPPBEEncryptedData) { - skesks.add((PGPPBEEncryptedData) esk); - } - else if (esk instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk; - if (pkesk.getKeyID() != 0) { - pkesks.add(pkesk); - } else { - anonPkesks.add(pkesk); - } - } else { - throw new IllegalArgumentException("Unknown ESK class type."); - } - } - } - - public List all() { - List esks = new ArrayList<>(); - esks.addAll(skesks); - esks.addAll(pkesks); - esks.addAll(anonPkesks); - return esks; - } - } - - // In 'OPS LIT("Foo") SIG', OPS is only updated with "Foo" - // In 'OPS[1] OPS LIT("Foo") SIG SIG', OPS[1] (nested) is updated with OPS LIT("Foo") SIG. - // Therefore, we need to handle the innermost signature layer differently when updating with Literal data. - // Furthermore, For 'OPS COMP(LIT("Foo")) SIG', the signature is updated with "Foo". CHAOS!!! - private static final class Signatures extends OutputStream { - final ConsumerOptions options; - final List detachedSignatures; - final List prependedSignatures; - final List onePassSignatures; - final Stack> opsUpdateStack; - List literalOPS = new ArrayList<>(); - final List correspondingSignatures; - final List prependedSignaturesWithMissingCert = new ArrayList<>(); - final List inbandSignaturesWithMissingCert = new ArrayList<>(); - final List detachedSignaturesWithMissingCert = new ArrayList<>(); - boolean isLiteral = true; - - private Signatures(ConsumerOptions options) { - this.options = options; - this.detachedSignatures = new ArrayList<>(); - this.prependedSignatures = new ArrayList<>(); - this.onePassSignatures = new ArrayList<>(); - this.opsUpdateStack = new Stack<>(); - this.correspondingSignatures = new ArrayList<>(); - } - - void addDetachedSignatures(Collection signatures) { - for (PGPSignature signature : signatures) { - addDetachedSignature(signature); - } - } - - void addDetachedSignature(PGPSignature signature) { - SignatureCheck check = initializeSignature(signature); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (check != null) { - detachedSignatures.add(check); - } else { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - this.detachedSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key") - )); - } - } - - void addPrependedSignature(PGPSignature signature) { - SignatureCheck check = initializeSignature(signature); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (check != null) { - this.prependedSignatures.add(check); - } else { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - this.prependedSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key") - )); - } - } - - SignatureCheck initializeSignature(PGPSignature signature) { - long keyId = SignatureUtils.determineIssuerKeyId(signature); - PGPPublicKeyRing certificate = findCertificate(keyId); - if (certificate == null) { - return null; - } - - SubkeyIdentifier verifierKey = new SubkeyIdentifier(certificate, keyId); - initialize(signature, certificate, keyId); - return new SignatureCheck(signature, certificate, verifierKey); - } - - void addOnePassSignature(PGPOnePassSignature signature) { - PGPPublicKeyRing certificate = findCertificate(signature.getKeyID()); - - if (certificate != null) { - OnePassSignatureCheck ops = new OnePassSignatureCheck(signature, certificate); - initialize(signature, certificate); - onePassSignatures.add(ops); - - literalOPS.add(ops); - } - if (signature.isContaining()) { - enterNesting(); - } - } - - void addCorrespondingOnePassSignature(PGPSignature signature, MessageMetadata.Layer layer, Policy policy) { - boolean found = false; - long keyId = SignatureUtils.determineIssuerKeyId(signature); - for (int i = onePassSignatures.size() - 1; i >= 0; i--) { - OnePassSignatureCheck onePassSignature = onePassSignatures.get(i); - if (onePassSignature.getOnePassSignature().getKeyID() != keyId) { - continue; - } - found = true; - - if (onePassSignature.getSignature() != null) { - continue; - } - - onePassSignature.setSignature(signature); - SignatureVerification verification = new SignatureVerification(signature, - new SubkeyIdentifier(onePassSignature.getVerificationKeys(), onePassSignature.getOnePassSignature().getKeyID())); - - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(signature); - CertificateValidator.validateCertificateAndVerifyOnePassSignature(onePassSignature, policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedOnePassSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedOnePassSignature(new SignatureVerification.Failure(verification, e)); - } - break; - } - - if (!found) { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - inbandSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key"))); - } - } - - void enterNesting() { - opsUpdateStack.push(literalOPS); - literalOPS = new ArrayList<>(); - } - - void leaveNesting() { - if (opsUpdateStack.isEmpty()) { - return; - } - opsUpdateStack.pop(); - } - - private static void initialize(@Nonnull PGPSignature signature, @Nonnull PGPPublicKeyRing certificate, long keyId) { - PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(); - try { - signature.init(verifierProvider, certificate.getPublicKey(keyId)); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - - private static void initialize(@Nonnull PGPOnePassSignature ops, @Nonnull PGPPublicKeyRing certificate) { - PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(); - try { - ops.init(verifierProvider, certificate.getPublicKey(ops.getKeyID())); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - - private PGPPublicKeyRing findCertificate(long keyId) { - PGPPublicKeyRing cert = options.getCertificateSource().getCertificate(keyId); - if (cert != null) { - return cert; - } - - if (options.getMissingCertificateCallback() != null) { - return options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId); - } - return null; // TODO: Missing cert for sig - } - - public void updateLiteral(byte b) { - for (OnePassSignatureCheck ops : literalOPS) { - ops.getOnePassSignature().update(b); - } - - for (SignatureCheck detached : detachedSignatures) { - detached.getSignature().update(b); - } - - for (SignatureCheck prepended : prependedSignatures) { - prepended.getSignature().update(b); - } - } - - public void updateLiteral(byte[] b, int off, int len) { - for (OnePassSignatureCheck ops : literalOPS) { - ops.getOnePassSignature().update(b, off, len); - } - - for (SignatureCheck detached : detachedSignatures) { - detached.getSignature().update(b, off, len); - } - - for (SignatureCheck prepended : prependedSignatures) { - prepended.getSignature().update(b, off, len); - } - } - - public void updatePacket(byte b) { - for (int i = opsUpdateStack.size() - 1; i >= 0; i--) { - List nestedOPSs = opsUpdateStack.get(i); - for (OnePassSignatureCheck ops : nestedOPSs) { - ops.getOnePassSignature().update(b); - } - } - } - - public void updatePacket(byte[] buf, int off, int len) { - for (int i = opsUpdateStack.size() - 1; i >= 0; i--) { - List nestedOPSs = opsUpdateStack.get(i); - for (OnePassSignatureCheck ops : nestedOPSs) { - ops.getOnePassSignature().update(buf, off, len); - } - } - } - - public void finish(MessageMetadata.Layer layer, Policy policy) { - for (SignatureCheck detached : detachedSignatures) { - SignatureVerification verification = new SignatureVerification(detached.getSignature(), detached.getSigningKeyIdentifier()); - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(detached.getSignature()); - CertificateValidator.validateCertificateAndVerifyInitializedSignature( - detached.getSignature(), KeyRingUtils.publicKeys(detached.getSigningKeyRing()), policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedDetachedSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedDetachedSignature(new SignatureVerification.Failure(verification, e)); - } - } - - for (SignatureCheck prepended : prependedSignatures) { - SignatureVerification verification = new SignatureVerification(prepended.getSignature(), prepended.getSigningKeyIdentifier()); - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(prepended.getSignature()); - CertificateValidator.validateCertificateAndVerifyInitializedSignature( - prepended.getSignature(), KeyRingUtils.publicKeys(prepended.getSigningKeyRing()), policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedPrependedSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedPrependedSignature(new SignatureVerification.Failure(verification, e)); - } - } - - for (SignatureVerification.Failure rejected : inbandSignaturesWithMissingCert) { - layer.addRejectedOnePassSignature(rejected); - } - - for (SignatureVerification.Failure rejected : prependedSignaturesWithMissingCert) { - layer.addRejectedPrependedSignature(rejected); - } - - for (SignatureVerification.Failure rejected : detachedSignaturesWithMissingCert) { - layer.addRejectedDetachedSignature(rejected); - } - } - - @Override - public void write(int b) { - updatePacket((byte) b); - } - - @Override - public void write(@Nonnull byte[] b, int off, int len) { - updatePacket(b, off, len); - } - - public void nextPacket(OpenPgpPacket nextPacket) { - if (nextPacket == OpenPgpPacket.LIT) { - isLiteral = true; - if (literalOPS.isEmpty() && !opsUpdateStack.isEmpty()) { - literalOPS = opsUpdateStack.pop(); - } - } else { - isLiteral = false; - } - } - - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt new file mode 100644 index 00000000..25b29100 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -0,0 +1,903 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.bcpg.BCPGInputStream +import org.bouncycastle.bcpg.UnsupportedPacketVersionException +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.bouncycastle.util.io.TeeInputStream +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.* +import org.pgpainless.decryption_verification.MessageMetadata.* +import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil +import org.pgpainless.decryption_verification.syntax_check.InputSymbol +import org.pgpainless.decryption_verification.syntax_check.PDA +import org.pgpainless.decryption_verification.syntax_check.StackSymbol +import org.pgpainless.exception.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.UnlockSecretKey +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.policy.Policy +import org.pgpainless.signature.SignatureUtils +import org.pgpainless.signature.consumer.CertificateValidator +import org.pgpainless.signature.consumer.OnePassSignatureCheck +import org.pgpainless.signature.consumer.SignatureCheck +import org.pgpainless.signature.consumer.SignatureValidator +import org.pgpainless.util.ArmoredInputStreamFactory +import org.pgpainless.util.SessionKey +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +class OpenPgpMessageInputStream( + type: Type, + inputStream: InputStream, + private val options: ConsumerOptions, + private val metadata: Layer, + private val policy: Policy) : DecryptionStream() { + + private val signatures: Signatures = Signatures(options) + private var packetInputStream: TeeBCPGInputStream? = null + private var nestedInputStream: InputStream? = null + private val syntaxVerifier = PDA() + private var closed = false + + init { + + // Add detached signatures only on the outermost OpenPgpMessageInputStream + if (metadata is Message) { + signatures.addDetachedSignatures(options.detachedSignatures) + } + + when(type) { + Type.standard -> { + + // tee out packet bytes for signature verification + packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures) + + // *omnomnom* + consumePackets() + } + + Type.cleartext_signed -> { + val multiPassStrategy = options.multiPassStrategy + val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( + inputStream, multiPassStrategy.messageOutputStream) + + for (signature in detachedSignatures) { + signatures.addDetachedSignature(signature) + } + + options.forceNonOpenPgpData() + nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) + } + + Type.non_openpgp -> { + packetInputStream = null + nestedInputStream = TeeInputStream(inputStream, this.signatures) + } + } + } + + enum class Type { + standard, cleartext_signed, non_openpgp + } + + constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy): + this(Type.standard, inputStream, options, metadata, policy) + + private fun consumePackets() { + val pIn = packetInputStream ?: return + + var packet: OpenPgpPacket? + + // Comsume packets, potentially stepping into nested layers + layer@ while (run { + packet = pIn.nextPacketTag() + packet + } != null) { + + signatures.nextPacket(packet!!) + // Consume packets in a layer + when(packet) { + + OpenPgpPacket.LIT -> { + processLiteralData() + break@layer // nest down + } + + OpenPgpPacket.COMP -> { + processCompressedData() + break@layer // nest down + } + + OpenPgpPacket.OPS -> { + processOnePassSignature() // OPS is on the same layer, no nest down + } + + OpenPgpPacket.SIG -> { + processSignature() // SIG is on the same layer, no nest down + } + + OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> { + if (processEncryptedData()) { + break@layer + } + throw MissingDecryptionMethodException("No working decryption method found.") + } + + OpenPgpPacket.MARKER -> { + LOGGER.debug("Skipping Marker Packet") + pIn.readMarker() + } + + OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR -> + throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet") + + OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 -> + throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet") + + else -> + throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet") + } + } + } + + private fun processLiteralData() { + LOGGER.debug("Literal Data Packet at depth ${metadata.depth} encountered.") + syntaxVerifier.next(InputSymbol.LITERAL_DATA) + val literalData = packetInputStream!!.readLiteralData() + + // Extract Metadata + metadata.setChild(LiteralData( + literalData.fileName, literalData.modificationTime, + StreamEncoding.requireFromCode(literalData.format))) + + nestedInputStream = literalData.inputStream + } + + private fun processCompressedData() { + syntaxVerifier.next(InputSymbol.COMPRESSED_DATA) + signatures.enterNesting() + val compressedData = packetInputStream!!.readCompressedData() + + // Extract Metadata + val compressionLayer = CompressedData( + CompressionAlgorithm.requireFromId(compressedData.algorithm), + metadata.depth + 1) + + LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${metadata.depth} encountered.") + nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) + } + + private fun processOnePassSignature() { + syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) + val ops = packetInputStream!!.readOnePassSignature() + LOGGER.debug("One-Pass-Signature Packet by key ${KeyIdUtil.formatKeyId(ops.keyID)} at depth ${metadata.depth} encountered.") + signatures.addOnePassSignature(ops) + } + + private fun processSignature() { + // true if signature corresponds to OPS + val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS + syntaxVerifier.next(InputSymbol.SIGNATURE) + val signature = try { + packetInputStream!!.readSignature() + } catch (e : UnsupportedPacketVersionException) { + LOGGER.debug("Unsupported Signature at depth ${metadata.depth} encountered.", e) + return + } + + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (isSigForOps) { + LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with + signatures.addCorrespondingOnePassSignature(signature, metadata, policy) + } else { + LOGGER.debug("Prepended Signature Packet by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + signatures.addPrependedSignature(signature) + } + } + + private fun processEncryptedData(): Boolean { + LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${metadata.depth} encountered.") + syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) + val encDataList = packetInputStream!!.readEncryptedDataList() + if (!encDataList.isIntegrityProtected) { + LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.") + if (!options.isIgnoreMDCErrors) { + throw MessageNotIntegrityProtectedException() + } + } + + val esks = SortedESKs(encDataList) + LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + + " ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" + + " have an anonymous recipient.") + + // try custom decryptor factories + for ((key, decryptorFactory) in options.customDecryptorFactories) { + LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.") + esks.pkesks.filter { + // find matching PKESK + it.keyID == key.subkeyId + }.forEach { + // attempt decryption + if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) { + return true + } + } + } + + // try provided session key + if (options.sessionKey != null) { + val sk = options.sessionKey!! + LOGGER.debug("Attempt decryption with provided session key.") + throwIfUnacceptable(sk.algorithm) + + val decryptorFactory = ImplementationFactory.getInstance() + .getSessionKeyDataDecryptorFactory(sk) + val layer = EncryptedData(sk.algorithm, metadata.depth + 1) + val skEncData = encDataList.extractSessionKeyEncryptedData() + try { + val decrypted = skEncData.getDataStream(decryptorFactory) + layer.sessionKey = sk + val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy) + LOGGER.debug("Successfully decrypted data using provided session key") + return true + } catch (e : PGPException) { + // Session key mismatch? + LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e) + } + } + + // try passwords + for (passphrase in options.decryptionPassphrases) { + for (skesk in esks.skesks) { + LOGGER.debug("Attempt decryption with provided passphrase") + val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm) + if (!isAcceptable(algorithm)) { + LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm") + continue + } + + val decryptorFactory = ImplementationFactory.getInstance() + .getPBEDataDecryptorFactory(passphrase) + if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { + return true + } + } + } + + val postponedDueToMissingPassphrase = mutableListOf>() + + // try (known) secret keys + for (pkesk in esks.pkesks) { + val keyId = pkesk.keyID + LOGGER.debug("Encountered PKESK for recipient ${KeyIdUtil.formatKeyId(keyId)}") + val decryptionKeys = getDecryptionKey(keyId) + if (decryptionKeys == null) { + LOGGER.debug("Skipping PKESK because no matching key ${KeyIdUtil.formatKeyId(keyId)} was provided.") + continue + } + val secretKey = decryptionKeys.getSecretKey(keyId) + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") + val protector = options.getSecretKeyProtector(decryptionKeys) + if (!protector.hasPassphraseFor(keyId)) { + LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(secretKey to pkesk) + continue + } + + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + + // try anonymous secret keys + for (pkesk in esks.anonPkesks) { + for ((decryptionKeys, secretKey) in findPotentialDecryptionKeys(pkesk)) { + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.") + val protector = options.getSecretKeyProtector(decryptionKeys) + + if (!protector.hasPassphraseFor(secretKey.keyID)) { + LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(secretKey to pkesk) + continue + } + + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + } + + if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { + // Non-interactive mode: Throw an exception with all locked decryption keys + postponedDueToMissingPassphrase.map { + SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) + }.also { + if (it.isNotEmpty()) + throw MissingPassphraseException(it.toSet()) + } + } else if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.INTERACTIVE) { + for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { + val keyId = secretKey.keyID + val decryptionKeys = getDecryptionKey(keyId)!! + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") + val protector = options.getSecretKeyProtector(decryptionKeys) + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + } else { + throw IllegalStateException("Invalid PostponedKeysStrategy set in consumer options.") + } + + // We did not yet succeed in decrypting any session key :/ + LOGGER.debug("Failed to decrypt encrypted data packet.") + return false + } + + private fun decryptWithPrivateKey(esks: SortedESKs, + privateKey: PGPPrivateKey, + decryptionKeyId: SubkeyIdentifier, + pkesk: PGPPublicKeyEncryptedData): Boolean { + val decryptorFactory = ImplementationFactory.getInstance() + .getPublicKeyDataDecryptorFactory(privateKey) + return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk) + } + + private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean { + val s2k = secretKey.s2K + if (s2k != null) { + if (s2k.type in 100..110) { + LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") + return true + } + } + return false + } + + private fun decryptSKESKAndStream(esks: SortedESKs, + skesk: PGPPBEEncryptedData, + decryptorFactory: PBEDataDecryptorFactory): Boolean { + try { + val decrypted = skesk.getDataStream(decryptorFactory) + val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) + throwIfUnacceptable(sessionKey.algorithm) + val encryptedData = EncryptedData(sessionKey.algorithm, metadata.depth + 1) + encryptedData.sessionKey = sessionKey + encryptedData.recipients = esks.pkesks.map { it.keyID } + LOGGER.debug("Successfully decrypted data with passphrase") + val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + return true + } catch (e : UnacceptableAlgorithmException) { + throw e + } catch (e : PGPException) { + LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e) + } + return false + } + + private fun decryptPKESKAndStream(esks: SortedESKs, + decryptionKeyId: SubkeyIdentifier, + decryptorFactory: PublicKeyDataDecryptorFactory, + pkesk: PGPPublicKeyEncryptedData): Boolean { + try { + val decrypted = pkesk.getDataStream(decryptorFactory) + val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory)) + throwIfUnacceptable(sessionKey.algorithm) + + val encryptedData = EncryptedData( + SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)), + metadata.depth + 1) + encryptedData.decryptionKey = decryptionKeyId + encryptedData.sessionKey = sessionKey + encryptedData.recipients = esks.pkesks.plus(esks.anonPkesks).map { it.keyID } + LOGGER.debug("Successfully decrypted data with key $decryptionKeyId") + val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + return true + } catch (e : UnacceptableAlgorithmException) { + throw e + } catch (e : PGPException) { + LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e) + } + return false + } + + override fun read(): Int { + if (nestedInputStream == null) { + if (packetInputStream != null) { + syntaxVerifier.assertValid() + } + return -1 + } + + val r: Int = try { + nestedInputStream!!.read() + } catch (e: IOException) { + -1 + } + if (r != -1) { + signatures.updateLiteral(r.toByte()) + } else { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + if (packetInputStream != null) { + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + } + signatures.finish(metadata, policy) + } + return r + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + if (nestedInputStream == null) { + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + } + return -1 + } + val r = nestedInputStream!!.read(b, off, len) + if (r != -1) { + signatures.updateLiteral(b, off, r) + } else { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + if (packetInputStream != null) { + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + } + signatures.finish(metadata, policy) + } + return r + } + + override fun close() { + super.close() + if (closed) { + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + } + return + } + if (nestedInputStream != null) { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + } + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + packetInputStream!!.close() + } + closed = true + } + + private fun collectMetadata() { + if (nestedInputStream is OpenPgpMessageInputStream) { + val child = nestedInputStream as OpenPgpMessageInputStream + metadata.setChild(child.metadata as Nested) + } + } + + override fun getMetadata(): MessageMetadata { + check(closed) { "Stream must be closed before access to metadata can be granted." } + + return MessageMetadata((metadata as Message)) + } + + private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.decryptionKeys.firstOrNull { + it.any { + k -> k.keyID == keyId + }.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { + k -> k.keyID == keyId + }) + } + + private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List> { + val algorithm = pkesk.algorithm + val candidates = mutableListOf>() + options.decryptionKeys.forEach { + val info = PGPainless.inspectKeyRing(it) + for (key in info.decryptionSubkeys) { + if (key.algorithm == algorithm && info.isSecretKeyAvailable(key.keyID)) { + candidates.add(it to it.getSecretKey(key.keyID)) + } + } + } + return candidates + } + + private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean = + policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm) + + private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) { + if (!isAcceptable(algorithm)) { + throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.") + } + } + + private class SortedESKs(esks: PGPEncryptedDataList) { + val skesks: List + val pkesks: List + val anonPkesks: List + + init { + skesks = mutableListOf() + pkesks = mutableListOf() + anonPkesks = mutableListOf() + for (esk in esks) { + if (esk is PGPPBEEncryptedData) { + skesks.add(esk) + } else if (esk is PGPPublicKeyEncryptedData) { + if (esk.keyID != 0L) { + pkesks.add(esk) + } else { + anonPkesks.add(esk) + } + } else { + throw IllegalArgumentException("Unknown ESK class type ${esk.javaClass}") + } + } + } + + val all: List + get() = skesks.plus(pkesks).plus(anonPkesks) + } + + private class Signatures( + val options: ConsumerOptions + ) : OutputStream() { + val detachedSignatures = mutableListOf() + val prependedSignatures = mutableListOf() + val onePassSignatures = mutableListOf() + val opsUpdateStack = ArrayDeque>() + var literalOPS = mutableListOf() + val correspondingSignatures = mutableListOf() + val prependedSignaturesWithMissingCert = mutableListOf() + val inbandSignaturesWithMissingCert = mutableListOf() + val detachedSignaturesWithMissingCert = mutableListOf() + var isLiteral = true + + fun addDetachedSignatures(signatures: Collection) { + for (signature in signatures) { + addDetachedSignature(signature) + } + } + + fun addDetachedSignature(signature: PGPSignature) { + val check = initializeSignature(signature) + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (check != null) { + detachedSignatures.add(check) + } else { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key."))) + } + } + + fun addPrependedSignature(signature: PGPSignature) { + val check = initializeSignature(signature) + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (check != null) { + prependedSignatures.add(check) + } else { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key") + )) + } + } + + fun initializeSignature(signature: PGPSignature): SignatureCheck? { + val keyId = SignatureUtils.determineIssuerKeyId(signature) + val certificate = findCertificate(keyId) ?: return null + + val verifierKey = SubkeyIdentifier(certificate, keyId) + initialize(signature, certificate, keyId) + return SignatureCheck(signature, certificate, verifierKey) + } + + fun addOnePassSignature(signature: PGPOnePassSignature) { + val certificate = findCertificate(signature.keyID) + + if (certificate != null) { + val ops = OnePassSignatureCheck(signature, certificate) + initialize(signature, certificate) + onePassSignatures.add(ops) + literalOPS.add(ops) + } + if (signature.isContaining) { + enterNesting() + } + } + + fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) { + var found = false + val keyId = SignatureUtils.determineIssuerKeyId(signature) + for ((i, check) in onePassSignatures.withIndex().reversed()) { + if (check.onePassSignature.keyID != keyId) { + continue + } + found = true + + if (check.signature != null) { + continue + } + + check.signature = signature + val verification = SignatureVerification(signature, + SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) + + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(signature) + CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedOnePassSignature(verification) + } catch (e: SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e)) + } + break + } + + if (!found) { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key.") + )) + } + } + + fun enterNesting() { + opsUpdateStack.addFirst(literalOPS) + literalOPS = mutableListOf() + } + + fun leaveNesting() { + if (opsUpdateStack.isEmpty()) { + return + } + opsUpdateStack.removeFirst() + } + + fun findCertificate(keyId: Long): PGPPublicKeyRing? { + val cert = options.certificateSource.getCertificate(keyId) + if (cert != null) { + return cert + } + + if (options.missingCertificateCallback != null) { + return options.missingCertificateCallback!!.onMissingPublicKeyEncountered(keyId) + } + return null // TODO: Missing cert for sig + } + + fun updateLiteral(b: Byte) { + for (ops in literalOPS) { + ops.onePassSignature.update(b) + } + + for (detached in detachedSignatures) { + detached.signature.update(b) + } + + for (prepended in prependedSignatures) { + prepended.signature.update(b) + } + } + + fun updateLiteral(buf: ByteArray, off: Int, len: Int) { + for (ops in literalOPS) { + ops.onePassSignature.update(buf, off, len) + } + + for (detached in detachedSignatures) { + detached.signature.update(buf, off, len) + } + + for (prepended in prependedSignatures) { + prepended.signature.update(buf, off, len) + } + } + + fun updatePacket(b: Byte) { + for (nestedOPSs in opsUpdateStack.reversed()) { + for (ops in nestedOPSs) { + ops.onePassSignature.update(b) + } + } + } + + fun updatePacket(buf: ByteArray, off: Int, len: Int) { + for (nestedOPSs in opsUpdateStack.reversed()) { + for (ops in nestedOPSs) { + ops.onePassSignature.update(buf, off, len) + } + } + } + + fun finish(layer: Layer, policy: Policy) { + for (detached in detachedSignatures) { + val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier) + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(detached.signature) + CertificateValidator.validateCertificateAndVerifyInitializedSignature( + detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedDetachedSignature(verification) + } catch (e : SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e)) + } + } + + for (prepended in prependedSignatures) { + val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(prepended.signature) + CertificateValidator.validateCertificateAndVerifyInitializedSignature( + prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedPrependedSignature(verification) + } catch (e : SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e)) + } + } + + for (rejected in inbandSignaturesWithMissingCert) { + layer.addRejectedOnePassSignature(rejected) + } + + for (rejected in prependedSignaturesWithMissingCert) { + layer.addRejectedPrependedSignature(rejected) + } + + for (rejected in detachedSignaturesWithMissingCert) { + layer.addRejectedDetachedSignature(rejected) + } + } + + override fun write(b: Int) { + updatePacket(b.toByte()) + } + + override fun write(buf: ByteArray, off: Int, len: Int) { + updatePacket(buf, off, len) + } + + fun nextPacket(nextPacket: OpenPgpPacket) { + if (nextPacket == OpenPgpPacket.LIT) { + isLiteral = true + if (literalOPS.isEmpty() && opsUpdateStack.isNotEmpty()) { + literalOPS = opsUpdateStack.removeFirst() + } + } else { + isLiteral = false + } + } + + companion object { + @JvmStatic + private fun initialize(signature: PGPSignature, certificate: PGPPublicKeyRing, keyId: Long) { + val verifierProvider = ImplementationFactory.getInstance() + .pgpContentVerifierBuilderProvider + try { + signature.init(verifierProvider, certificate.getPublicKey(keyId)) + } catch (e : PGPException) { + throw RuntimeException(e) + } + } + + @JvmStatic + private fun initialize(ops: PGPOnePassSignature, certificate: PGPPublicKeyRing) { + val verifierProvider = ImplementationFactory.getInstance() + .pgpContentVerifierBuilderProvider + try { + ops.init(verifierProvider, certificate.getPublicKey(ops.keyID)) + } catch (e : PGPException) { + throw RuntimeException(e) + } + } + } + } + + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java) + + @JvmStatic + fun create(inputStream: InputStream, + options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy()) + + @JvmStatic + fun create(inputStream: InputStream, + options: ConsumerOptions, + policy: Policy) = create(inputStream, options, Message(), policy) + + @JvmStatic + internal fun create(inputStream: InputStream, + options: ConsumerOptions, + metadata: Layer, + policy: Policy): OpenPgpMessageInputStream { + val openPgpIn = OpenPgpInputStream(inputStream) + openPgpIn.reset() + + if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData) { + return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy) + } + + if (openPgpIn.isBinaryOpenPgp) { + // Simply consume OpenPGP message + return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy) + } + + return if (openPgpIn.isAsciiArmored) { + val armorIn = ArmoredInputStreamFactory.get(openPgpIn) + if (armorIn.isClearText) { + (metadata as Message).cleartextSigned = true + OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy) + } else { + // Simply consume dearmored OpenPGP message + OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy) + } + } else { + throw AssertionError("Cannot deduce type of data.") + } + } + } +} \ No newline at end of file