1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-09 22:01:10 +01:00

WIP: Create HardwreKey/HardwareToken interfaces

This commit is contained in:
Paul Schaub 2025-12-09 10:58:02 +01:00
parent 84c30f9629
commit 5b55150234
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
5 changed files with 72 additions and 29 deletions

View file

@ -0,0 +1,11 @@
package org.bouncycastle.openpgp.hardware
/**
* Represents a single cryptographic key stored on a [HardwareToken].
*
* @param label 20 octets of label, such as the keys v4 fingerprint.
* @param identifier slot identifier
*/
open class HardwareKey<I>(val label: ByteArray, val identifier: I) {
}

View file

@ -0,0 +1,6 @@
package org.bouncycastle.openpgp.hardware
interface HardwareToken {
val keys: Map<ByteArray, HardwareKey>
}

View file

@ -10,20 +10,55 @@ import com.yubico.yubikit.management.DeviceInfo
import com.yubico.yubikit.openpgp.KeyRef
import com.yubico.yubikit.openpgp.OpenPgpSession
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey
import org.bouncycastle.openpgp.hardware.HardwareKey
import org.bouncycastle.openpgp.hardware.HardwareToken
import org.gnupg.GnuPGDummyKeyUtil
data class Yubikey(val info: DeviceInfo, val device: YubiKeyDevice) {
data class Yubikey(val info: DeviceInfo, val device: YubiKeyDevice) : HardwareToken {
override val keys: Map<ByteArray, HardwareKey<KeyRef>>
get() = getFingerprints().values
.filterNotNull()
.associateWith { HardwareKey(it, keyRefForFingerprint(it)!!) }
fun openSession(): OpenPgpSession {
return OpenPgpSession(device.openConnection(SmartCardConnection::class.java))
}
fun factoryReset() {
openSession().use {
it.reset()
}
}
fun storeKeyInSlot(key: OpenPGPPrivateKey, keyRef: KeyRef, adminPin: CharArray) {
device.openConnection(SmartCardConnection::class.java).use {
val session = OpenPgpSession(it as SmartCardConnection)
openSession().use {
// Storing keys requires admin pin
session.verifyAdminPin(adminPin)
it.verifyAdminPin(adminPin)
session.writePrivateKey(key, keyRef)
session.writeFingerprint(key.publicKey, keyRef)
session.writeGenerationTime(key.publicKey, keyRef)
it.writePrivateKey(key, keyRef)
it.writeFingerprint(key.publicKey, keyRef)
it.writeGenerationTime(key.publicKey, keyRef)
}
}
fun keyRefForFingerprint(fingerprint: ByteArray): KeyRef? {
return getFingerprints().entries
.find { it.value?.contentEquals(fingerprint) ?: false }
?.key
}
fun getFingerprints(): Map<KeyRef, ByteArray?> {
return openSession().use {
// session.getData(KeyRef.DEC.fingerprint)
val ddo = it.applicationRelatedData.discretionary
buildMap {
put(KeyRef.ATT, ddo.getFingerprint(KeyRef.ATT))
put(KeyRef.SIG, ddo.getFingerprint(KeyRef.SIG))
put(KeyRef.DEC, ddo.getFingerprint(KeyRef.DEC))
put(KeyRef.AUT, ddo.getFingerprint(KeyRef.AUT))
}
}
}

View file

@ -51,18 +51,16 @@ class YubikeyHardwareTokenBackend : HardwareTokenBackend {
override fun listKeyFingerprints(): Map<ByteArray, List<ByteArray>> {
return YubikeyHelper().listDevices().associate { yk ->
yk.encodedSerialNumber to
yk.device.openConnection(SmartCardConnection::class.java).use {
val session = OpenPgpSession(it)
// session.getData(KeyRef.DEC.fingerprint)
val ddo = session.applicationRelatedData.discretionary
yk.encodedSerialNumber to yk.openSession().use {
// session.getData(KeyRef.DEC.fingerprint)
val ddo = it.applicationRelatedData.discretionary
listOfNotNull(
ddo.getFingerprint(KeyRef.ATT),
ddo.getFingerprint(KeyRef.SIG),
ddo.getFingerprint(KeyRef.DEC),
ddo.getFingerprint(KeyRef.AUT))
}
listOfNotNull(
ddo.getFingerprint(KeyRef.ATT),
ddo.getFingerprint(KeyRef.SIG),
ddo.getFingerprint(KeyRef.DEC),
ddo.getFingerprint(KeyRef.AUT))
}
}
}
}

View file

@ -4,11 +4,9 @@
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
@ -28,20 +26,15 @@ class YubikeyHelper(private val api: PGPainless = PGPainless.getInstance()) {
.filter { it.key is CompositeDevice }
.map { Yubikey(it.value, it.key) }
} catch (e: RuntimeException) {
// If there are no tokens, yubikit throws a RuntimeException :/
emptyList()
}
fun factoryReset(yubikey: Yubikey) {
yubikey.device.openConnection(SmartCardConnection::class.java).use {
OpenPgpSession(it).reset()
}
}
fun moveToYubikey(
componentKey: OpenPGPPrivateKey,
yubikey: Yubikey,
adminPin: CharArray,
keyRef: KeyRef = keyRefForKey(componentKey.publicKey)
keyRef: KeyRef = guessKeyRefForKey(componentKey.publicKey)
): OpenPGPKey {
// Move private key to hardware token
yubikey.storeKeyInSlot(componentKey, keyRef, adminPin)
@ -58,7 +51,7 @@ class YubikeyHelper(private val api: PGPainless = PGPainless.getInstance()) {
.toOpenPGPKey(api.implementation)
}
private fun keyRefForKey(key: OpenPGPComponentKey): KeyRef {
private fun guessKeyRefForKey(key: OpenPGPComponentKey): KeyRef {
return when {
key.isSigningKey -> KeyRef.SIG
key.isEncryptionKey -> KeyRef.DEC