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

WIP: Explore implementing a concrete HardwareSecurity implementation using Yubikit

This commit is contained in:
Paul Schaub 2025-09-06 19:51:11 +02:00
parent cccd3287c1
commit 9f0a9ccfa6
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 112 additions and 8 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<SubkeyIdentifier, PublicKeyDataDecryptorFactory>() mutableMapOf<KeyIdentifier, 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.subkeyIdentifier] = factory customDecryptorFactories[factory.keyIdentifier] = 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 subkeyIdentifier: SubkeyIdentifier abstract val keyIdentifier: KeyIdentifier
} }

View file

@ -72,8 +72,8 @@ class HardwareSecurity {
* session keys to a [DecryptionCallback]. Users can provide such a callback to delegate * session keys to a [DecryptionCallback]. Users can provide such a callback to delegate
* decryption of messages to hardware security SDKs. * decryption of messages to hardware security SDKs.
*/ */
class HardwareDataDecryptorFactory( open class HardwareDataDecryptorFactory(
override val subkeyIdentifier: SubkeyIdentifier, override val keyIdentifier: KeyIdentifier,
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(
subkeyIdentifier.keyIdentifier, keyAlgorithm, secKeyData[0], pkeskVersion) 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

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Logging
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation(project(":pgpainless-core"))
api "com.yubico.yubikit:openpgp:$yubikitVersion"
implementation "com.google.code.findbugs:jsr305:3.0.2"
}
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_modular_auto
tasks.named('jar') {
manifest {
attributes('Automatic-Module-Name': 'org.pgpainless.yubikey')
}
}

View file

@ -0,0 +1,67 @@
package org.pgpainless.yubikey
import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.openpgp.KeyRef
import com.yubico.yubikit.openpgp.OpenPgpSession
import org.bouncycastle.bcpg.KeyIdentifier
import org.pgpainless.decryption_verification.HardwareSecurity
class YubikeyDataDecryptorFactory(
smartCardConnection: SmartCardConnection,
callback: HardwareSecurity.DecryptionCallback,
keyIdentifier: KeyIdentifier,
) : HardwareSecurity.HardwareDataDecryptorFactory(keyIdentifier, callback) {
companion object {
@JvmStatic
fun createDecryptorFromConnection(smartCardConnection: SmartCardConnection): HardwareSecurity.HardwareDataDecryptorFactory {
val openpgpSession = OpenPgpSession(smartCardConnection)
val fingerprintBytes = openpgpSession.getData(KeyRef.DEC.fingerprint)
val decKeyIdentifier = KeyIdentifier(fingerprintBytes)
val rsa = true
val callback = object : HardwareSecurity.DecryptionCallback {
override fun decryptSessionKey(
keyIdentifier: KeyIdentifier,
keyAlgorithm: Int,
sessionKeyData: ByteArray,
pkeskVersion: Int
): ByteArray {
openpgpSession.verifyUserPin("asdasd".toCharArray(), true)
if(rsa) {
val decryptedSessionKey = openpgpSession.decrypt(sessionKeyData)
return decryptedSessionKey
} else {
/*
val secret = openpgpSession.decrypt(sessionKeyData)
val hashAlgorithm: Int = ecPubKey.getHashAlgorithm().toInt()
val symmetricKeyAlgorithm: Int = ecPubKey.getSymmetricKeyAlgorithm().toInt()
val userKeyingMaterial = RFC6637Utils.createUserKeyingMaterial(
this.pgpPrivKey.getPublicKeyPacket(),
BcKeyFingerprintCalculator(),
)
val rfc6637KDFCalculator = RFC6637KDFCalculator(
BcPGPDigestCalculatorProvider()[hashAlgorithm], symmetricKeyAlgorithm,
)
val key =
KeyParameter(rfc6637KDFCalculator.createKey(secret, userKeyingMaterial))
return PGPPad.unpadSessionData(
BcPublicKeyDataDecryptorFactory.unwrapSessionData(
keyEnc,
symmetricKeyAlgorithm,
key,
),
)
*/
throw UnsupportedOperationException("ECDH decryption is not yet implemented.")
}
}
}
return YubikeyDataDecryptorFactory(smartCardConnection, callback, decKeyIdentifier)
}
}
}

View file

@ -6,5 +6,6 @@ rootProject.name = 'PGPainless'
include 'pgpainless-core', include 'pgpainless-core',
'pgpainless-sop', 'pgpainless-sop',
'pgpainless-cli' 'pgpainless-cli',
'pgpainless-yubikey'

View file

@ -14,5 +14,6 @@ allprojects {
mockitoVersion = '4.5.1' mockitoVersion = '4.5.1'
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'
sopJavaVersion = '14.0.1-SNAPSHOT' sopJavaVersion = '14.0.1-SNAPSHOT'
yubikitVersion = '2.8.2'
} }
} }