1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

First draft for SEIPD2 negotiation

This commit is contained in:
Paul Schaub 2025-05-05 12:17:47 +02:00
parent 7a33e84497
commit b41fb2c468
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
6 changed files with 124 additions and 49 deletions

View file

@ -5,9 +5,34 @@
package org.pgpainless.bouncycastle.extensions
import org.bouncycastle.bcpg.HashAlgorithmTags
import org.bouncycastle.openpgp.api.EncryptedDataPacketType
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPImplementation
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder
import org.bouncycastle.openpgp.operator.PGPDigestCalculator
fun OpenPGPImplementation.checksumCalculator(): PGPDigestCalculator {
return pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)
}
fun OpenPGPImplementation.pgpDataEncryptorBuilder(
mechanism: MessageEncryptionMechanism
): PGPDataEncryptorBuilder {
require(mechanism.isEncrypted) { "Cannot create PGPDataEncryptorBuilder for NULL algorithm." }
return pgpDataEncryptorBuilder(mechanism.symmetricKeyAlgorithm).also {
when (mechanism.mode!!) {
EncryptedDataPacketType.SED -> it.setWithIntegrityPacket(false)
EncryptedDataPacketType.SEIPDv1 -> it.setWithIntegrityPacket(true)
EncryptedDataPacketType.SEIPDv2 -> {
it.setWithAEAD(mechanism.aeadAlgorithm, mechanism.symmetricKeyAlgorithm)
it.setUseV6AEAD()
}
EncryptedDataPacketType.LIBREPGP_OED -> {
it.setWithAEAD(mechanism.aeadAlgorithm, mechanism.symmetricKeyAlgorithm)
it.setUseV5AEAD()
}
}
return it
}
}

View file

