mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-24 11:57:59 +01:00
Kotlin conversion: OpenPgpMessageInputStream
This commit is contained in:
parent
a3ab5de2f5
commit
a67d7aeed9
2 changed files with 903 additions and 1139 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,903 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// 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<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
|
||||
|
||||
// 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<Pair<PGPSecretKeyRing, PGPSecretKey>> {
|
||||
val algorithm = pkesk.algorithm
|
||||
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
||||
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<PGPPBEEncryptedData>
|
||||
val pkesks: List<PGPPublicKeyEncryptedData>
|
||||
val anonPkesks: List<PGPPublicKeyEncryptedData>
|
||||
|
||||
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<PGPEncryptedData>
|
||||
get() = skesks.plus(pkesks).plus(anonPkesks)
|
||||
}
|
||||
|
||||
private class Signatures(
|
||||
val options: ConsumerOptions
|
||||
) : OutputStream() {
|
||||
val detachedSignatures = mutableListOf<SignatureCheck>()
|
||||
val prependedSignatures = mutableListOf<SignatureCheck>()
|
||||
val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
|
||||
val opsUpdateStack = ArrayDeque<MutableList<OnePassSignatureCheck>>()
|
||||
var literalOPS = mutableListOf<OnePassSignatureCheck>()
|
||||
val correspondingSignatures = mutableListOf<PGPSignature>()
|
||||
val prependedSignaturesWithMissingCert = mutableListOf<SignatureVerification.Failure>()
|
||||
val inbandSignaturesWithMissingCert = mutableListOf<SignatureVerification.Failure>()
|
||||
val detachedSignaturesWithMissingCert = mutableListOf<SignatureVerification.Failure>()
|
||||
var isLiteral = true
|
||||
|
||||
fun addDetachedSignatures(signatures: Collection<PGPSignature>) {
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue