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

WIP: Decryption using ECDH key

This commit is contained in:
Paul Schaub 2025-09-24 20:04:13 +02:00
parent 06a6302001
commit f26d91d41f
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 231 additions and 18 deletions

View file

@ -38,7 +38,7 @@ class ConsumerOptions(private val api: PGPainless) {
private var sessionKey: SessionKey? = null private var sessionKey: SessionKey? = null
private val customDecryptorFactories = private val customDecryptorFactories =
mutableMapOf<KeyIdentifier, PublicKeyDataDecryptorFactory>() mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
private val decryptionKeys = mutableMapOf<OpenPGPKey, SecretKeyRingProtector>() private val decryptionKeys = mutableMapOf<OpenPGPKey, SecretKeyRingProtector>()
private val decryptionPassphrases = mutableSetOf<Passphrase>() private val decryptionPassphrases = mutableSetOf<Passphrase>()
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
@ -245,7 +245,7 @@ class ConsumerOptions(private val api: PGPainless) {
* @return options * @return options
*/ */
fun addCustomDecryptorFactory(factory: CustomPublicKeyDataDecryptorFactory) = apply { fun addCustomDecryptorFactory(factory: CustomPublicKeyDataDecryptorFactory) = apply {
customDecryptorFactories[factory.keyIdentifier] = factory customDecryptorFactories[factory.subkeyIdentifier] = factory
} }
/** /**

View file

@ -4,9 +4,9 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.operator.AbstractPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.AbstractPublicKeyDataDecryptorFactory
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.pgpainless.key.SubkeyIdentifier
/** /**
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message
@ -23,5 +23,5 @@ abstract class CustomPublicKeyDataDecryptorFactory : AbstractPublicKeyDataDecryp
* *
* @return subkey identifier * @return subkey identifier
*/ */
abstract val keyIdentifier: KeyIdentifier abstract val subkeyIdentifier: SubkeyIdentifier
} }

View file

