1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 10:19:39 +02:00

Sketch out yubikey-based public key decryption.

Note: Requires a modified copy of Bouncy Castle with the following changes:
* BcPublicKeyDataDecryptorFactory.unwrapSessionData(): make public
* org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator: Make class public
This commit is contained in:
Paul Schaub 2025-09-07 11:56:46 +02:00
parent 9f0a9ccfa6
commit b0c3533b8c
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311

View file

@ -1,24 +1,47 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.yubikey package org.pgpainless.yubikey
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.KeyRef
import com.yubico.yubikit.openpgp.OpenPgpSession import com.yubico.yubikit.openpgp.OpenPgpSession
import org.bouncycastle.bcpg.ECDHPublicBCPGKey
import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.operator.PGPPad
import org.bouncycastle.openpgp.operator.RFC6637Utils
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
import org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator
import org.pgpainless.decryption_verification.HardwareSecurity import org.pgpainless.decryption_verification.HardwareSecurity
class YubikeyDataDecryptorFactory( class YubikeyDataDecryptorFactory(
smartCardConnection: SmartCardConnection,
callback: HardwareSecurity.DecryptionCallback, callback: HardwareSecurity.DecryptionCallback,
keyIdentifier: KeyIdentifier, keyIdentifier: KeyIdentifier,
) : HardwareSecurity.HardwareDataDecryptorFactory(keyIdentifier, callback) { ) : HardwareSecurity.HardwareDataDecryptorFactory(keyIdentifier, callback) {
companion object { companion object {
@JvmStatic @JvmStatic
fun createDecryptorFromConnection(smartCardConnection: SmartCardConnection): HardwareSecurity.HardwareDataDecryptorFactory { fun createDecryptorFromConnection(
smartCardConnection: SmartCardConnection,
pubkey: PGPPublicKey
): HardwareSecurity.HardwareDataDecryptorFactory {
val openpgpSession = OpenPgpSession(smartCardConnection) val openpgpSession = OpenPgpSession(smartCardConnection)
val fingerprintBytes = openpgpSession.getData(KeyRef.DEC.fingerprint) val fingerprintBytes = openpgpSession.getData(KeyRef.DEC.fingerprint)
val decKeyIdentifier = KeyIdentifier(fingerprintBytes) val decKeyIdentifier = KeyIdentifier(fingerprintBytes)
val rsa = true
if (!decKeyIdentifier.matches(pubkey.keyIdentifier)) {
throw IllegalArgumentException("Fingerprint mismatch.")
}
val isRSAKey = pubkey.algorithm == PublicKeyAlgorithmTags.RSA_GENERAL
|| pubkey.algorithm == PublicKeyAlgorithmTags.RSA_SIGN
|| pubkey.algorithm == PublicKeyAlgorithmTags.RSA_ENCRYPT
val callback = object : HardwareSecurity.DecryptionCallback { val callback = object : HardwareSecurity.DecryptionCallback {
override fun decryptSessionKey( override fun decryptSessionKey(
@ -27,25 +50,49 @@ class YubikeyDataDecryptorFactory(
sessionKeyData: ByteArray, sessionKeyData: ByteArray,
pkeskVersion: Int pkeskVersion: Int
): ByteArray { ): ByteArray {
// TODO: Move user pin verification somewhere else
openpgpSession.verifyUserPin("asdasd".toCharArray(), true) openpgpSession.verifyUserPin("asdasd".toCharArray(), true)
if(rsa) {
val decryptedSessionKey = openpgpSession.decrypt(sessionKeyData)
if(isRSAKey) {
// easy
val decryptedSessionKey = openpgpSession.decrypt(sessionKeyData)
return decryptedSessionKey return decryptedSessionKey
} else { } else {
/* // meh...
val secret = openpgpSession.decrypt(sessionKeyData) val ecPubKey: ECDHPublicBCPGKey = pubkey.publicKeyPacket.key as ECDHPublicBCPGKey
val hashAlgorithm: Int = ecPubKey.getHashAlgorithm().toInt() // split session data into peer key and encrypted session key
val symmetricKeyAlgorithm: Int = ecPubKey.getSymmetricKeyAlgorithm().toInt()
// peer key
val pLen =
((((sessionKeyData[0].toInt() and 0xff) shl 8) + (sessionKeyData[1].toInt() and 0xff)) + 7) / 8
checkRange(2 + pLen + 1, sessionKeyData)
val pEnc = ByteArray(pLen)
System.arraycopy(sessionKeyData, 2, pEnc, 0, pLen)
// encrypted session key
val keyLen = sessionKeyData[pLen + 2].toInt() and 0xff
checkRange(2 + pLen + 1 + keyLen, sessionKeyData)
val keyEnc = ByteArray(keyLen)
System.arraycopy(sessionKeyData, 2 + pLen + 1, keyEnc, 0, keyLen)
// perform ECDH key agreement via the Yubikey
val secret = openpgpSession.decrypt(pEnc)
// Use the shared key to decrypt the session key
val hashAlgorithm: Int = ecPubKey.hashAlgorithm.toInt()
val symmetricKeyAlgorithm: Int = ecPubKey.symmetricKeyAlgorithm.toInt()
val userKeyingMaterial = RFC6637Utils.createUserKeyingMaterial( val userKeyingMaterial = RFC6637Utils.createUserKeyingMaterial(
this.pgpPrivKey.getPublicKeyPacket(), pubkey.publicKeyPacket,
BcKeyFingerprintCalculator(), BcKeyFingerprintCalculator(),
) )
val rfc6637KDFCalculator = RFC6637KDFCalculator( val rfc6637KDFCalculator =
BcPGPDigestCalculatorProvider()[hashAlgorithm], symmetricKeyAlgorithm, RFC6637KDFCalculator(
BcPGPDigestCalculatorProvider()[hashAlgorithm],
symmetricKeyAlgorithm,
) )
val key = val key =
KeyParameter(rfc6637KDFCalculator.createKey(secret, userKeyingMaterial)) KeyParameter(rfc6637KDFCalculator.createKey(secret, userKeyingMaterial))
return PGPPad.unpadSessionData( return PGPPad.unpadSessionData(
BcPublicKeyDataDecryptorFactory.unwrapSessionData( BcPublicKeyDataDecryptorFactory.unwrapSessionData(
keyEnc, keyEnc,
@ -53,14 +100,11 @@ class YubikeyDataDecryptorFactory(
key, key,
), ),
) )
*/ }
throw UnsupportedOperationException("ECDH decryption is not yet implemented.")
} }
} }
} return YubikeyDataDecryptorFactory(callback, decKeyIdentifier)
return YubikeyDataDecryptorFactory(smartCardConnection, callback, decKeyIdentifier)
} }
} }