@ -6,11 +6,15 @@ package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.AEADAlgorithm
import org.pgpainless.algorithm.AEADCipherMode
import org.pgpainless.algorithm.EncryptionPurpose
import org.pgpainless.algorithm.Feature
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
import org.pgpainless.authentication.CertificateAuthority
@ -23,32 +27,34 @@ import org.pgpainless.util.Passphrase
class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: PGPainless) {
private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf()
private val _encryptionKeys: MutableSet<OpenPGPComponentKey> = mutableSetOf()
private val keysAndAccessors: MutableMap<OpenPGPComponentKey, KeyAccessor> = mutableMapOf()
private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf()
private val _keyViews: MutableMap<SubkeyIdentifier, KeyAccessor> = mutableMapOf()
private val encryptionKeySelector: EncryptionKeySelector = encryptToAllCapableSubkeys()
private var allowEncryptionWithMissingKeyFlags = false
private var evaluationDate = Date()
private var _encryptionAlgorithmOverride: SymmetricKeyAlgorithm? = null
private var _encryptionMechanismOverride: MessageEncryptionMechanism? = null
val encryptionMethods
get() = _encryptionMethods.toSet()
val encryptionKeyIdentifiers
get() = _encryptionKeys.map { SubkeyIdentifier(it) }
get() = keysAndAccessors.keys.map { SubkeyIdentifier(it) }
val encryptionKeys
get() = _encryptionKeys.toSet()
val keyRingInfo
get() = _keyRingInfo.toMap()
val keyViews
get() = _keyViews.toMap()
get() = keysAndAccessors.keys.toSet()
@Deprecated(
"Deprecated in favor of encryptionMechanismOverride",
replaceWith = ReplaceWith("encryptionMechanismOverride"))
val encryptionAlgorithmOverride
get() = _encryptionAlgorithmOverride
get() =
_encryptionMechanismOverride?.let {
SymmetricKeyAlgorithm.requireFromId(it.symmetricKeyAlgorithm)
}
val encryptionMechanismOverride
get() = _encryptionMechanismOverride
constructor(api: PGPainless) : this(EncryptionPurpose.ANY, api)
@ -200,8 +206,8 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
for (subkey in subkeys) {
val keyId = SubkeyIdentifier(subkey)
_keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaUserId(subkey, cert.getUserId(userId.toString()))
addRecipientKey(subkey, false)
val accessor = KeyAccessor.ViaUserId(subkey, cert.getUserId(userId.toString()))
addRecipientKey(subkey, accessor, false)
}
}
@ -319,13 +325,17 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
for (subkey in encryptionSubkeys) {
val keyId = SubkeyIdentifier(subkey)
_keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaKeyIdentifier(subkey)
addRecipientKey(subkey, wildcardKeyId)
val accessor = KeyAccessor.ViaKeyIdentifier(subkey)
addRecipientKey(subkey, accessor, wildcardKeyId)
}
}
private fun addRecipientKey(key: OpenPGPComponentKey, wildcardRecipient: Boolean) {
_encryptionKeys.add(key)
private fun addRecipientKey(
key: OpenPGPComponentKey,
accessor: KeyAccessor,
wildcardRecipient: Boolean
) {
keysAndAccessors[key] = accessor
addEncryptionMethod(
api.implementation.publicKeyKeyEncryptionMethodGenerator(key.pgpPublicKey).also {
it.setUseWildcardRecipient(wildcardRecipient)
@ -381,11 +391,21 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
* @param encryptionAlgorithm encryption algorithm override
* @return this
*/
@Deprecated(
"Deprecated in favor of overrideEncryptionMechanism",
replaceWith =
ReplaceWith(
"overrideEncryptionMechanism(MessageEncryptionMechanism.integrityProtected(encryptionAlgorithm.algorithmId))"))
fun overrideEncryptionAlgorithm(encryptionAlgorithm: SymmetricKeyAlgorithm) = apply {
require(encryptionAlgorithm != SymmetricKeyAlgorithm.NULL) {
"Encryption algorithm override cannot be NULL."
}
_encryptionAlgorithmOverride = encryptionAlgorithm
overrideEncryptionMechanism(
MessageEncryptionMechanism.integrityProtected(encryptionAlgorithm.algorithmId))
}
fun overrideEncryptionMechanism(encryptionMechanism: MessageEncryptionMechanism) = apply {
_encryptionMechanismOverride = encryptionMechanism
}
/**
@ -403,7 +423,8 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
internal fun negotiateSymmetricEncryptionAlgorithm(): SymmetricKeyAlgorithm {
val preferences = keyViews.values.map { it.preferredSymmetricKeyAlgorithms }.toList()
val preferences =
keysAndAccessors.values.map { it.preferredSymmetricKeyAlgorithms }.toList()
val algorithm =
byPopularity()
.negotiate(
@ -413,6 +434,28 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
return algorithm
}
internal fun negotiateEncryptionMechanism(): MessageEncryptionMechanism {
val features = keysAndAccessors.values.map { it.features }.toList()
if (features.all { it.contains(Feature.MODIFICATION_DETECTION_2) }) {
val aeadPrefs = keysAndAccessors.values.map { it.preferredAEADCipherSuites }.toList()
val counted = mutableMapOf<AEADCipherMode, Int>()
for (pref in aeadPrefs) {
for (mode in pref) {
counted[mode] = counted.getOrDefault(mode, 0) + 1
}
}
val max: AEADCipherMode =
counted.maxByOrNull { it.value }?.key
?: AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128)
return MessageEncryptionMechanism.aead(
max.ciphermode.algorithmId, max.aeadAlgorithm.algorithmId)
} else {
return MessageEncryptionMechanism.integrityProtected(
negotiateSymmetricEncryptionAlgorithm().algorithmId)
}
}
fun interface EncryptionKeySelector {
fun selectEncryptionSubkeys(
encryptionCapableKeys: List<OpenPGPComponentKey>

View file

@ -8,6 +8,7 @@ import java.util.*
import org.bouncycastle.openpgp.PGPLiteralData
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
import org.pgpainless.algorithm.CompressionAlgorithm
@ -18,7 +19,7 @@ import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.util.MultiMap
data class EncryptionResult(
val encryptionAlgorithm: SymmetricKeyAlgorithm,
val encryptionMechanism: MessageEncryptionMechanism,
val compressionAlgorithm: CompressionAlgorithm,
val detachedDocumentSignatures: OpenPGPSignatureSet<OpenPGPDocumentSignature>,
val recipients: Set<SubkeyIdentifier>,
@ -27,6 +28,11 @@ data class EncryptionResult(
val fileEncoding: StreamEncoding
) {
@Deprecated(
"Use encryptionMechanism instead.", replaceWith = ReplaceWith("encryptionMechanism"))
val encryptionAlgorithm: SymmetricKeyAlgorithm?
get() = SymmetricKeyAlgorithm.fromId(encryptionMechanism.symmetricKeyAlgorithm)
@Deprecated(
"Use detachedSignatures instead", replaceWith = ReplaceWith("detachedDocumentSignatures"))
// TODO: Remove in 2.1
@ -69,7 +75,8 @@ data class EncryptionResult(
}
class Builder {
var _encryptionAlgorithm: SymmetricKeyAlgorithm? = null
var _encryptionMechanism: MessageEncryptionMechanism =
MessageEncryptionMechanism.unencrypted()
var _compressionAlgorithm: CompressionAlgorithm? = null
val detachedSignatures: MutableList<OpenPGPDocumentSignature> = mutableListOf()
@ -78,8 +85,8 @@ data class EncryptionResult(
private var _modificationDate = Date(0)
private var _encoding = StreamEncoding.BINARY
fun setEncryptionAlgorithm(encryptionAlgorithm: SymmetricKeyAlgorithm) = apply {
_encryptionAlgorithm = encryptionAlgorithm
fun setEncryptionMechanism(mechanism: MessageEncryptionMechanism): Builder = apply {
_encryptionMechanism = mechanism
}
fun setCompressionAlgorithm(compressionAlgorithm: CompressionAlgorithm) = apply {
@ -103,11 +110,10 @@ data class EncryptionResult(
}
fun build(): EncryptionResult {
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." }
return EncryptionResult(
_encryptionAlgorithm!!,
_encryptionMechanism,
_compressionAlgorithm!!,
OpenPGPSignatureSet(detachedSignatures),
recipients,

View file

@ -13,11 +13,12 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPLiteralDataGenerator
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.StreamEncoding
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.bouncycastle.extensions.pgpDataEncryptorBuilder
import org.pgpainless.util.ArmoredOutputStreamFactory
// 1 << 8 causes wrong partial body length encoding
@ -74,34 +75,29 @@ class EncryptionStream(
@Throws(IOException::class, PGPException::class)
private fun prepareEncryption() {
if (options.encryptionOptions == null) {
// No encryption options -> no encryption
resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL)
return
}
require(options.encryptionOptions.encryptionMethods.isNotEmpty()) {
"If EncryptionOptions are provided, at least one encryption method MUST be provided as well."
}
options.encryptionOptions.negotiateSymmetricEncryptionAlgorithm().let {
resultBuilder.setEncryptionAlgorithm(it)
val encryptedDataGenerator =
PGPEncryptedDataGenerator(
api.implementation.pgpDataEncryptorBuilder(it.algorithmId).apply {
setWithIntegrityPacket(true)
})
options.encryptionOptions.encryptionMethods.forEach { m ->
encryptedDataGenerator.addMethod(m)
}
options.encryptionOptions.encryptionKeyIdentifiers.forEach { r ->
resultBuilder.addRecipient(r)
}
val mechanism: MessageEncryptionMechanism =
options.encryptionOptions.negotiateEncryptionMechanism()
resultBuilder.setEncryptionMechanism(mechanism)
val encryptedDataGenerator =
PGPEncryptedDataGenerator(api.implementation.pgpDataEncryptorBuilder(mechanism))
publicKeyEncryptedStream =
encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream
->
outermostStream = stream
}
options.encryptionOptions.encryptionMethods.forEach { m ->
encryptedDataGenerator.addMethod(m)
}
options.encryptionOptions.encryptionKeyIdentifiers.forEach { r ->
resultBuilder.addRecipient(r)
}
publicKeyEncryptedStream =
encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream ->
outermostStream = stream
}
}
@Throws(IOException::class)

View file

@ -4,6 +4,7 @@
package org.pgpainless.key.info
import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites.Combination
import java.util.*
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPCertificateComponent
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
@ -40,7 +41,10 @@ abstract class KeyAccessor(
val preferredAEADCipherSuites: Set<AEADCipherMode>
get() =
component.getAEADCipherSuitePreferences(referenceTime)?.toAEADCipherModes() ?: setOf()
component.getAEADCipherSuitePreferences(referenceTime)
?.rawAlgorithms
?.map { AEADCipherMode(it) }
?.toSet() ?: setOf()
val features: Set<Feature>
get() =

View file

@ -376,6 +376,7 @@ public class EncryptDecryptTest {
eOut.write(testMessage.getBytes(StandardCharsets.UTF_8));
eOut.close();
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
DecryptionStream dIn = PGPainless.decryptAndOrVerify()
.onInputStream(bIn)
@ -389,7 +390,7 @@ public class EncryptDecryptTest {
MessageMetadata metadata = dIn.getMetadata();
MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism();
assertEquals(
MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_128.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()),
MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_192.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()),
encryptionMechanism);
}
}