@ -73,7 +73,7 @@ class HardwareSecurity {
* decryption of messages to hardware security SDKs. * decryption of messages to hardware security SDKs.
*/ */
open class HardwareDataDecryptorFactory( open class HardwareDataDecryptorFactory(
override val keyIdentifier: KeyIdentifier, override val subkeyIdentifier: SubkeyIdentifier,
private val callback: DecryptionCallback, private val callback: DecryptionCallback,
) : CustomPublicKeyDataDecryptorFactory() { ) : CustomPublicKeyDataDecryptorFactory() {
@ -110,7 +110,7 @@ class HardwareSecurity {
): ByteArray { ): ByteArray {
return try { return try {
callback.decryptSessionKey( callback.decryptSessionKey(
keyIdentifier, keyAlgorithm, secKeyData[0], pkeskVersion) subkeyIdentifier.keyIdentifier, keyAlgorithm, secKeyData[0], pkeskVersion)
} catch (e: HardwareSecurityException) { } catch (e: HardwareSecurityException) {
throw PGPException("Hardware-backed decryption failed.", e) throw PGPException("Hardware-backed decryption failed.", e)
} }

View file

@ -21,8 +21,11 @@ dependencies {
testImplementation "ch.qos.logback:logback-classic:$logbackVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation(project(":pgpainless-core")) implementation(project(":pgpainless-core"))
// api "org.bouncycastle:bcpkix-jdk18on:$bouncyCastleVersion"
api "com.yubico.yubikit:openpgp:$yubikitVersion" api "com.yubico.yubikit:openpgp:$yubikitVersion"
api "com.yubico.yubikit:desktop:$yubikitVersion"
// api "com.yubico.yubikit:piv:$yubikitVersion"
implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.code.findbugs:jsr305:3.0.2"
} }

View file

@ -4,13 +4,15 @@
package org.pgpainless.yubikey package org.pgpainless.yubikey
import com.yubico.yubikit.core.keys.PublicKeyValues
import com.yubico.yubikit.core.smartcard.SmartCardConnection import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.openpgp.KeyRef
import com.yubico.yubikit.openpgp.OpenPgpSession import com.yubico.yubikit.openpgp.OpenPgpSession
import org.bouncycastle.bcpg.ECDHPublicBCPGKey import org.bouncycastle.bcpg.ECDHPublicBCPGKey
import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
import org.bouncycastle.bcpg.PublicKeyPacket
import org.bouncycastle.crypto.params.KeyParameter import org.bouncycastle.crypto.params.KeyParameter
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
import org.bouncycastle.openpgp.operator.RFC6637Utils import org.bouncycastle.openpgp.operator.RFC6637Utils
@ -18,26 +20,30 @@ import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider 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.pgpainless.decryption_verification.HardwareSecurity import org.pgpainless.decryption_verification.HardwareSecurity
import org.pgpainless.key.OpenPgpV4Fingerprint
import org.pgpainless.key.SubkeyIdentifier
import java.util.*
class YubikeyDataDecryptorFactory( class YubikeyDataDecryptorFactory(
callback: HardwareSecurity.DecryptionCallback, callback: HardwareSecurity.DecryptionCallback,
keyIdentifier: KeyIdentifier, subkeyIdentifier: SubkeyIdentifier,
) : HardwareSecurity.HardwareDataDecryptorFactory(keyIdentifier, callback) { ) : HardwareSecurity.HardwareDataDecryptorFactory(subkeyIdentifier, callback) {
companion object { companion object {
val ADMIN_PIN: CharArray = "12345678".toCharArray()
val USER_PIN: CharArray = "123456".toCharArray()
@JvmStatic @JvmStatic
fun createDecryptorFromConnection( fun createDecryptorFromConnection(
smartCardConnection: SmartCardConnection, smartCardConnection: SmartCardConnection,
pubkey: PGPPublicKey pubkey: PGPPublicKey
): HardwareSecurity.HardwareDataDecryptorFactory { ): HardwareSecurity.HardwareDataDecryptorFactory {
val openpgpSession = OpenPgpSession(smartCardConnection) val openpgpSession = OpenPgpSession(smartCardConnection)
val fingerprintBytes = openpgpSession.getData(KeyRef.DEC.fingerprint) // openpgpSession.verifyAdminPin(ADMIN_PIN)
val decKeyIdentifier = KeyIdentifier(fingerprintBytes) val decKeyIdentifier: SubkeyIdentifier = SubkeyIdentifier(OpenPgpV4Fingerprint(pubkey))
if (!decKeyIdentifier.matches(pubkey.keyIdentifier)) {
throw IllegalArgumentException("Fingerprint mismatch.")
}
val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL
|| pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN || pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN
@ -51,7 +57,8 @@ class YubikeyDataDecryptorFactory(
pkeskVersion: Int pkeskVersion: Int
): ByteArray { ): ByteArray {
// TODO: Move user pin verification somewhere else // TODO: Move user pin verification somewhere else
openpgpSession.verifyUserPin("asdasd".toCharArray(), true) openpgpSession.verifyAdminPin(ADMIN_PIN)
openpgpSession.verifyUserPin(USER_PIN, true)
if(isRSAKey) { if(isRSAKey) {
// easy // easy
@ -76,7 +83,26 @@ 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 secret = openpgpSession.decrypt(pEnc) val x9Params =
org.bouncycastle.asn1.x9.ECNamedCurveTable.getByOIDLazy(ecPubKey.curveOID)
val publicPoint = x9Params.curve.decodePoint(pEnc)
val peerKey = JcaPGPKeyConverter().setProvider(BouncyCastleProvider())
.getPublicKey(
PGPPublicKey(
PublicKeyPacket(
pubkey.version, PublicKeyAlgorithmTags.ECDH, Date(),
ECDHPublicBCPGKey(
ecPubKey.curveOID,
publicPoint,
ecPubKey.hashAlgorithm.toInt(),
ecPubKey.symmetricKeyAlgorithm.toInt(),
),
),
BcKeyFingerprintCalculator(),
),
)
val secret = openpgpSession.decrypt(PublicKeyValues.fromPublicKey(peerKey))
// Use the shared key to decrypt the session key // Use the shared key to decrypt the session key
val hashAlgorithm: Int = ecPubKey.hashAlgorithm.toInt() val hashAlgorithm: Int = ecPubKey.hashAlgorithm.toInt()
@ -107,5 +133,4 @@ class YubikeyDataDecryptorFactory(
return YubikeyDataDecryptorFactory(callback, decKeyIdentifier) return YubikeyDataDecryptorFactory(callback, decKeyIdentifier)
} }
} }
} }

View file

@ -0,0 +1,174 @@
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 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.Test
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.OpenPGPKeyVersion
import org.pgpainless.bouncycastle.extensions.toOpenPGPKey
import org.pgpainless.decryption_verification.ConsumerOptions
import org.pgpainless.encryption_signing.EncryptionOptions
import org.pgpainless.encryption_signing.ProducerOptions
import org.pgpainless.encryption_signing.SigningOptions
import org.pgpainless.key.generation.KeySpec
import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.ecc.EllipticCurve
import org.pgpainless.key.protection.SecretKeyRingProtector
class YubikeyTest {
val USER_PIN: CharArray = "123456".toCharArray()
val ADMIN_PIN: CharArray = "12345678".toCharArray()
private val KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Comment: BB2A C3E1 E595 CD05 CFA5 CFE6 EB2E 570D 9EE2 2891\n" +
"Comment: Alice <alice@pgpainless.org>\n" +
"\n" +
"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\n" +
"1POPHzF3cMIReYhZfiJUEBV19suL\n" +
"=dA6G\n" +
"-----END PGP PRIVATE KEY BLOCK-----"
private val CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Comment: BB2A C3E1 E595 CD05 CFA5 CFE6 EB2E 570D 9EE2 2891\n" +
"Comment: Alice <alice@pgpainless.org>\n" +
"\n" +
"mJMEaNQmhRMFK4EEACMEIwQBgF429XlvPyJdpfXDxVjVEOJc04wcpfkIoX1CzIjm\n" +
"daRyv+mz2jfFZlQsCkhw2GsrPRJuKz++1JkspKU+4Vot9dIBD94Y+MoZUgHM4m0t\n" +
"ItqAdaRcxZWXDpSB0eZH3/lC+VkMUjiqK1Po4qOdZttgpLz+uHcox3gxanjyndAQ\n" +
"gVQf36u0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz7CwCEEExMKAFEFgmjU\n" +
"JoUJEOsuVw2e4iiRFqEEuyrD4eWVzQXPpc/m6y5XDZ7iKJECmwEFFQoJCAsFFgID\n" +
"AQAECwkIBwknCQEJAgkDCAECngkFiQlmAYACmQEAAEjjAgQOfqnx03DV4MUGGytd\n" +
"G02k5KbyeHbuAooyAcn8LItbaAlwzMn9Pu2uwLFEeqi0yhfF2QILwnh4QPHkhC6U\n" +
"wn5nbwIED8I1+3lbVifmpZm+1xwyU8JldGHvGDd0nkJ+wZsB22rr8dTIOgju2Z2a\n" +
"UOOBRlaCnRfPwYjlMaaacn+T2RZBQhK4lwRo1CaFEgUrgQQAIwQjBAE/KkBQQfj0\n" +
"4fzk0LWTlrbbdh95ZK4SyTLoXVY8lueyRPlTu399uPBZUxVBkJNaStvv+TgzEXXO\n" +
"1cHXOccEI9b04ACpUWbTq1ArP162V7OUuSGIAoxMw3B/jMWlNgPy/ktvv1Tidj66\n" +
"Q3QKtx6Iir340AWuMszisM/9sJRazsJxVFZIvgMBCgnCugQYEwoAKgWCaNQmhQkQ\n" +
"3dKuQkQmRdEWoQQCHpcKMNUN0N4jKkrd0q5CRCZF0QKbDAAAYPsCCOFKIUwIzOrc\n" +
"Hguz8+xc90UQRnfKpmZ61Ex8fkVN2sWRTnA10+dlb8zIDEXeUApSU8NB3W3ktTQM\n" +
"ATfKt/0xluzRAgjN1uXdxss0e0WVUuetJDkeVxBJCSuauqNqJsGQ70/5G8kQY9Wt\n" +
"Cn+5WmQDsjjThmlAlpF0LjLmbBeL7LrUz0ibariTBGjUJoUTBSuBBAAjBCMEAXJu\n" +
"E0+qkMbYx9+TmpzsD5L/8XYCyurEGj1YqTjFwoHLXQKCulsHP/UipGu1ulw1pjS9\n" +
"yXlhzMEb/k+0sxM4xdB0AHmdObt8e2xzeO8qkNm4NdvHANQb+tiQch0gIZ2nR1x+\n" +
"ZjhcRy+Dr/TCZvUk2l98YFcK0jS2qKvnd2HKxp4NCWU9wsCdBBgTCgDMBYJo1CaF\n" +
"CRBLTd5YEMyfuxahBDYBgrodBZ7KXs6saUtN3lgQzJ+7ApsCoSAEGRMKAAYFgmjU\n" +
"JoUACgkQS03eWBDMn7uLJgIInpyhe/J0Fy+likj7uzAt1XrCY44hh0Agdr8wVfpD\n" +
"haGScktFOEf8x9UOjT7eVyRVG1MmPsQqViiIKp8t+Jz3B34CBAkVYweWbPmVQyuv\n" +
"WGHaoTZY4xmbboF/VV6zIT6C1Rg8hPvSzaN70ZPMVLNqtp1AEgGxtWqL542Z49B+\n" +
"OJRoCqnxAACt9QIJAfwhrKW+CB/YI34UIVXC5cqTwvqZkSEwd5F9r0k4wob3AXD0\n" +
"SQ2bWAHY7SBZZlGK1J5UA8SVDp7xvxmQjKpweUHsAgdinLvX6C9DtLelq18l0MJI\n" +
"pjM8eSnn8ruFW7I9JvMleWkuHmzxJscbVmjru9Tzjx8xd3DCEXmIWX4iVBAVdfbL\n" +
"iw==\n" +
"=Oq+Y\n" +
"-----END PGP PUBLIC KEY BLOCK-----"
private val MSG = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"wcAQBhUEAh6XCjDVDdDeIypK3dKuQkQmRdESBCMEAV9Lm/I5jEe9t8Mdd7Pmk7S0\n" +
"3q308GnSq640CbhgORysK4+dnRYMzZFphil7dDsKWe2X7RMz7TDiPQhaoro6z0JP\n" +
"AZMx5eFiL0irdC9qV+0LvSnGJ8CyW3K15mKUomX82unAhquEhLtuPBufAN4bf2ia\n" +
"EiM85oz2U8CZ2Un48QLldDoHMKOdeAqX1xqFeBrD+ObgNsNfCLoYg4SM/EOUc06x\n" +
"U78DC23EfOI428Nfvzq1GiqVhtLAYgIJAQMNZthd/Qa2vPy8EaMLXn/NV35v4PzO\n" +
"39OYkdHRTO6g6OTI4Qf6fpXWoC8GdHIMOHGPMh2hKCXIXPEV0bncfnrUIXk9+miX\n" +
"7pFaM7kn/YGO48QUtY5ZxJdJAcjZA+vHBws8eDKC5Ajl5VYZrX187MQ+x/JID642\n" +
"QNsxUocyYwvRZenRQCuUV0vee08iLia/olzVjYQvsPYg6F/wa0KZRat2WMi/ofy9\n" +
"8C0tMUo31K4v2/z9T58DAR0P8AmLH/+196ijRbJ61U8HdiqYYPz7pevKdRB3N/0b\n" +
"dKkwF/chL+a/fSaxfAtJF2Zua4iW1OyrsbgIyXADUoS12K056A24yYE6dbMGVdhS\n" +
"+kQi5FkaOBCe1HVuETfNZ9XYV1312Dlj\n" +
"=XVu4\n" +
"-----END PGP MESSAGE-----"
@Test
fun test() {
val api = PGPainless(BcOpenPGPImplementation())
val key = api.readKey().parseKey(KEY)
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(key.toCertificate().toAsciiArmoredString())
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()
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 {
val decFac = YubikeyDataDecryptorFactory.createDecryptorFromConnection(it, decKey.pgpPublicKey)
val decIn = api.processMessage()
.onInputStream(msgIn)
.withOptions(
ConsumerOptions.get(api)
//.addDecryptionKey(api.readKey().parseKey(KEY))
.addCustomDecryptorFactory(decFac)
)
val msg = decIn.readAllBytes()
decIn.close()
assertEquals("Hello, World!\n", String(msg))
}
}
}

View file

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="STDOUT" />
</root>
</configuration>