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:
parent
cccd3287c1
commit
9f0a9ccfa6
7 changed files with 112 additions and 8 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
35
pgpainless-yubikey/build.gradle
Normal file
35
pgpainless-yubikey/build.gradle
Normal 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')
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,5 +6,6 @@ rootProject.name = 'PGPainless'
|
||||||
|
|
||||||
include 'pgpainless-core',
|
include 'pgpainless-core',
|
||||||
'pgpainless-sop',
|
'pgpainless-sop',
|
||||||
'pgpainless-cli'
|
'pgpainless-cli',
|
||||||
|
'pgpainless-yubikey'
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue