From 1dd666d32b4d154ad8e6613e4ab1bf976b1ce03b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 15:28:06 +0200 Subject: [PATCH] Implement crude update-key command (only merges certs for now) --- .../main/kotlin/org/pgpainless/sop/SOPImpl.kt | 2 +- .../org/pgpainless/sop/UpdateKeyImpl.kt | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/SOPImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/SOPImpl.kt index c3ee703c..d2d3bf35 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/SOPImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/SOPImpl.kt @@ -66,7 +66,7 @@ class SOPImpl( override fun revokeKey(): RevokeKey = RevokeKeyImpl(api) - override fun updateKey(): UpdateKey? = null + override fun updateKey(): UpdateKey = UpdateKeyImpl(api) override fun validateUserId(): ValidateUserId = ValidateUserIdImpl(api) diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt new file mode 100644 index 00000000..28f4a296 --- /dev/null +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: CC0-1.0 + +package org.pgpainless.sop + +import java.io.InputStream +import java.io.OutputStream +import org.bouncycastle.bcpg.KeyIdentifier +import org.bouncycastle.bcpg.PacketFormat +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.api.OpenPGPCertificate +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.pgpainless.PGPainless +import org.pgpainless.util.ArmoredOutputStreamFactory +import org.pgpainless.util.Passphrase +import sop.Ready +import sop.operation.UpdateKey + +class UpdateKeyImpl(private val api: PGPainless) : UpdateKey { + + private var armor = true + private var addCapabilities = true + private var signingOnly = false + private val protector: MatchMakingSecretKeyRingProtector = MatchMakingSecretKeyRingProtector() + + private val mergeCerts: MutableMap = mutableMapOf() + + override fun key(key: InputStream): Ready { + return object : Ready() { + override fun writeTo(outputStream: OutputStream) { + val out = + if (armor) { + ArmoredOutputStreamFactory.get(outputStream) + } else { + outputStream + } + + val keyList = api.readKey().parseKeys(key) + for (k in keyList) { + val updatedKey: OpenPGPKey = + if (mergeCerts[k.keyIdentifier] == null) { + k + } else { + val updatedCert: OpenPGPCertificate = + api.mergeCertificate( + k.toCertificate(), mergeCerts[k.keyIdentifier]!!) + api.toKey( + PGPSecretKeyRing.replacePublicKeys( + k.pgpSecretKeyRing, updatedCert.pgpPublicKeyRing)) + } + out.write(updatedKey.getEncoded(PacketFormat.CURRENT)) + } + + if (armor) { + out.close() + } + outputStream.close() + } + } + } + + override fun mergeCerts(certs: InputStream): UpdateKey = apply { + val certList = api.readKey().parseCertificates(certs) + for (cert in certList) { + if (mergeCerts[cert.keyIdentifier] == null) { + mergeCerts[cert.keyIdentifier] = cert + } else { + val existing = mergeCerts[cert.keyIdentifier]!! + mergeCerts[cert.keyIdentifier] = api.mergeCertificate(existing, cert) + } + } + } + + override fun noAddedCapabilities(): UpdateKey = apply { addCapabilities = false } + + override fun noArmor(): UpdateKey = apply { armor = false } + + override fun signingOnly(): UpdateKey = apply { signingOnly = true } + + override fun withKeyPassword(password: ByteArray): UpdateKey = apply { + protector.addPassphrase(Passphrase.fromPassword(String(password))) + } +}