diff --git a/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareKey.kt b/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareKey.kt new file mode 100644 index 00000000..1595cee2 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareKey.kt @@ -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(val label: ByteArray, val identifier: I) { + +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareToken.kt b/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareToken.kt new file mode 100644 index 00000000..f81752d0 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/bouncycastle/openpgp/hardware/HardwareToken.kt @@ -0,0 +1,6 @@ +package org.bouncycastle.openpgp.hardware + +interface HardwareToken { + + val keys: 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 index fb79e822..9b430742 100644 --- a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/Yubikey.kt @@ -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> + 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 { + 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)) + } } } diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt index fc1edac0..237536e5 100644 --- a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHardwareTokenBackend.kt @@ -51,18 +51,16 @@ class YubikeyHardwareTokenBackend : HardwareTokenBackend { override fun listKeyFingerprints(): Map> { 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)) + } } } } diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt index 00c984c3..ac06b790 100644 --- a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyHelper.kt @@ -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