From 510f8276e75833d49692715e3dcc268c96222d14 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 28 Nov 2025 12:46:36 +0100 Subject: [PATCH] WIP --- .../kotlin/org/gnupg/GnuPGDummyKeyUtil.kt | 6 + .../main/kotlin/org/pgpainless/PGPainless.kt | 3 + .../org/pgpainless/exception/KeyException.kt | 4 + .../hardware/HardwareTokenBackend.kt | 7 ++ .../kotlin/org/pgpainless/yubikey/Yubikey.kt | 35 ++++++ .../yubikey/YubikeyHardwareTokenBackend.kt | 34 +++++ .../org/pgpainless/yubikey/YubikeyHelper.kt | 61 +++++++++ .../pgpainless/yubikey/YubikeyKeyGenerator.kt | 118 ++++++++++++++++++ .../yubikey/YubikeyDecryptionTest.kt | 49 ++------ .../YubikeyHardwareTokenBackendTest.kt | 22 ++++ .../yubikey/YubikeyKeyGeneratorTest.kt | 18 +++ .../pgpainless/yubikey/YubikeySigningTest.kt | 55 ++------ version.gradle | 2 +- 13 files changed, 333 insertions(+), 81 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/hardware/HardwareTokenBackend.kt create mode 100644 pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt create mode 100644 pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt create mode 100644 pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt create mode 100644 pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyKeyGenerator.kt create mode 100644 pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackendTest.kt create mode 100644 pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyKeyGeneratorTest.kt diff --git a/pgpainless-core/src/main/kotlin/org/gnupg/GnuPGDummyKeyUtil.kt b/pgpainless-core/src/main/kotlin/org/gnupg/GnuPGDummyKeyUtil.kt index eafe2cf2..3094e201 100644 --- a/pgpainless-core/src/main/kotlin/org/gnupg/GnuPGDummyKeyUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/gnupg/GnuPGDummyKeyUtil.kt @@ -56,6 +56,12 @@ class GnuPGDummyKeyUtil private constructor() { * @return builder */ @JvmStatic fun modify(secretKeys: PGPSecretKeyRing) = Builder(secretKeys) + + @JvmStatic fun serialToBytes(sn: Int) = byteArrayOf( + (sn shr 24).toByte(), + (sn shr(16)).toByte(), + (sn shr(8)).toByte(), + sn.toByte()) } class Builder(private val keys: PGPSecretKeyRing) { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index 446de46f..4aa98328 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -27,6 +27,7 @@ import org.pgpainless.bouncycastle.PolicyAdapter import org.pgpainless.bouncycastle.extensions.setAlgorithmSuite import org.pgpainless.decryption_verification.DecryptionBuilder import org.pgpainless.encryption_signing.EncryptionBuilder +import org.pgpainless.hardware.HardwareTokenBackend import org.pgpainless.key.certification.CertifyCertificate import org.pgpainless.key.generation.KeyRingBuilder import org.pgpainless.key.generation.KeyRingTemplates @@ -52,6 +53,8 @@ class PGPainless( val algorithmPolicy: Policy = Policy() ) { + val hardwareTokenBackends = mutableListOf() + constructor( algorithmPolicy: Policy ) : this(OpenPGPImplementation.getInstance(), algorithmPolicy) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/exception/KeyException.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/exception/KeyException.kt index 584de2ad..18a67c33 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/exception/KeyException.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/exception/KeyException.kt @@ -209,4 +209,8 @@ $algorithm of size $bitSize is not acceptable.""", } } } + + class GeneralKeyException(message: String, + fingerprint: OpenPgpFingerprint + ) : KeyException(message, fingerprint) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/hardware/HardwareTokenBackend.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/hardware/HardwareTokenBackend.kt new file mode 100644 index 00000000..4e60f7c6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/hardware/HardwareTokenBackend.kt @@ -0,0 +1,7 @@ +package org.pgpainless.hardware + +interface HardwareTokenBackend { + fun listDeviceSerials(): List + + fun listKeyFingerprints(): Map> +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt new file mode 100644 index 00000000..32a91e43 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt @@ -0,0 +1,35 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.YubiKeyDevice +import com.yubico.yubikit.core.keys.PrivateKeyValues +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.management.DeviceInfo +import com.yubico.yubikit.openpgp.KeyRef +import com.yubico.yubikit.openpgp.OpenPgpSession +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter +import org.gnupg.GnuPGDummyKeyUtil + +data class Yubikey(val info: DeviceInfo, val device: YubiKeyDevice) { + fun storeKeyInSlot(key: OpenPGPPrivateKey, keyRef: KeyRef, adminPin: CharArray) { + device.openConnection(SmartCardConnection::class.java).use { + // Extract private key + val privateKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()) + .getPrivateKey(key.keyPair.privateKey) + + val session = OpenPgpSession(it as SmartCardConnection) + + // Storing keys requires admin pin + session.verifyAdminPin(adminPin) + + session.putKey(keyRef, PrivateKeyValues.fromPrivateKey(privateKey)) + val fp = key.publicKey.pgpPublicKey.fingerprint + session.setFingerprint(keyRef, fp) + val time = (key.publicKey.pgpPublicKey.publicKeyPacket.time.time / 1000).toInt() + session.setGenerationTime(keyRef, time) + } + } + + val encodedSerial = GnuPGDummyKeyUtil.serialToBytes(info.serialNumber!!) +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt new file mode 100644 index 00000000..51c36732 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt @@ -0,0 +1,34 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.openpgp.KeyRef +import com.yubico.yubikit.openpgp.OpenPgpSession +import org.gnupg.GnuPGDummyKeyUtil +import org.pgpainless.hardware.HardwareTokenBackend + +class YubikeyHardwareTokenBackend : HardwareTokenBackend { + + override fun listDeviceSerials(): List { + return YubikeyHelper().listDevices() + .mapNotNull { yk -> yk.info.serialNumber?.let { GnuPGDummyKeyUtil.serialToBytes(it) } } + } + + override fun listKeyFingerprints(): Map> { + return YubikeyHelper().listDevices() + .associate { yk -> + yk.encodedSerial to yk.device.openConnection(SmartCardConnection::class.java).use { + val session = OpenPgpSession(it) + //6session.getData(KeyRef.DEC.fingerprint) + session.getData(KeyRef.SIG.fingerprint) + + + listOfNotNull( + session.getData(KeyRef.ATT.fingerprint), + session.getData(KeyRef.SIG.fingerprint), + session.getData(KeyRef.DEC.fingerprint), + session.getData(KeyRef.AUT.fingerprint) + ) + } + } + } +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt new file mode 100644 index 00000000..467992d5 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt @@ -0,0 +1,61 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.desktop.CompositeDevice +import com.yubico.yubikit.desktop.YubiKitManager +import com.yubico.yubikit.openpgp.KeyRef +import com.yubico.yubikit.openpgp.OpenPgpSession +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey +import org.gnupg.GnuPGDummyKeyUtil +import org.pgpainless.PGPainless +import org.pgpainless.bouncycastle.extensions.toOpenPGPKey +import org.pgpainless.exception.KeyException +import org.pgpainless.key.OpenPgpFingerprint + +class YubikeyHelper(private val api: PGPainless = PGPainless.getInstance()) { + + fun listDevices( + manager: YubiKitManager = YubiKitManager() + ): List = manager.listAllDevices() + .filter { it.key is CompositeDevice } + .map { Yubikey(it.value, it.key) } + + fun factoryReset(yubikey: Yubikey) { + yubikey.device.openConnection(SmartCardConnection::class.java).use { + OpenPgpSession(it).reset() + } + } + + fun moveKeyToCard(componentKey: OpenPGPPrivateKey, + yubikey: Yubikey, + adminPin: CharArray, + keyRef: KeyRef = keyRefForKey(componentKey.publicKey) + ): OpenPGPKey { + // Move private key to hardware token + yubikey.storeKeyInSlot(componentKey, keyRef, adminPin) + + // Modify software key to indicate key has been diverted to card + return indicateMovedToCard(componentKey.secretKey, yubikey) + } + + private fun indicateMovedToCard(key: OpenPGPSecretKey, yubikey: Yubikey): OpenPGPKey { + return GnuPGDummyKeyUtil.modify(key.openPGPKey) + .divertPrivateKeysToCard( + { it.matchesExplicit(key.keyIdentifier) }, + GnuPGDummyKeyUtil.serialToBytes(yubikey.info.serialNumber!!)) + .toOpenPGPKey(api.implementation) + } + + private fun keyRefForKey(key: OpenPGPComponentKey): KeyRef { + return when { + key.isSigningKey -> KeyRef.SIG + key.isEncryptionKey -> KeyRef.DEC + key.isCertificationKey -> KeyRef.ATT + else -> throw KeyException.GeneralKeyException( + "Cannot determine usage for the key.", OpenPgpFingerprint.of(key)) + } + } +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyKeyGenerator.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyKeyGenerator.kt new file mode 100644 index 00000000..69d723e6 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyKeyGenerator.kt @@ -0,0 +1,118 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.keys.PublicKeyValues +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.management.DeviceInfo +import com.yubico.yubikit.openpgp.KeyRef +import com.yubico.yubikit.openpgp.OpenPgpCurve +import com.yubico.yubikit.openpgp.OpenPgpSession +import openpgp.toSecondsPrecision +import org.bouncycastle.bcpg.PublicSubkeyPacket +import org.bouncycastle.bcpg.S2K +import org.bouncycastle.bcpg.SecretKeyPacket +import org.bouncycastle.bcpg.SecretSubkeyPacket +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter +import org.gnupg.GnuPGDummyKeyUtil +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.OpenPGPKeyVersion +import org.pgpainless.algorithm.PublicKeyAlgorithm +import java.util.* + +class YubikeyKeyGenerator(private val api: PGPainless) { + + private val converter = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()) + + fun generateModernKey(yubikey: Yubikey, + adminPin: CharArray, + keyVersion: OpenPGPKeyVersion = OpenPGPKeyVersion.v4, + creationTime: Date = Date()): OpenPGPKey { + val primaryKey: PGPSecretKey = yubikey.device.openConnection(SmartCardConnection::class.java).use { + val session = OpenPgpSession(it) + session.verifyAdminPin(adminPin) + + val pkVal = session.generateEcKey(KeyRef.ATT, OpenPgpCurve.SECP521R1) + val pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime, PublicKeyAlgorithm.ECDSA) + + toStubbedSecretKey(pubKey, yubikey.info) + } + + val signingKey: PGPSecretKey = yubikey.device.openConnection(SmartCardConnection::class.java).use { + val session = OpenPgpSession(it) + session.verifyAdminPin(adminPin) + + val pkVal = session.generateEcKey(KeyRef.SIG, OpenPgpCurve.SECP521R1) + val pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime,PublicKeyAlgorithm.ECDSA) + + toSecretSubKey(toStubbedSecretKey(pubKey, yubikey.info), yubikey.info) + } + + val encryptionKey: PGPSecretKey = yubikey.device.openConnection(SmartCardConnection::class.java).use { + val session = OpenPgpSession(it) + session.verifyAdminPin(adminPin) + + val pkVal = session.generateEcKey(KeyRef.DEC, OpenPgpCurve.X25519) + val pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime, + if (keyVersion == OpenPGPKeyVersion.v6) PublicKeyAlgorithm.X25519 + else PublicKeyAlgorithm.ECDH) + + toSecretSubKey(toStubbedSecretKey(pubKey, yubikey.info), yubikey.info) + } + + return OpenPGPKey(PGPSecretKeyRing(listOf(primaryKey, signingKey, encryptionKey))) + } + + private fun toPGPPublicKey(pkVal: PublicKeyValues, + version: OpenPGPKeyVersion, + creationTime: Date, + algorithm: PublicKeyAlgorithm + ): PGPPublicKey { + return converter.getPGPPublicKey(version.numeric, + algorithm.algorithmId, + null, + pkVal.toPublicKey(), + creationTime.toSecondsPrecision()) + } + + private fun toStubbedSecretKey(pubKey: PGPPublicKey, deviceInfo: DeviceInfo): PGPSecretKey { + return PGPSecretKey( + SecretKeyPacket( + pubKey.publicKeyPacket, + 0, + SecretKeyPacket.USAGE_SHA1, + S2K.gnuDummyS2K(S2K.GNUDummyParams.divertToCard()), + null, + GnuPGDummyKeyUtil.serialToBytes(deviceInfo.serialNumber!!) + ), + pubKey) + } + + private fun toSecretSubKey( + key: PGPSecretKey, + deviceInfo: DeviceInfo, + fingerPrintCalculator: KeyFingerPrintCalculator = api.implementation.keyFingerPrintCalculator() + ): PGPSecretKey { + val pubSubKey = PGPPublicKey( + PublicSubkeyPacket( + key.publicKey.version, + key.publicKey.algorithm, + key.publicKey.creationTime, + key.publicKey.publicKeyPacket.key), + fingerPrintCalculator) + return PGPSecretKey( + SecretSubkeyPacket( + pubSubKey.publicKeyPacket, + 0, + SecretKeyPacket.USAGE_SHA1, + S2K.gnuDummyS2K(S2K.GNUDummyParams.divertToCard()), + null, + GnuPGDummyKeyUtil.serialToBytes(deviceInfo.serialNumber!!)), + pubSubKey + ) + } +} diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt index cd3fc7de..6b55f524 100644 --- a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt @@ -1,18 +1,12 @@ 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 com.yubico.yubikit.openpgp.KeyRef 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.Assumptions.assumeTrue import org.junit.jupiter.api.Test import org.pgpainless.PGPainless -import org.pgpainless.bouncycastle.extensions.toOpenPGPKey import org.pgpainless.decryption_verification.ConsumerOptions class YubikeyDecryptionTest { @@ -111,43 +105,22 @@ class YubikeyDecryptionTest { @Test fun decryptMessageUsingYubikey() { val api = PGPainless(BcOpenPGPImplementation()) + api.hardwareTokenBackends.add(YubikeyHardwareTokenBackend()) val key = api.readKey().parseKey(KEY) + val helper = YubikeyHelper(api) + val devices = helper.listDevices() + assumeTrue(devices.isNotEmpty()) + val yubikey = devices.first() + 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(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() + val divertToCard = yubikey.storeKeyInSlot(decKey.unlock(), KeyRef.DEC, ADMIN_PIN) - 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 { + // Decrypt + yubikey.device.openConnection(SmartCardConnection::class.java).use { val decFac = YubikeyDataDecryptorFactory.createDecryptorFromConnection(it, decKey.pgpPublicKey) val decIn = api.processMessage() .onInputStream(msgIn) diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackendTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackendTest.kt new file mode 100644 index 00000000..5d3ecf28 --- /dev/null +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackendTest.kt @@ -0,0 +1,22 @@ +package org.pgpainless.yubikey + +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Test + +class YubikeyHardwareTokenBackendTest { + + val backend = YubikeyHardwareTokenBackend() + + @Test + fun testListDeviceSerials() { + assertNotNull(backend.listDeviceSerials()) + assumeTrue(backend.listDeviceSerials().isNotEmpty()) + } + + @Test + fun testListKeys() { + val keys = backend.listKeyFingerprints() + assumeTrue(keys.isNotEmpty()) + } +} diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyKeyGeneratorTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyKeyGeneratorTest.kt new file mode 100644 index 00000000..223b6fa7 --- /dev/null +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyKeyGeneratorTest.kt @@ -0,0 +1,18 @@ +package org.pgpainless.yubikey + +import org.junit.jupiter.api.Test +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.OpenPGPKeyVersion +import java.util.* + +class YubikeyKeyGeneratorTest { + val ADMIN_PIN: CharArray = "12345678".toCharArray() + + @Test + fun generateKey() { + val helper = YubikeyHelper() + val keyGen = YubikeyKeyGenerator(PGPainless.getInstance()) + val key = keyGen.generateModernKey(helper.listDevices().first(), ADMIN_PIN, OpenPGPKeyVersion.v4, Date()) + println(key.toAsciiArmoredString()) + } +} diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt index a00e2260..7b3c4f09 100644 --- a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt @@ -1,20 +1,15 @@ 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.KeyRef import com.yubico.yubikit.openpgp.OpenPgpSession -import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter -import org.gnupg.GnuPGDummyKeyUtil import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test import org.pgpainless.PGPainless import org.pgpainless.algorithm.HashAlgorithm -import org.pgpainless.bouncycastle.extensions.toOpenPGPKey import org.pgpainless.decryption_verification.ConsumerOptions import org.pgpainless.encryption_signing.ProducerOptions import org.pgpainless.encryption_signing.SigningOptions @@ -101,43 +96,19 @@ class YubikeySigningTest { @Test fun signMessageWithYubikey() { val api = PGPainless(BcOpenPGPImplementation()) + val helper = YubikeyHelper(api) + + val devices = helper.listDevices() + assumeTrue(devices.isNotEmpty()) + + val yubikey = devices.first() + val device = yubikey.device val key = api.readKey().parseKey(KEY) val signingKey = key.secretKeys[key.signingKeys[0].keyIdentifier]!! - val privKey = signingKey.pgpSecretKey.extractPrivateKey(null) - val k = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()).getPrivateKey(privKey) - val sn = 15472425 - val movedToCard = GnuPGDummyKeyUtil.modify(key) - .divertPrivateKeysToCard( - GnuPGDummyKeyUtil.KeyFilter { it.matchesExplicit(signingKey.keyIdentifier) }, byteArrayOf( - (sn shr 24).toByte(), (sn shr(16)).toByte(), (sn shr(8)).toByte(), sn.toByte() - )).toOpenPGPKey(api.implementation) - println(movedToCard.toAsciiArmoredString()) - - 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.SIG, - PrivateKeyValues.fromPrivateKey(k) - ) - val fp = signingKey.pgpPublicKey.fingerprint - openpgp.setFingerprint(com.yubico.yubikit.openpgp.KeyRef.SIG, fp) - openpgp.setGenerationTime( - com.yubico.yubikit.openpgp.KeyRef.SIG, - (signingKey.pgpPublicKey.publicKeyPacket.time.time / 1000).toInt() - ) - } + val signingKeyOnCard = helper.moveKeyToCard(signingKey.unlock(), yubikey, ADMIN_PIN) + println(signingKeyOnCard.toAsciiArmoredString()) val msgOut = ByteArrayOutputStream() device.openConnection(SmartCardConnection::class.java).use { @@ -152,7 +123,7 @@ class YubikeySigningTest { .generateMessage() .onOutputStream(msgOut) .withOptions(ProducerOptions.sign(SigningOptions.get() - .addInlineSignature(movedToCard.signingKeys[0], factory, HashAlgorithm.SHA512))) + .addInlineSignature(signingKeyOnCard.signingKeys[0], factory, HashAlgorithm.SHA512))) sigOut.write("Hello, World!".toByteArray()) sigOut.close() @@ -162,7 +133,7 @@ class YubikeySigningTest { api.processMessage() .onInputStream(ByteArrayInputStream(msgOut.toByteArray())) .withOptions(ConsumerOptions.get() - .addVerificationCert(key.toCertificate()) + .addVerificationCert(signingKeyOnCard.toCertificate()) ).use { it.readAllBytes() it.close() diff --git a/version.gradle b/version.gradle index 2b20d26f..fdc3745e 100644 --- a/version.gradle +++ b/version.gradle @@ -14,6 +14,6 @@ allprojects { mockitoVersion = '4.5.1' slf4jVersion = '1.7.36' sopJavaVersion = '14.0.3' - yubikitVersion = '2.8.2' + yubikitVersion = '2.9.0' } }