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:
parent
0c122c1643
commit
33f516efe8
2 changed files with 67 additions and 3 deletions
|
@ -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);
|
||||||
|
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue