1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-16 17:32:06 +01:00

Fix detection of signed messages when verification keys are missing

Fixes #187, supersedes #189
This commit is contained in:
Paul Schaub 2021-10-08 14:03:12 +02:00
parent 0c122c1643
commit 33f516efe8
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
2 changed files with 67 additions and 3 deletions

View file

@ -9,8 +9,10 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -45,6 +47,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.MessageNotIntegrityProtectedException; import org.pgpainless.exception.MessageNotIntegrityProtectedException;
import org.pgpainless.exception.MissingDecryptionMethodException; import org.pgpainless.exception.MissingDecryptionMethodException;
import org.pgpainless.exception.MissingLiteralDataException; import org.pgpainless.exception.MissingLiteralDataException;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.exception.UnacceptableAlgorithmException; import org.pgpainless.exception.UnacceptableAlgorithmException;
import org.pgpainless.exception.WrongConsumingMethodException; import org.pgpainless.exception.WrongConsumingMethodException;
import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.implementation.ImplementationFactory;
@ -71,6 +74,7 @@ public final class DecryptionStreamFactory {
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>(); private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>();
private final List<DetachedSignatureCheck> detachedSignatureChecks = new ArrayList<>(); private final List<DetachedSignatureCheck> detachedSignatureChecks = new ArrayList<>();
private final Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert = new HashMap<>();
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(); ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
@ -96,6 +100,8 @@ public final class DecryptionStreamFactory {
long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature); long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature);
PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId); PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
if (signingKeyRing == null) { if (signingKeyRing == null) {
SignatureValidationException ex = new SignatureValidationException("Missing verification certificate " + Long.toHexString(issuerKeyId));
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex);
continue; continue;
} }
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId); PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
@ -105,7 +111,8 @@ public final class DecryptionStreamFactory {
DetachedSignatureCheck detachedSignature = new DetachedSignatureCheck(signature, signingKeyRing, signingKeyIdentifier); DetachedSignatureCheck detachedSignature = new DetachedSignatureCheck(signature, signingKeyRing, signingKeyIdentifier);
detachedSignatureChecks.add(detachedSignature); detachedSignatureChecks.add(detachedSignature);
} catch (PGPException e) { } catch (PGPException e) {
LOGGER.warn("Cannot verify detached signature made by {}. Reason: {}", signingKeyIdentifier, e.getMessage(), e); SignatureValidationException ex = new SignatureValidationException("Cannot verify detached signature made by " + signingKeyIdentifier + ".", e);
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, signingKeyIdentifier), ex);
} }
} }
} }
@ -223,7 +230,7 @@ public final class DecryptionStreamFactory {
.setModificationDate(pgpLiteralData.getModificationTime()) .setModificationDate(pgpLiteralData.getModificationTime())
.setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat())); .setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
if (onePassSignatureChecks.isEmpty()) { if (onePassSignatureChecks.isEmpty() && onePassSignaturesWithMissingCert.isEmpty()) {
LOGGER.debug("No OnePassSignatures found -> We are done"); LOGGER.debug("No OnePassSignatures found -> We are done");
return literalDataInputStream; return literalDataInputStream;
} }
@ -237,6 +244,16 @@ public final class DecryptionStreamFactory {
onePassSignatureChecks.get(i).setSignature(signatureList.get(reversedIndex)); onePassSignatureChecks.get(i).setSignature(signatureList.get(reversedIndex));
} }
for (PGPSignature signature : signatureList) {
if (onePassSignaturesWithMissingCert.containsKey(signature.getKeyID())) {
OnePassSignatureCheck check = onePassSignaturesWithMissingCert.remove(signature.getKeyID());
check.setSignature(signature);
resultBuilder.addInvalidInbandSignature(new SignatureVerification(signature, null),
new SignatureValidationException("Missing verification certificate " + Long.toHexString(signature.getKeyID())));
}
}
return new SignatureInputStream.VerifySignatures(literalDataInputStream, return new SignatureInputStream.VerifySignatures(literalDataInputStream,
onePassSignatureChecks, detachedSignatureChecks, options, resultBuilder) { onePassSignatureChecks, detachedSignatureChecks, options, resultBuilder) {
}; };
@ -488,7 +505,7 @@ public final class DecryptionStreamFactory {
// Find public key // Find public key
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId); PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
if (verificationKeyRing == null) { if (verificationKeyRing == null) {
LOGGER.debug("Missing verification key from {}", Long.toHexString(keyId)); onePassSignaturesWithMissingCert.put(keyId, new OnePassSignatureCheck(signature, null));
return; return;
} }
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId); PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
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.util.io.Streams;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
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";
@Test
public void verifyMissingVerificationCertOptionStillResultsInMessageIsSigned() throws IOException, PGPException {
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.");
}
}