diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt index a6891f0f..11fe1643 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt @@ -67,9 +67,11 @@ abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: S info.getLatestUserIdCertification(userId).let { if (it != null) return it } } - return checkNotNull(info.latestDirectKeySelfSignature) { - "No valid signature found." + if (info.latestDirectKeySelfSignature != null) { + return info.latestDirectKeySelfSignature } + + return info.getCurrentSubkeyBindingSignature(key.subkeyId)!! } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index 9f0bbc87..ea5de18d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -172,8 +172,11 @@ class KeyRingInfo( primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { + /* throw NoSuchElementException( "No direct-key signature and no user-id signature found.") + */ + return null } if (directKeyExpirationDate != null && userIdExpirationDate == null) { return directKeyExpirationDate diff --git a/pgpainless-core/src/test/kotlin/org/pgpainless/key/KeyWithoutSelfSigsTest.kt b/pgpainless-core/src/test/kotlin/org/pgpainless/key/KeyWithoutSelfSigsTest.kt new file mode 100644 index 00000000..bb3e2bd4 --- /dev/null +++ b/pgpainless-core/src/test/kotlin/org/pgpainless/key/KeyWithoutSelfSigsTest.kt @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key + +import java.io.ByteArrayOutputStream +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.util.io.Streams +import org.junit.jupiter.api.Test +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.KeyFlag +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.eddsa.EdDSACurve +import org.pgpainless.key.generation.type.xdh.XDHSpec +import org.pgpainless.key.protection.SecretKeyRingProtector + +class KeyWithoutSelfSigsTest { + + @Test + fun signAndVerify() { + val key = PGPainless.readKeyRing().secretKeyRing(KEY) + val cert = PGPainless.extractCertificate(key!!) + + val ciphertextOut = ByteArrayOutputStream() + val encryptionStream = + PGPainless.encryptAndOrSign() + .onOutputStream(ciphertextOut) + .withOptions( + ProducerOptions.signAndEncrypt( + EncryptionOptions.encryptCommunications().addRecipient(cert), + SigningOptions.get() + .addSignature(SecretKeyRingProtector.unprotectedKeys(), key))) + encryptionStream.write("Hello, World!\n".toByteArray()) + encryptionStream.close() + + val plaintextOut = ByteArrayOutputStream() + val decryptionStream = + PGPainless.decryptAndOrVerify() + .onInputStream(ciphertextOut.toByteArray().inputStream()) + .withOptions( + ConsumerOptions.get() + .addVerificationCert(cert) + .addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys())) + Streams.pipeAll(decryptionStream, plaintextOut) + decryptionStream.close() + } + + fun generateKey() { + val key = + PGPainless.buildKeyRing() + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))) + .addSubkey( + KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) + .addSubkey( + KeySpec.getBuilder( + KeyType.XDH(XDHSpec._X25519), + KeyFlag.ENCRYPT_STORAGE, + KeyFlag.ENCRYPT_COMMS)) + .build() + .let { + var cert = PGPainless.extractCertificate(it) + cert = + PGPPublicKeyRing( + buildList { + val iterator = cert.publicKeys + val primaryKey = iterator.next() + add( + PGPPublicKey.removeCertification( + primaryKey, primaryKey.signatures.next())) + while (iterator.hasNext()) { + add(iterator.next()) + } + }) + PGPSecretKeyRing.replacePublicKeys(it, cert) + } + println(PGPainless.asciiArmor(key)) + } + + companion object { + + const val KEY = + "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: DA3E CC77 1CD6 46F0 C6C4 4FDA 86A3 7B22 7802 2FC7\n" + + "\n" + + "lFgEZUuWuhYJKwYBBAHaRw8BAQdAuXfarON/+UG1qwhVy4/VCYuEb9iLFLb8KGQt\n" + + "KfX4Se0AAQDgqGHsb2M43F+6wK5Hla+oZzFkTUsBx8HMpRx2yeQT6hFAnFgEZUuW\n" + + "uhYJKwYBBAHaRw8BAQdAx0OHISLtekltdUVGGrG/Gs3asc/jG/nqCkBEZ5uyELwA\n" + + "AP0faf8bprP3fj248/NacfynKEVnjzc1gocfhGiWrnVgAxC1iNUEGBYKAH0FAmVL\n" + + "lroCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJlS5a6AAoJED9gFx9r\n" + + "B25syqoA/0JR3Zcs6fHQ0jW7+u6330SD5h8WvG78IKsE6AfChBLXAP4hlXGidztq\n" + + "5sOHEQvXD2KPCHEJ6MuQ+rbNSSf0fQhgDwAKCRCGo3sieAIvxzmIAP9+9vRoevUM\n" + + "luQhZzQ7DgYqTCyNkeq2cpVgOfa0lyVDgwEApwrd5DlU3GorGHAQHFS6jhw1IOoG\n" + + "FGQ3zpWaOXd7XwKcXQRlS5a6EgorBgEEAZdVAQUBAQdAZIY7ISyNzp0oMoK0dgb8\n" + + "dX6t/i4Uh+l0jnxM0Z1dEB8DAQgHAAD/fhL5dzdJQ7hFhr78AmDEZKFE4txZFPvd\n" + + "ZVFvIWTthFgQ5Ih1BBgWCgAdBQJlS5a6Ap4BApsMBRYCAwEABAsJCAcFFQoJCAsA\n" + + "CgkQhqN7IngCL8cIGgEAzydjTfKvdrTvzXXu97j8TAoOxk89QnLqsM6BU0VsVmkA\n" + + "/1IzH+PXgPPW9ff+elxTi2NWmK+P033P6i5b5Jdf41YD\n" + + "=GBVS\n" + + "-----END PGP PRIVATE KEY BLOCK-----" + } +}