mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-12-05 03:41:07 +01:00
Update yubikit to 3.0.0, fix key generation, refactor code
This commit is contained in:
parent
bb796a6819
commit
84c30f9629
9 changed files with 101 additions and 28 deletions
|
|
@ -23,8 +23,12 @@ dependencies {
|
|||
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:openpgp:$yubikitVersion") {
|
||||
exclude group: "org.junit", module: "junit-bom"
|
||||
}
|
||||
api ("com.yubico.yubikit:desktop:$yubikitVersion") {
|
||||
exclude group: "org.junit", module: "junit-bom"
|
||||
}
|
||||
// api "com.yubico.yubikit:piv:$yubikitVersion"
|
||||
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
|
|
|
|||
|
|
@ -5,36 +5,25 @@
|
|||
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)
|
||||
session.writePrivateKey(key, keyRef)
|
||||
session.writeFingerprint(key.publicKey, keyRef)
|
||||
session.writeGenerationTime(key.publicKey, keyRef)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.yubikey
|
||||
|
||||
import com.yubico.yubikit.core.keys.PrivateKeyValues
|
||||
import com.yubico.yubikit.openpgp.KeyRef
|
||||
import com.yubico.yubikit.openpgp.OpenPgpSession
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
||||
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter
|
||||
|
||||
/**
|
||||
* Writes the private key bytes of an [OpenPGPPrivateKey] to the slot identified by the provided
|
||||
* [KeyRef]. This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writePrivateKey(key: OpenPGPPrivateKey, keyRef: KeyRef) {
|
||||
writePrivateKey(key.keyPair.privateKey, keyRef)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the private key bytes of a [PGPPrivateKey] to the slot identified by the provided
|
||||
* [KeyRef]. This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writePrivateKey(key: PGPPrivateKey, keyRef: KeyRef) {
|
||||
val privateKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()).getPrivateKey(key)
|
||||
putKey(keyRef, PrivateKeyValues.fromPrivateKey(privateKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the 20-octet fingerprint of an OpenPGP key to the slot identified by the provided
|
||||
* [KeyRef]. This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writeFingerprint(
|
||||
key: OpenPGPCertificate.OpenPGPComponentKey,
|
||||
keyRef: KeyRef
|
||||
) = writeFingerprint(key.pgpPublicKey, keyRef)
|
||||
|
||||
/**
|
||||
* Writes the 20-octet fingerprint of an OpenPGP key to the slot identified by the provided
|
||||
* [KeyRef]. This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writeFingerprint(key: PGPPublicKey, keyRef: KeyRef) {
|
||||
setFingerprint(keyRef, key.fingerprint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the key generation time of an OpenPGP key to the slot identified by the provided [KeyRef].
|
||||
* This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writeGenerationTime(
|
||||
key: OpenPGPCertificate.OpenPGPComponentKey,
|
||||
keyRef: KeyRef
|
||||
) = writeGenerationTime(key.pgpPublicKey, keyRef)
|
||||
|
||||
/**
|
||||
* Writes the key generation time of an OpenPGP key to the slot identified by the provided [KeyRef].
|
||||
* This method requires the session to have a verified admin pin.
|
||||
*/
|
||||
internal fun OpenPgpSession.writeGenerationTime(key: PGPPublicKey, keyRef: KeyRef) {
|
||||
val time = (key.publicKeyPacket.time.time / 1000).toInt()
|
||||
setGenerationTime(keyRef, time)
|
||||
}
|
||||
|
|
@ -55,13 +55,13 @@ class YubikeyHardwareTokenBackend : HardwareTokenBackend {
|
|||
yk.device.openConnection(SmartCardConnection::class.java).use {
|
||||
val session = OpenPgpSession(it)
|
||||
// session.getData(KeyRef.DEC.fingerprint)
|
||||
session.getData(KeyRef.SIG.fingerprint)
|
||||
val ddo = session.applicationRelatedData.discretionary
|
||||
|
||||
listOfNotNull(
|
||||
session.getData(KeyRef.ATT.fingerprint),
|
||||
session.getData(KeyRef.SIG.fingerprint),
|
||||
session.getData(KeyRef.DEC.fingerprint),
|
||||
session.getData(KeyRef.AUT.fingerprint))
|
||||
ddo.getFingerprint(KeyRef.ATT),
|
||||
ddo.getFingerprint(KeyRef.SIG),
|
||||
ddo.getFingerprint(KeyRef.DEC),
|
||||
ddo.getFingerprint(KeyRef.AUT))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@ 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) }
|
||||
try {
|
||||
manager
|
||||
.listAllDevices()
|
||||
.filter { it.key is CompositeDevice }
|
||||
.map { Yubikey(it.value, it.key) }
|
||||
} catch (e: RuntimeException) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
fun factoryReset(yubikey: Yubikey) {
|
||||
yubikey.device.openConnection(SmartCardConnection::class.java).use {
|
||||
|
|
|
|||
|
|
@ -44,16 +44,22 @@ class YubikeyKeyGenerator(private val api: PGPainless) {
|
|||
|
||||
var pkVal = session.generateEcKey(KeyRef.ATT, OpenPgpCurve.SECP521R1)
|
||||
var pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime, PublicKeyAlgorithm.ECDSA)
|
||||
session.writeFingerprint(pubKey, KeyRef.ATT)
|
||||
session.writeGenerationTime(pubKey, KeyRef.ATT)
|
||||
|
||||
val primarykey = toExternalSecretKey(pubKey, yubikey.info)
|
||||
|
||||
pkVal = session.generateEcKey(KeyRef.SIG, OpenPgpCurve.SECP521R1)
|
||||
pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime, PublicKeyAlgorithm.ECDSA)
|
||||
session.writeFingerprint(pubKey, KeyRef.SIG)
|
||||
session.writeGenerationTime(pubKey, KeyRef.SIG)
|
||||
|
||||
val signingKey = toSecretSubKey(toExternalSecretKey(pubKey, yubikey.info), yubikey.info)
|
||||
|
||||
pkVal = session.generateEcKey(KeyRef.DEC, OpenPgpCurve.SECP521R1)
|
||||
pubKey = toPGPPublicKey(pkVal, keyVersion, creationTime, PublicKeyAlgorithm.ECDH)
|
||||
session.writeFingerprint(pubKey, KeyRef.DEC)
|
||||
session.writeGenerationTime(pubKey, KeyRef.DEC)
|
||||
|
||||
val encryptionKey =
|
||||
toSecretSubKey(toExternalSecretKey(pubKey, yubikey.info), yubikey.info)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ package org.pgpainless.yubikey
|
|||
import org.gnupg.GnuPGDummyKeyUtil
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Assumptions.assumeTrue
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class YubikeyHardwareTokenBackendTest : YubikeyTest() {
|
||||
|
|
@ -22,7 +21,6 @@ class YubikeyHardwareTokenBackendTest : YubikeyTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("because yubikit-android 2.9.0 cannot extract fingerprints")
|
||||
fun testListKeys() {
|
||||
val keys = backend.listKeyFingerprints()
|
||||
assumeTrue(keys.isNotEmpty())
|
||||
|
|
|
|||
|
|
@ -14,14 +14,19 @@ class YubikeyKeyGeneratorTest : YubikeyTest() {
|
|||
|
||||
@Test
|
||||
fun generateKey() {
|
||||
val backend = YubikeyHardwareTokenBackend()
|
||||
val keyGen = YubikeyKeyGenerator(PGPainless.getInstance())
|
||||
val key = keyGen.generateModernKey(yubikey, adminPin, OpenPGPKeyVersion.v4, Date())
|
||||
|
||||
println(key.toAsciiArmoredString())
|
||||
// TODO: More thorough checking once key generation is implemented with binding signatures,
|
||||
// userids and other metadata
|
||||
val fingerprints = backend.listKeyFingerprints().entries.first().value
|
||||
for (subkey in key.secretKeys) {
|
||||
assertTrue(subkey.value.hasExternalSecretKey())
|
||||
assertTrue {
|
||||
fingerprints.any { it.contentEquals(subkey.value.pgpPublicKey.fingerprint) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ allprojects {
|
|||
mockitoVersion = '4.5.1'
|
||||
slf4jVersion = '1.7.36'
|
||||
sopJavaVersion = '14.0.3'
|
||||
yubikitVersion = '2.9.0'
|
||||
yubikitVersion = '3.0.0'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue