diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt index bf66b6dc..f5f11957 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt @@ -69,6 +69,10 @@ class BcPGPHashContextContentSignerBuilder(private val messageDigest: MessageDig } } + override fun build(signatureType: Int): PGPContentSigner { + throw PGPException("Not yet implemented.") + } + companion object { @JvmStatic private fun requireFromName(digestName: String): HashAlgorithm { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt index 1fc82383..d551d648 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -24,6 +24,7 @@ import org.pgpainless.key.OpenPgpFingerprint.Companion.of import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey import org.pgpainless.policy.Policy +import org.pgpainless.signature.PGPContentSignerBuilderProviderFactory import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper @@ -312,6 +313,19 @@ class SigningOptions(private val api: PGPainless) { subpacketsCallback) } + fun addInlineSignature(hardwareBackedKey: OpenPGPComponentKey, + hardwareContentSignerBuilderProviderFactory: PGPContentSignerBuilderProviderFactory, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = addHardwareSigningMethod( + hardwareBackedKey, + hardwareContentSignerBuilderProviderFactory, + hashAlgorithm, + signatureType, + false, + subpacketsCallback) + /** * Add detached signatures with all key rings from the provided secret key ring collection. * @@ -512,6 +526,38 @@ class SigningOptions(private val api: PGPainless) { subpacketsCallback) } + fun addDetachedSignature( + hardwareBackedKey: OpenPGPComponentKey, + hardwareContentSignerBuilderProviderFactory: PGPContentSignerBuilderProviderFactory, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = addHardwareSigningMethod(hardwareBackedKey, hardwareContentSignerBuilderProviderFactory, hashAlgorithm, signatureType, true, subpacketsCallback) + + private fun addHardwareSigningMethod( + hardwareBackedKey: OpenPGPComponentKey, + hardwareContentSignerBuilderProviderFactory: PGPContentSignerBuilderProviderFactory, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + detached: Boolean, + subpacketsCallback: Callback? = null) = apply { + rejectWeakKeys(hardwareBackedKey) + val pubkey = hardwareBackedKey.pgpPublicKey + val pgpContentSignerBuilder = hardwareContentSignerBuilderProviderFactory.create(hashAlgorithm) + .get(pubkey) + + val generator = PGPSignatureGenerator( + pgpContentSignerBuilder, pubkey) + .apply { + init(signatureType.signatureType.code, pubkey) + } + + prepareSignatureGenerator(generator, pubkey, subpacketsCallback) + (signingMethods as MutableMap)[hardwareBackedKey] = + if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) + else SigningMethod.inlineSignature(generator, hashAlgorithm) + } + private fun addSigningMethod( signingKey: OpenPGPPrivateKey, hashAlgorithm: HashAlgorithm, @@ -519,22 +565,34 @@ class SigningOptions(private val api: PGPainless) { detached: Boolean, subpacketCallback: Callback? = null ) { - val signingSecretKey: PGPSecretKey = signingKey.secretKey.pgpSecretKey - val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) - val bitStrength = signingSecretKey.publicKey.bitStrength - if (!api.algorithmPolicy.publicKeyAlgorithmPolicy.isAcceptable( - publicKeyAlgorithm, bitStrength)) { - throw UnacceptableSigningKeyException( - PublicKeyAlgorithmPolicyException( - signingKey, publicKeyAlgorithm, bitStrength)) - } + rejectWeakKeys(signingKey.secretKey) val generator: PGPSignatureGenerator = createSignatureGenerator(signingKey.keyPair, hashAlgorithm, signatureType) + prepareSignatureGenerator(generator, signingKey.publicKey.pgpPublicKey, subpacketCallback) + + val signingMethod = + if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) + else SigningMethod.inlineSignature(generator, hashAlgorithm) + (signingMethods as MutableMap)[signingKey.publicKey] = signingMethod + } + + private fun rejectWeakKeys(signingKey: OpenPGPComponentKey) { + val publicKeyAlgorithm = requireFromId(signingKey.pgpPublicKey.algorithm) + val bitStrength = signingKey.pgpPublicKey.bitStrength + if (!api.algorithmPolicy.publicKeyAlgorithmPolicy.isAcceptable( + publicKeyAlgorithm, bitStrength)) { + throw UnacceptableSigningKeyException( + PublicKeyAlgorithmPolicyException( + signingKey, publicKeyAlgorithm, bitStrength)) + } + } + + private fun prepareSignatureGenerator(generator: PGPSignatureGenerator, signingKey: PGPPublicKey, subpacketCallback: Callback?) { // Subpackets val hashedSubpackets = - SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) + SignatureSubpackets.createHashedSubpackets(signingKey) val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets() if (subpacketCallback != null) { subpacketCallback.modifyHashedSubpackets(hashedSubpackets) @@ -542,11 +600,6 @@ class SigningOptions(private val api: PGPainless) { } generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)) generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)) - - val signingMethod = - if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) - else SigningMethod.inlineSignature(generator, hashAlgorithm) - (signingMethods as MutableMap)[signingKey] = signingMethod } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/PGPContentSignerBuilderProviderFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/PGPContentSignerBuilderProviderFactory.kt new file mode 100644 index 00000000..b9873940 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/PGPContentSignerBuilderProviderFactory.kt @@ -0,0 +1,9 @@ +package org.pgpainless.signature + +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider +import org.pgpainless.algorithm.HashAlgorithm + +interface PGPContentSignerBuilderProviderFactory { + + fun create(hashAlgorithm: HashAlgorithm): PGPContentSignerBuilderProvider +} diff --git a/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyPGPContentSignerBuilderProvider.kt b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyPGPContentSignerBuilderProvider.kt new file mode 100644 index 00000000..213ceb74 --- /dev/null +++ b/pgpainless-yubikey/src/main/kotlin/org/pgpainless/yubikey/YubikeyPGPContentSignerBuilderProvider.kt @@ -0,0 +1,77 @@ +package org.pgpainless.yubikey + +import com.yubico.yubikit.core.smartcard.SmartCardConnection +import com.yubico.yubikit.openpgp.OpenPgpSession +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.api.OpenPGPImplementation +import org.bouncycastle.openpgp.operator.PGPContentSigner +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.yubikey.YubikeyDataDecryptorFactory.Companion.USER_PIN +import java.io.OutputStream + +class YubikeyPGPContentSignerBuilderProvider( + val hashAlgorithm: HashAlgorithm, + private val smartcardConnection: SmartCardConnection, + private val implementation: OpenPGPImplementation = OpenPGPImplementation.getInstance() +) : PGPContentSignerBuilderProvider(hashAlgorithm.algorithmId) { + + private val softwareSignerBuilderProvider = implementation.pgpContentSignerBuilderProvider(hashAlgorithm.algorithmId) + + override fun get(publicSigningKey: PGPPublicKey): PGPContentSignerBuilder { + return object : PGPContentSignerBuilder { + + override fun build(signatureType: Int, + privateKey: PGPPrivateKey? + ): PGPContentSigner { + // Delegate software-based signing keys to the implementations default + // content signer builder provider + return softwareSignerBuilderProvider.get(publicSigningKey) + .build(signatureType, privateKey) + } + + override fun build(signatureType: Int + ): PGPContentSigner { + val digestCalculator = implementation.pgpDigestCalculatorProvider().get(hashAlgorithmId) + val openPgpSession = OpenPgpSession(smartcardConnection) + + // TODO: Move pin authorization somewhere else + openPgpSession.verifyUserPin(USER_PIN, false) + + // Return custom PGPContentSigner utilizing Yubikit for signing operations + return object : PGPContentSigner { + + override fun getOutputStream(): OutputStream { + return digestCalculator.outputStream + } + + override fun getSignature(): ByteArray { + return openPgpSession.sign(digest) + } + + override fun getDigest(): ByteArray { + return digestCalculator.digest + } + + override fun getType(): Int { + return signatureType + } + + override fun getHashAlgorithm(): Int { + return hashAlgorithmId + } + + override fun getKeyAlgorithm(): Int { + return publicSigningKey.algorithm + } + + override fun getKeyID(): Long { + return publicSigningKey.keyID + } + } + } + } + } +} diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt similarity index 76% rename from pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt rename to pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt index 8aed562d..cd3fc7de 100644 --- a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyTest.kt +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeyDecryptionTest.kt @@ -5,31 +5,17 @@ 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.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.openpgp.PGPUtil 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.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue 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.getCurveName 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 { +class YubikeyDecryptionTest { val USER_PIN: CharArray = "123456".toCharArray() val ADMIN_PIN: CharArray = "12345678".toCharArray() @@ -123,8 +109,7 @@ class YubikeyTest { "-----END PGP MESSAGE-----" @Test - fun test() { - println(CERT) + fun decryptMessageUsingYubikey() { val api = PGPainless(BcOpenPGPImplementation()) val key = api.readKey().parseKey(KEY) @@ -138,7 +123,6 @@ class YubikeyTest { .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 @@ -177,46 +161,4 @@ class YubikeyTest { 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 - - 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 - } } diff --git a/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt new file mode 100644 index 00000000..a00e2260 --- /dev/null +++ b/pgpainless-yubikey/src/test/kotlin/org/pgpainless/yubikey/YubikeySigningTest.kt @@ -0,0 +1,172 @@ +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.PGPContentSignerBuilderProvider +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter +import org.gnupg.GnuPGDummyKeyUtil +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.bouncycastle.extensions.toOpenPGPKey +import org.pgpainless.decryption_verification.ConsumerOptions +import org.pgpainless.encryption_signing.ProducerOptions +import org.pgpainless.encryption_signing.SigningOptions +import org.pgpainless.signature.PGPContentSignerBuilderProviderFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +class YubikeySigningTest { + + 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 \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 \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-----" + + @Test + fun signMessageWithYubikey() { + val api = PGPainless(BcOpenPGPImplementation()) + val key = api.readKey().parseKey(KEY) + + val signingKey = key.secretKeys[key.signingKeys[0].keyIdentifier]!! + + val privKey = signingKey.pgpSecretKey.extractPrivateKey(null) + val k = JcaPGPKeyConverter().setProvider(BouncyCastleProvider()).getPrivateKey(privKey) + val sn = 15472425 + val movedToCard = GnuPGDummyKeyUtil.modify(key) + .divertPrivateKeysToCard( + GnuPGDummyKeyUtil.KeyFilter { it.matchesExplicit(signingKey.keyIdentifier) }, byteArrayOf( + (sn shr 24).toByte(), (sn shr(16)).toByte(), (sn shr(8)).toByte(), sn.toByte() + )).toOpenPGPKey(api.implementation) + println(movedToCard.toAsciiArmoredString()) + + 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.SIG, + PrivateKeyValues.fromPrivateKey(k) + ) + val fp = signingKey.pgpPublicKey.fingerprint + openpgp.setFingerprint(com.yubico.yubikit.openpgp.KeyRef.SIG, fp) + openpgp.setGenerationTime( + com.yubico.yubikit.openpgp.KeyRef.SIG, + (signingKey.pgpPublicKey.publicKeyPacket.time.time / 1000).toInt() + ) + } + + val msgOut = ByteArrayOutputStream() + device.openConnection(SmartCardConnection::class.java).use { + val connection = it + val factory = object : PGPContentSignerBuilderProviderFactory { + override fun create(hashAlgorithm: HashAlgorithm): PGPContentSignerBuilderProvider { + return YubikeyPGPContentSignerBuilderProvider(hashAlgorithm, connection) + } + } + + val sigOut = api + .generateMessage() + .onOutputStream(msgOut) + .withOptions(ProducerOptions.sign(SigningOptions.get() + .addInlineSignature(movedToCard.signingKeys[0], factory, HashAlgorithm.SHA512))) + + sigOut.write("Hello, World!".toByteArray()) + sigOut.close() + println(msgOut) + } + + api.processMessage() + .onInputStream(ByteArrayInputStream(msgOut.toByteArray())) + .withOptions(ConsumerOptions.get() + .addVerificationCert(key.toCertificate()) + ).use { + it.readAllBytes() + it.close() + assertTrue(it.metadata.isVerifiedSigned()) + } + } +}