mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-12-05 03:41:07 +01:00
WIP
This commit is contained in:
parent
6f3f988707
commit
510f8276e7
13 changed files with 333 additions and 81 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<HardwareTokenBackend>()
|
||||
|
||||
constructor(
|
||||
algorithmPolicy: Policy
|
||||
) : this(OpenPGPImplementation.getInstance(), algorithmPolicy)
|
||||
|
|
|
|||
|
|
@ -209,4 +209,8 @@ $algorithm of size $bitSize is not acceptable.""",
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralKeyException(message: String,
|
||||
fingerprint: OpenPgpFingerprint
|
||||
) : KeyException(message, fingerprint)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package org.pgpainless.hardware
|
||||
|
||||
interface HardwareTokenBackend {
|
||||
fun listDeviceSerials(): List<ByteArray>
|
||||
|
||||
fun listKeyFingerprints(): Map<ByteArray, List<ByteArray>>
|
||||
}
|
||||
|
|
@ -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!!)
|
||||
}
|
||||
|
|
@ -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<ByteArray> {
|
||||
return YubikeyHelper().listDevices()
|
||||
.mapNotNull { yk -> yk.info.serialNumber?.let { GnuPGDummyKeyUtil.serialToBytes(it) } }
|
||||
}
|
||||
|
||||
override fun listKeyFingerprints(): Map<ByteArray, List<ByteArray>> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Yubikey> = 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue