From 9e16fc37d710ae0d9e390a4ae788680d2c3307bf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Sep 2021 16:58:45 +0200 Subject: [PATCH] Fix OpenPgpMetadata.isSigned() returning false on signed message due to missing verification cert --- .../DecryptionStreamFactory.java | 6 +- .../SignatureVerification.java | 3 +- ...eVerificationWithoutCertIsStillSigned.java | 85 +++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSigned.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index 746ecb32..9ffc6c7f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -56,6 +56,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.exception.MessageNotIntegrityProtectedException; import org.pgpainless.exception.MissingDecryptionMethodException; import org.pgpainless.exception.MissingLiteralDataException; +import org.pgpainless.exception.SignatureValidationException; import org.pgpainless.exception.UnacceptableAlgorithmException; import org.pgpainless.exception.WrongConsumingMethodException; import org.pgpainless.implementation.ImplementationFactory; @@ -107,6 +108,8 @@ public final class DecryptionStreamFactory { long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature); PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId); if (signingKeyRing == null) { + SignatureValidationException ex = new SignatureValidationException("Missing verification certificate " + Long.toHexString(issuerKeyId)); + resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex); continue; } PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId); @@ -497,7 +500,8 @@ public final class DecryptionStreamFactory { // Find public key PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId); if (verificationKeyRing == null) { - LOGGER.debug("Missing verification key from {}", Long.toHexString(keyId)); + SignatureValidationException ex = new SignatureValidationException("Missing verification certificate " + Long.toHexString(keyId)); + resultBuilder.addInvalidInbandSignature(new SignatureVerification(null, null), ex); return; } PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId); diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java index 4d151394..4f9c68f0 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java @@ -38,7 +38,7 @@ public class SignatureVerification { * @param signature PGPSignature object * @param signingKey identifier of the signing key */ - public SignatureVerification(PGPSignature signature, @Nullable SubkeyIdentifier signingKey) { + public SignatureVerification(@Nullable PGPSignature signature, @Nullable SubkeyIdentifier signingKey) { this.signature = signature; this.signingKey = signingKey; } @@ -48,6 +48,7 @@ public class SignatureVerification { * * @return signature */ + @Nullable public PGPSignature getSignature() { return signature; } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSigned.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSigned.java new file mode 100644 index 00000000..2431c244 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSigned.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.decryption_verification; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.bouncycastle.openpgp.PGPException; +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.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class SignedMessageVerificationWithoutCertIsStillSigned { + + private static final String message = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "owGbwMvMwCGmFN+gfIiXM5zxtG4SQ2Iw74rgzPS81BSFktSKEoW0/CKFlNS0xNKc\n" + + "Eoe0nPzy5KLKghK9ktTiEq6OXhYGMQ4GUzFFFtvXL7+VX9252+LpIheYcaxMQLMO\n" + + "iMtg183AxSkAUynizshwbBMnx4e4tn6NgJYtG/od3HL1y26GvpgqUtr2o37HpC+v\n" + + "GRmudmly/g+Osdt3t6Rb+8t8i8Y94ZJ3P/zNlk015FihXM0JAA==\n" + + "=A8uF\n" + + "-----END PGP MESSAGE-----\n"; + private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "\n" + + "lIYEYIq7phYJKwYBBAHaRw8BAQdAat45rrh+gvQwWwJw5eScq3Pdxt/8d+lWNVSm\n" + + "kImXcRP+CQMCvWfx3mzDdd5g6c59LcPqADK0p70/7ZmTkp3ZC1YViTprg4tQt/PF\n" + + "QJL+VPCG+BF9bWyFcfxKe+KAnXRTWml5O6xrv6ZkiNmAxoYyO1shzLQWZGVmYXVs\n" + + "dEBmbG93Y3J5cHQudGVzdIh4BBMWCgAgBQJgirumAhsDBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCHgECGQEACgkQIl+AI8INCVcysgD/cu23M07rImuV5gIl98uOnSIR+QnHUD/M\n" + + "I34b7iY/iTQBALMIsqO1PwYl2qKwmXb5lSoMj5SmnzRRE2RwAFW3AiMCnIsEYIq7\n" + + "phIKKwYBBAGXVQEFAQEHQA8q7iPr+0OXqBGBSAL6WNDjzHuBsG7uiu5w8l/A6v8l\n" + + "AwEIB/4JAwK9Z/HebMN13mCOF6Wy/9oZK4d0DW9cNLuQDeRVZejxT8oFMm7G8iGw\n" + + "CGNjIWWcQSvctBZtHwgcMeplCW7tmzkD3Nq/ty50lCwQQd6gZSXMiHUEGBYKAB0F\n" + + "AmCKu6YCGwwFFgIDAQAECwkIBwUVCgkICwIeAQAKCRAiX4Ajwg0JV+sbAQCv4LVM\n" + + "0+AN54ivWa4vPRyYOfSQ1FqsipkYLJce+xwUeAD+LZpEVCypFtGWQVdeSJVxIHx3\n" + + "k40IfHsK0fGgR+NrRAw=\n" + + "=osuI\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + private static final String passphrase = "android"; + + @Test + public void verifyMissingVerificationCertOptionStillResultsInMessageIsSigned() throws IOException, PGPException { + PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key); + SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAllKeysWith( + Passphrase.fromPassword(passphrase), secretKey); + PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKey); + + ConsumerOptions withoutVerificationCert = new ConsumerOptions(); + DecryptionStream verificationStream = PGPainless.decryptAndOrVerify() + .onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8))) + .withOptions(withoutVerificationCert); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(verificationStream, out); + verificationStream.close(); + + OpenPgpMetadata metadata = verificationStream.getResult(); + + assertTrue(metadata.isSigned(), "Message is signed, even though we miss the verification cert."); + assertFalse(metadata.isVerified(), "Message is not verified because we lack the verification cert."); + } +}