mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-09 02:09:38 +02:00
First draft for SEIPD2 negotiation
This commit is contained in:
parent
7a33e84497
commit
b41fb2c468
6 changed files with 124 additions and 49 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue