Ensure proper compatibility with keys with missing direct-key or certification self-sigs

This commit is contained in:
Paul Schaub 2023-11-08 15:16:41 +01:00
parent 6fdf136024
commit 620c1fc96a
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
3 changed files with 117 additions and 2 deletions

View File

@ -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)!!
}
}

View File

@ -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

View File

@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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-----"
}
}