From f26d91d41f0e497e9a9d48b0e14c52b5de989de3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Sep 2025 20:04:13 +0200 Subject: [PATCH] WIP: Decryption using ECDH key --- .../ConsumerOptions.kt | 4 +- .../CustomPublicKeyDataDecryptorFactory.kt | 4 +- .../HardwareSecurity.kt | 4 +- pgpainless-yubikey/build.gradle | 3 + .../yubikey/YubikeyDataDecryptorFactory.kt | 49 +++-- .../org/pgpainless/yubikey/YubikeyTest.kt | 174 ++++++++++++++++++ .../src/test/resources/logback.xml | 11 ++ 7 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt create mode 100644 pgpainless-yubikey/src/test/resources/logback.xml diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index 92656602..d8433f25 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -38,7 +38,7 @@ class ConsumerOptions(private val api: PGPainless) { private var sessionKey: SessionKey? = null private val customDecryptorFactories = - mutableMapOf() + mutableMapOf() private val decryptionKeys = mutableMapOf() private val decryptionPassphrases = mutableSetOf() private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE @@ -245,7 +245,7 @@ class ConsumerOptions(private val api: PGPainless) { * @return options */ fun addCustomDecryptorFactory(factory: CustomPublicKeyDataDecryptorFactory) = apply { - customDecryptorFactories[factory.keyIdentifier] = factory + customDecryptorFactories[factory.subkeyIdentifier] = factory } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt index 2281a771..cb6254dc 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt @@ -4,9 +4,9 @@ package org.pgpainless.decryption_verification -import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.operator.AbstractPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.pgpainless.key.SubkeyIdentifier /** * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message @@ -23,5 +23,5 @@ abstract class CustomPublicKeyDataDecryptorFactory : AbstractPublicKeyDataDecryp * * @return subkey identifier */ - abstract val keyIdentifier: KeyIdentifier + abstract val subkeyIdentifier: SubkeyIdentifier } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt index db8df8c9..fa6d8d93 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt @@ -73,7 +73,7 @@ class HardwareSecurity { * decryption of messages to hardware security SDKs. */ open class HardwareDataDecryptorFactory( - override val keyIdentifier: KeyIdentifier, + override val subkeyIdentifier: SubkeyIdentifier, private val callback: DecryptionCallback, ) : CustomPublicKeyDataDecryptorFactory() { @@ -110,7 +110,7 @@ class HardwareSecurity { ): ByteArray { return try { callback.decryptSessionKey( - keyIdentifier, keyAlgorithm, secKeyData[0], pkeskVersion) + subkeyIdentifier.keyIdentifier, keyAlgorithm, secKeyData[0], pkeskVersion) } catch (e: HardwareSecurityException) { throw PGPException("Hardware-backed decryption failed.", e) } diff --git a/pgpainless-yubikey/build.gradle b/pgpainless-yubikey/build.gradle index 051942a2..4986228f 100644 --- a/pgpainless-yubikey/build.gradle +++ b/pgpainless-yubikey/build.gradle @@ -21,8 +21,11 @@ dependencies { testImplementation "ch.qos.logback:logback-classic:$logbackVersion" implementation(project(":pgpainless-core")) + // api "org.bouncycastle:bcpkix-jdk18on:$bouncyCastleVersion" api "com.yubico.yubikit:openpgp:$yubikitVersion" + api "com.yubico.yubikit:desktop:$yubikitVersion" + // api "com.yubico.yubikit:piv:$yubikitVersion" implementation "com.google.code.findbugs:jsr305:3.0.2" } diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyDataDecryptorFactory.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyDataDecryptorFactory.kt index 4a4d8e0f..ade9a1b5 100644 --- a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyDataDecryptorFactory.kt +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyDataDecryptorFactory.kt @@ -4,13 +4,15 @@ package org.pgpainless.yubikey +import com.yubico.yubikit.core.keys.PublicKeyValues import com.yubico.yubikit.core.smartcard.SmartCardConnection -import com.yubico.yubikit.openpgp.KeyRef import com.yubico.yubikit.openpgp.OpenPgpSession import org.bouncycastle.bcpg.ECDHPublicBCPGKey import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.bcpg.PublicKeyAlgorithmTags +import org.bouncycastle.bcpg.PublicKeyPacket import org.bouncycastle.crypto.params.KeyParameter +import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.operator.PGPPad import org.bouncycastle.openpgp.operator.RFC6637Utils @@ -18,26 +20,30 @@ import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter import org.pgpainless.decryption_verification.HardwareSecurity +import org.pgpainless.key.OpenPgpV4Fingerprint +import org.pgpainless.key.SubkeyIdentifier +import java.util.* class YubikeyDataDecryptorFactory( callback: HardwareSecurity.DecryptionCallback, - keyIdentifier: KeyIdentifier, -) : HardwareSecurity.HardwareDataDecryptorFactory(keyIdentifier, callback) { + subkeyIdentifier: SubkeyIdentifier, +) : HardwareSecurity.HardwareDataDecryptorFactory(subkeyIdentifier, callback) { companion object { + val ADMIN_PIN: CharArray = "12345678".toCharArray() + val USER_PIN: CharArray = "123456".toCharArray() + + @JvmStatic fun createDecryptorFromConnection( smartCardConnection: SmartCardConnection, pubkey: PGPPublicKey ): HardwareSecurity.HardwareDataDecryptorFactory { val openpgpSession = OpenPgpSession(smartCardConnection) - val fingerprintBytes = openpgpSession.getData(KeyRef.DEC.fingerprint) - val decKeyIdentifier = KeyIdentifier(fingerprintBytes) - - if (!decKeyIdentifier.matches(pubkey.keyIdentifier)) { - throw IllegalArgumentException("Fingerprint mismatch.") - } + // openpgpSession.verifyAdminPin(ADMIN_PIN) + val decKeyIdentifier: SubkeyIdentifier = SubkeyIdentifier(OpenPgpV4Fingerprint(pubkey)) val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL || pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN @@ -51,7 +57,8 @@ class YubikeyDataDecryptorFactory( pkeskVersion: Int ): ByteArray { // TODO: Move user pin verification somewhere else - openpgpSession.verifyUserPin("asdasd".toCharArray(), true) + openpgpSession.verifyAdminPin(ADMIN_PIN) + openpgpSession.verifyUserPin(USER_PIN, true) if(isRSAKey) { // easy @@ -76,7 +83,26 @@ class YubikeyDataDecryptorFactory( System.arraycopy(sessionKeyData, 2 + pLen + 1, keyEnc, 0, keyLen) // perform ECDH key agreement via the Yubikey - val secret = openpgpSession.decrypt(pEnc) + val x9Params = + org.bouncycastle.asn1.x9.ECNamedCurveTable.getByOIDLazy(ecPubKey.curveOID) + val publicPoint = x9Params.curve.decodePoint(pEnc) + val peerKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()) + .getPublicKey( + PGPPublicKey( + PublicKeyPacket( + pubkey.version, PublicKeyAlgorithmTags.ECDH, Date(), + ECDHPublicBCPGKey( + ecPubKey.curveOID, + publicPoint, + ecPubKey.hashAlgorithm.toInt(), + ecPubKey.symmetricKeyAlgorithm.toInt(), + ), + ), + BcKeyFingerprintCalculator(), + ), + ) + + val secret = openpgpSession.decrypt(PublicKeyValues.fromPublicKey(peerKey)) // Use the shared key to decrypt the session key val hashAlgorithm: Int = ecPubKey.hashAlgorithm.toInt() @@ -107,5 +133,4 @@ class YubikeyDataDecryptorFactory( return YubikeyDataDecryptorFactory(callback, decKeyIdentifier) } } - } diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt new file mode 100644 index 00000000..126e6864 --- /dev/null +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt @@ -0,0 +1,174 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.keys.PrivateKeyValues +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.desktop.CompositeDevice +import com.yubico.yubikit.desktop.YubiKitManager +import com.yubico.yubikit.openpgp.OpenPgpSession +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter +import org.gnupg.GnuPGDummyKeyUtil +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.OpenPGPKeyVersion +import org.pgpainless.bouncycastle.extensions.toOpenPGPKey +import org.pgpainless.decryption_verification.ConsumerOptions +import org.pgpainless.encryption_signing.EncryptionOptions +import org.pgpainless.encryption_signing.ProducerOptions +import org.pgpainless.encryption_signing.SigningOptions +import org.pgpainless.key.generation.KeySpec +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.ecc.EllipticCurve +import org.pgpainless.key.protection.SecretKeyRingProtector + +class YubikeyTest { + + val USER_PIN: CharArray = "123456".toCharArray() + val ADMIN_PIN: CharArray = "12345678".toCharArray() + + private val KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: BB2A C3E1 E595 CD05 CFA5 CFE6 EB2E 570D 9EE2 2891\n" + + "Comment: Alice \n" + + "\n" + + "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\n" + + "1POPHzF3cMIReYhZfiJUEBV19suL\n" + + "=dA6G\n" + + "-----END PGP PRIVATE KEY BLOCK-----" + private val CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: BB2A C3E1 E595 CD05 CFA5 CFE6 EB2E 570D 9EE2 2891\n" + + "Comment: Alice \n" + + "\n" + + "mJMEaNQmhRMFK4EEACMEIwQBgF429XlvPyJdpfXDxVjVEOJc04wcpfkIoX1CzIjm\n" + + "daRyv+mz2jfFZlQsCkhw2GsrPRJuKz++1JkspKU+4Vot9dIBD94Y+MoZUgHM4m0t\n" + + "ItqAdaRcxZWXDpSB0eZH3/lC+VkMUjiqK1Po4qOdZttgpLz+uHcox3gxanjyndAQ\n" + + "gVQf36u0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz7CwCEEExMKAFEFgmjU\n" + + "JoUJEOsuVw2e4iiRFqEEuyrD4eWVzQXPpc/m6y5XDZ7iKJECmwEFFQoJCAsFFgID\n" + + "AQAECwkIBwknCQEJAgkDCAECngkFiQlmAYACmQEAAEjjAgQOfqnx03DV4MUGGytd\n" + + "G02k5KbyeHbuAooyAcn8LItbaAlwzMn9Pu2uwLFEeqi0yhfF2QILwnh4QPHkhC6U\n" + + "wn5nbwIED8I1+3lbVifmpZm+1xwyU8JldGHvGDd0nkJ+wZsB22rr8dTIOgju2Z2a\n" + + "UOOBRlaCnRfPwYjlMaaacn+T2RZBQhK4lwRo1CaFEgUrgQQAIwQjBAE/KkBQQfj0\n" + + "4fzk0LWTlrbbdh95ZK4SyTLoXVY8lueyRPlTu399uPBZUxVBkJNaStvv+TgzEXXO\n" + + "1cHXOccEI9b04ACpUWbTq1ArP162V7OUuSGIAoxMw3B/jMWlNgPy/ktvv1Tidj66\n" + + "Q3QKtx6Iir340AWuMszisM/9sJRazsJxVFZIvgMBCgnCugQYEwoAKgWCaNQmhQkQ\n" + + "3dKuQkQmRdEWoQQCHpcKMNUN0N4jKkrd0q5CRCZF0QKbDAAAYPsCCOFKIUwIzOrc\n" + + "Hguz8+xc90UQRnfKpmZ61Ex8fkVN2sWRTnA10+dlb8zIDEXeUApSU8NB3W3ktTQM\n" + + "ATfKt/0xluzRAgjN1uXdxss0e0WVUuetJDkeVxBJCSuauqNqJsGQ70/5G8kQY9Wt\n" + + "Cn+5WmQDsjjThmlAlpF0LjLmbBeL7LrUz0ibariTBGjUJoUTBSuBBAAjBCMEAXJu\n" + + "E0+qkMbYx9+TmpzsD5L/8XYCyurEGj1YqTjFwoHLXQKCulsHP/UipGu1ulw1pjS9\n" + + "yXlhzMEb/k+0sxM4xdB0AHmdObt8e2xzeO8qkNm4NdvHANQb+tiQch0gIZ2nR1x+\n" + + "ZjhcRy+Dr/TCZvUk2l98YFcK0jS2qKvnd2HKxp4NCWU9wsCdBBgTCgDMBYJo1CaF\n" + + "CRBLTd5YEMyfuxahBDYBgrodBZ7KXs6saUtN3lgQzJ+7ApsCoSAEGRMKAAYFgmjU\n" + + "JoUACgkQS03eWBDMn7uLJgIInpyhe/J0Fy+likj7uzAt1XrCY44hh0Agdr8wVfpD\n" + + "haGScktFOEf8x9UOjT7eVyRVG1MmPsQqViiIKp8t+Jz3B34CBAkVYweWbPmVQyuv\n" + + "WGHaoTZY4xmbboF/VV6zIT6C1Rg8hPvSzaN70ZPMVLNqtp1AEgGxtWqL542Z49B+\n" + + "OJRoCqnxAACt9QIJAfwhrKW+CB/YI34UIVXC5cqTwvqZkSEwd5F9r0k4wob3AXD0\n" + + "SQ2bWAHY7SBZZlGK1J5UA8SVDp7xvxmQjKpweUHsAgdinLvX6C9DtLelq18l0MJI\n" + + "pjM8eSnn8ruFW7I9JvMleWkuHmzxJscbVmjru9Tzjx8xd3DCEXmIWX4iVBAVdfbL\n" + + "iw==\n" + + "=Oq+Y\n" + + "-----END PGP PUBLIC KEY BLOCK-----" + + private val MSG = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "wcAQBhUEAh6XCjDVDdDeIypK3dKuQkQmRdESBCMEAV9Lm/I5jEe9t8Mdd7Pmk7S0\n" + + "3q308GnSq640CbhgORysK4+dnRYMzZFphil7dDsKWe2X7RMz7TDiPQhaoro6z0JP\n" + + "AZMx5eFiL0irdC9qV+0LvSnGJ8CyW3K15mKUomX82unAhquEhLtuPBufAN4bf2ia\n" + + "EiM85oz2U8CZ2Un48QLldDoHMKOdeAqX1xqFeBrD+ObgNsNfCLoYg4SM/EOUc06x\n" + + "U78DC23EfOI428Nfvzq1GiqVhtLAYgIJAQMNZthd/Qa2vPy8EaMLXn/NV35v4PzO\n" + + "39OYkdHRTO6g6OTI4Qf6fpXWoC8GdHIMOHGPMh2hKCXIXPEV0bncfnrUIXk9+miX\n" + + "7pFaM7kn/YGO48QUtY5ZxJdJAcjZA+vHBws8eDKC5Ajl5VYZrX187MQ+x/JID642\n" + + "QNsxUocyYwvRZenRQCuUV0vee08iLia/olzVjYQvsPYg6F/wa0KZRat2WMi/ofy9\n" + + "8C0tMUo31K4v2/z9T58DAR0P8AmLH/+196ijRbJ61U8HdiqYYPz7pevKdRB3N/0b\n" + + "dKkwF/chL+a/fSaxfAtJF2Zua4iW1OyrsbgIyXADUoS12K056A24yYE6dbMGVdhS\n" + + "+kQi5FkaOBCe1HVuETfNZ9XYV1312Dlj\n" + + "=XVu4\n" + + "-----END PGP MESSAGE-----" + + @Test + fun test() { + val api = PGPainless(BcOpenPGPImplementation()) + val key = api.readKey().parseKey(KEY) + + val decKey = key.secretKeys[key.encryptionKeys[0].keyIdentifier]!! + val msgIn = MSG.byteInputStream() + + val privKey = decKey.pgpSecretKey.extractPrivateKey(null) + val k = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()).getPrivateKey(privKey) + val sn = 15472425 + val movedToCard = GnuPGDummyKeyUtil.modify(key) + .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter { it.matchesExplicit(decKey.keyIdentifier) }, byteArrayOf( + (sn shr 24).toByte(), (sn shr(16)).toByte(), (sn shr(8)).toByte(), sn.toByte() + )).toOpenPGPKey(api.implementation) + println(key.toCertificate().toAsciiArmoredString()) + println(MSG) + val manager = YubiKitManager() + val device = manager.listAllDevices().entries.find { it.key is CompositeDevice }?.key + ?: throw IllegalStateException("No Yubikey attached.") + + // Write key + device.openConnection(SmartCardConnection::class.java).use { + val connection = it + val openpgp = OpenPgpSession(connection as SmartCardConnection) + openpgp.reset() + + openpgp.verifyAdminPin(ADMIN_PIN) + + openpgp.putKey( + com.yubico.yubikit.openpgp.KeyRef.DEC, + PrivateKeyValues.fromPrivateKey(k) + ) + val fp = decKey.pgpPublicKey.fingerprint + openpgp.setFingerprint(com.yubico.yubikit.openpgp.KeyRef.DEC, fp) + openpgp.setGenerationTime( + com.yubico.yubikit.openpgp.KeyRef.DEC, + (decKey.pgpPublicKey.publicKeyPacket.time.time / 1000).toInt() + ) + } + device.openConnection(SmartCardConnection::class.java).use { + val decFac = YubikeyDataDecryptorFactory.createDecryptorFromConnection(it, decKey.pgpPublicKey) + val decIn = api.processMessage() + .onInputStream(msgIn) + .withOptions( + ConsumerOptions.get(api) + //.addDecryptionKey(api.readKey().parseKey(KEY)) + .addCustomDecryptorFactory(decFac) + ) + val msg = decIn.readAllBytes() + decIn.close() + assertEquals("Hello, World!\n", String(msg)) + } + } +} diff --git a/pgpainless-yubikey/src/test/resources/logback.xml b/pgpainless-yubikey/src/test/resources/logback.xml new file mode 100644 index 00000000..8dd6e616 --- /dev/null +++ b/pgpainless-yubikey/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file