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

YubikeyDataDecryptorFactory: WIP with SECP521r1 keys

This commit is contained in:
Paul Schaub 2025-11-25 15:41:09 +01:00
parent f26d91d41f
commit 89bce1ca14
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
2 changed files with 63 additions and 5 deletions

View file

@ -12,6 +12,7 @@ import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
import org.bouncycastle.bcpg.PublicKeyPacket import org.bouncycastle.bcpg.PublicKeyPacket
import org.bouncycastle.crypto.params.KeyParameter import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.operator.PGPPad import org.bouncycastle.openpgp.operator.PGPPad
@ -21,9 +22,11 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
import org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator import org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter
import org.pgpainless.bouncycastle.extensions.getCurveName
import org.pgpainless.decryption_verification.HardwareSecurity import org.pgpainless.decryption_verification.HardwareSecurity
import org.pgpainless.key.OpenPgpV4Fingerprint import org.pgpainless.key.OpenPgpV4Fingerprint
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import org.slf4j.LoggerFactory
import java.util.* import java.util.*
class YubikeyDataDecryptorFactory( class YubikeyDataDecryptorFactory(
@ -32,6 +35,10 @@ class YubikeyDataDecryptorFactory(
) : HardwareSecurity.HardwareDataDecryptorFactory(subkeyIdentifier, callback) { ) : HardwareSecurity.HardwareDataDecryptorFactory(subkeyIdentifier, callback) {
companion object { companion object {
@JvmStatic
val LOGGER = LoggerFactory.getLogger(YubikeyDataDecryptorFactory::class.java)
val ADMIN_PIN: CharArray = "12345678".toCharArray() val ADMIN_PIN: CharArray = "12345678".toCharArray()
val USER_PIN: CharArray = "123456".toCharArray() val USER_PIN: CharArray = "123456".toCharArray()
@ -42,8 +49,7 @@ class YubikeyDataDecryptorFactory(
pubkey: PGPPublicKey pubkey: PGPPublicKey
): HardwareSecurity.HardwareDataDecryptorFactory { ): HardwareSecurity.HardwareDataDecryptorFactory {
val openpgpSession = OpenPgpSession(smartCardConnection) val openpgpSession = OpenPgpSession(smartCardConnection)
// openpgpSession.verifyAdminPin(ADMIN_PIN) val decKeyIdentifier = SubkeyIdentifier(OpenPgpV4Fingerprint(pubkey))
val decKeyIdentifier: SubkeyIdentifier = SubkeyIdentifier(OpenPgpV4Fingerprint(pubkey))
val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL
|| pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN || pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN
@ -60,13 +66,18 @@ class YubikeyDataDecryptorFactory(
openpgpSession.verifyAdminPin(ADMIN_PIN) openpgpSession.verifyAdminPin(ADMIN_PIN)
openpgpSession.verifyUserPin(USER_PIN, true) openpgpSession.verifyUserPin(USER_PIN, true)
LOGGER.debug("Attempt decryption with key {}", keyIdentifier)
if(isRSAKey) { if(isRSAKey) {
// easy // easy
LOGGER.debug("Key is RSA key of length {}", pubkey.bitStrength)
val decryptedSessionKey = openpgpSession.decrypt(sessionKeyData) val decryptedSessionKey = openpgpSession.decrypt(sessionKeyData)
return decryptedSessionKey return decryptedSessionKey
} else { } else {
// meh... // meh...
val curveName = pubkey.getCurveName()
val ecPubKey: ECDHPublicBCPGKey = pubkey.publicKeyPacket.key as ECDHPublicBCPGKey val ecPubKey: ECDHPublicBCPGKey = pubkey.publicKeyPacket.key as ECDHPublicBCPGKey
LOGGER.debug("Key is ECDH key over curve $curveName")
// split session data into peer key and encrypted session key // split session data into peer key and encrypted session key
// peer key // peer key
@ -83,9 +94,8 @@ class YubikeyDataDecryptorFactory(
System.arraycopy(sessionKeyData, 2 + pLen + 1, keyEnc, 0, keyLen) System.arraycopy(sessionKeyData, 2 + pLen + 1, keyEnc, 0, keyLen)
// perform ECDH key agreement via the Yubikey // perform ECDH key agreement via the Yubikey
val x9Params = val params = ECNamedCurveTable.getParameterSpec(curveName)
org.bouncycastle.asn1.x9.ECNamedCurveTable.getByOIDLazy(ecPubKey.curveOID) val publicPoint = params.curve.decodePoint(pEnc)
val publicPoint = x9Params.curve.decodePoint(pEnc)
val peerKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()) val peerKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider())
.getPublicKey( .getPublicKey(
PGPPublicKey( PGPPublicKey(

View file

@ -5,15 +5,20 @@ import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.desktop.CompositeDevice import com.yubico.yubikit.desktop.CompositeDevice
import com.yubico.yubikit.desktop.YubiKitManager import com.yubico.yubikit.desktop.YubiKitManager
import com.yubico.yubikit.openpgp.OpenPgpSession import com.yubico.yubikit.openpgp.OpenPgpSession
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openpgp.PGPUtil
import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter
import org.gnupg.GnuPGDummyKeyUtil import org.gnupg.GnuPGDummyKeyUtil
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.OpenPGPKeyVersion import org.pgpainless.algorithm.OpenPGPKeyVersion
import org.pgpainless.bouncycastle.extensions.getCurveName
import org.pgpainless.bouncycastle.extensions.toOpenPGPKey import org.pgpainless.bouncycastle.extensions.toOpenPGPKey
import org.pgpainless.decryption_verification.ConsumerOptions import org.pgpainless.decryption_verification.ConsumerOptions
import org.pgpainless.encryption_signing.EncryptionOptions import org.pgpainless.encryption_signing.EncryptionOptions
@ -119,6 +124,7 @@ class YubikeyTest {
@Test @Test
fun test() { fun test() {
println(CERT)
val api = PGPainless(BcOpenPGPImplementation()) val api = PGPainless(BcOpenPGPImplementation())
val key = api.readKey().parseKey(KEY) val key = api.readKey().parseKey(KEY)
@ -171,4 +177,46 @@ class YubikeyTest {
assertEquals("Hello, World!\n", String(msg)) assertEquals("Hello, World!\n", String(msg))
} }
} }
val pubKeyAscii = // "modernKeyRing"
"""
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: C68A 9140 9A00 3C55 5D8A 62A5 D3ED 03F9 FF75 68D4
Comment: john doe <j.doe@example.com>
mDMEaR7iehYJKwYBBAHaRw8BAQdA5XMRsc8HUXiJkjtOSCj86v+OeemU41U08Lmi
2PQ3Tnm0HGpvaG4gZG9lIDxqLmRvZUBleGFtcGxlLmNvbT7CnwQTFgoAUQkQ0+0D
+f91aNQWoQTGipFAmgA8VV2KYqXT7QP5/3Vo1AWCaR7iegKbAQUVCgkICwUWAgMB
AAQLCQgHCScJAQkCCQMIAQKeCQWJCWYBgAKZAQAAFecBALy6FxELczpihvkVJPa2
iaV7zDcfFBvX4KyTr506hxufAP4ixT59d/QRMWmuRN6QRkRcgduLaw2l/Hs/zBuV
tQjHDsKfBBMWCgBRApsBBRUKCQgLBRYCAwEABAsJCAcJJwkBCQIJAwgBAp4JCRDT
7QP5/3Vo1BahBMaKkUCaADxVXYpipdPtA/n/dWjUBYJpHuJ7BYkAAAAAApkBAAAK
SQD9FpbJAinkmaeHluaKmiCp0HggoGF8aji9rDqSvUDtnWsA/i5I1eZ0rPvxZc6z
pIbfRHdbdgOTmTZEOOz82GQVmsMLuDgEaR7iehIKKwYBBAGXVQEFAQEHQHc9W+J1
IPl7nekdLrx5SLdvYnNNocULlqqLoDgN3fV4AwEIB8J4BBgWCgAqCRDT7QP5/3Vo
1BahBMaKkUCaADxVXYpipdPtA/n/dWjUBYJpHuJ6ApsMAACh/AEA1r+JB8uhMX7N
l4B3QOF9zLmUXhihRvE0tyY3cCCwUrYA/2yqF1mA8dHsDuDnWEUYxgX+ZpYBXr+P
j9ZKl/HoNeIOuDMEaR7iehYJKwYBBAHaRw8BAQdAQPTWzF21MpuSRjclxeAS+lZH
ulTwm/HsOaVpur8vSZ/CwC8EGBYKAKEJENPtA/n/dWjUFqEExoqRQJoAPFVdimKl
0+0D+f91aNQFgmke4noCmwJ2IAQZFgoAHQWCaR7iehYhBMqKZfP+EDi1iRE58a14
HgK5jFyDAAoJEK14HgK5jFyDoRUA/0GmLpBUVFSEdbSh+o7tz6xncAIjkm20LWIy
PF81ilR9AP0a/MVoE9ivY7HK9uu79cc2Y5IratjiXRpamYqODQutAQAA848A/Ro1
SfAfFAmMDfcbuKvpQEK/d4T3455End3ohd5TXb7VAP9/wMxzLJ1K5mE6LTQ5Hw4b
m9XtYRYVHugI27XFacaFAg==
=3yy3
-----END PGP PUBLIC KEY BLOCK-----
""".trimIndent()
@Test
fun getx9ParamsTest() {
val certificate = org.bouncycastle.openpgp.api.OpenPGPKeyReader().parseCertificate(pubKeyAscii.toByteArray())
val pubKey = certificate.getEncryptionKeys().first().getPGPPublicKey()
assertTrue(pubKey.isEncryptionKey())
val ecPubKey = pubKey.getPublicKeyPacket().getKey() as org.bouncycastle.bcpg.ECDHPublicBCPGKey
val params = ECNamedCurveTable.getParameterSpec(pubKey.getCurveName())
assertNotNull(params) // fails
}
} }