From 4d6ca80e25798e1c3c9ebb49406d25860b76f8e6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 23 Aug 2021 15:47:21 +0200 Subject: [PATCH] Debug signature verification using debug build of bcpg --- pgpainless-core/build.gradle | 5 ++ .../signature/SignatureVerifier.java | 15 +++- .../ClearsignedMessageUtil.java | 70 +------------------ .../CleartextSignatureProcessor.java | 3 +- .../CleartextSignatureVerificationTest.java | 43 ++++++++++-- 5 files changed, 59 insertions(+), 77 deletions(-) diff --git a/pgpainless-core/build.gradle b/pgpainless-core/build.gradle index c3bf4787..590cbcf8 100644 --- a/pgpainless-core/build.gradle +++ b/pgpainless-core/build.gradle @@ -8,7 +8,12 @@ dependencies { testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" implementation "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion" + + //* api "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion" + /*/ + api files("libs/bcpg-jdk18on-1.70-SNAPSHOT.jar") + // */ implementation 'org.slf4j:slf4j-api:1.7.32' testImplementation 'ch.qos.logback:logback-classic:1.2.5' diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureVerifier.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureVerifier.java index 06d98dfb..4ddf7c00 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureVerifier.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureVerifier.java @@ -373,8 +373,21 @@ public final class SignatureVerifier { signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); int read; byte[] buf = new byte[8192]; + byte lastByte = -1; while ((read = signedData.read(buf)) != -1) { - signature.update(buf, 0, read); + // If we previously omitted a newline, but the stream is not yet empty, add it now + if (lastByte == (byte) '\n') { + signature.update(lastByte); + } + lastByte = buf[read - 1]; + + if (lastByte == (byte) '\n') { + // if last byte in buffer is newline, omit it for now + signature.update(buf, 0, read - 1); + } else { + // otherwise, write buffer as usual + signature.update(buf, 0, read); + } } } catch (PGPException e) { throw new SignatureValidationException("Cannot init signature.", e); diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/ClearsignedMessageUtil.java b/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/ClearsignedMessageUtil.java index 9894a7d9..5fa3bdbe 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/ClearsignedMessageUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/ClearsignedMessageUtil.java @@ -15,7 +15,6 @@ */ package org.pgpainless.signature.cleartext_signatures; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,11 +22,7 @@ import java.io.InputStream; import java.io.OutputStream; import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.util.Strings; import org.pgpainless.implementation.ImplementationFactory; @@ -69,19 +64,17 @@ public final class ClearsignedMessageUtil { if (lookAhead != -1 && in.isClearText()) { byte[] line = lineOut.toByteArray(); out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - out.write(lineSep); while (lookAhead != -1 && in.isClearText()) { lookAhead = readInputLine(lineOut, lookAhead, in); line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); out.write(lineSep); + out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); } } else { if (lookAhead != -1) { byte[] line = lineOut.toByteArray(); out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - out.write(lineSep); } } } finally { @@ -94,38 +87,6 @@ public final class ClearsignedMessageUtil { return signatures; } - /** - * Initialize the given signature by processing the data from the messageData input stream. - * - * @param signature uninitialized signature - * @param signingKey public signing key - * @param messageData input stream containing the data to which the signature belongs - * @return initialized signature - * - * @throws PGPException if the signature cannot be initialized - * @throws IOException if an IO error happens - */ - public static PGPSignature initializeSignature(PGPSignature signature, PGPPublicKey signingKey, InputStream messageData) - throws PGPException, IOException { - signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); - - InputStream sigIn = new BufferedInputStream(messageData); - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, sigIn); - processLine(signature, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, sigIn); - signature.update((byte) '\r'); - signature.update((byte) '\n'); - processLine(signature, lineOut.toByteArray()); - } while (lookAhead != -1); - } - sigIn.close(); - return signature; - } - public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) throws IOException { bOut.reset(); @@ -190,25 +151,6 @@ public final class ClearsignedMessageUtil { return nlBytes; } - public static void processLine(PGPSignature sig, byte[] line) { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sig.update(line, 0, length); - } - } - - public static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line) - throws IOException { - // note: trailing white space needs to be removed from the end of - // each line for signature calculation RFC 4880 Section 7.1 - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sGen.update(line, 0, length); - } - - aOut.write(line, 0, line.length); - } - private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) { int end = line.length - 1; @@ -223,16 +165,6 @@ public final class ClearsignedMessageUtil { return b == '\r' || b == '\n'; } - private static int getLengthWithoutWhiteSpace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - private static boolean isWhiteSpace(byte b) { return isLineEnding(b) || b == '\t' || b == ' '; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/CleartextSignatureProcessor.java b/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/CleartextSignatureProcessor.java index 3361ec78..aa86abdd 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/CleartextSignatureProcessor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/cleartext_signatures/CleartextSignatureProcessor.java @@ -34,6 +34,7 @@ import org.bouncycastle.openpgp.PGPSignatureList; import org.pgpainless.PGPainless; import org.pgpainless.exception.SignatureValidationException; import org.pgpainless.signature.CertificateValidator; +import org.pgpainless.signature.SignatureVerifier; import org.pgpainless.util.ArmoredInputStreamFactory; /** @@ -93,7 +94,7 @@ public class CleartextSignatureProcessor { } try { - ClearsignedMessageUtil.initializeSignature(signature, signingKey, multiPassStrategy.getMessageInputStream()); + SignatureVerifier.initializeSignatureAndUpdateWithSignedData(signature, multiPassStrategy.getMessageInputStream(), signingKey); CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, certificate, PGPainless.getPolicy()); return signature; } catch (SignatureValidationException e) { diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java index 9a470369..18e91258 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java @@ -30,15 +30,17 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.key.TestKeys; import org.pgpainless.signature.CertificateValidator; import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.cleartext_signatures.ClearsignedMessageUtil; +import org.pgpainless.signature.SignatureVerifier; import org.pgpainless.signature.cleartext_signatures.CleartextSignatureProcessor; import org.pgpainless.signature.cleartext_signatures.InMemoryMultiPassStrategy; import org.pgpainless.signature.cleartext_signatures.MultiPassStrategy; +import org.pgpainless.util.ArmorUtils; import org.pgpainless.util.TestUtils; public class CleartextSignatureVerificationTest { @@ -48,7 +50,7 @@ public class CleartextSignatureVerificationTest { "To blazon it, then sweeten with thy breath\n" + "This neighbor air, and let rich music’s tongue\n" + "Unfold the imagined happiness that both\n" + - "Receive in either by this dear encounter.\n"; + "Receive in either by this dear encounter."; public static final String MESSAGE_SIGNED = "-----BEGIN PGP SIGNED MESSAGE-----\n" + "Hash: SHA512\n" + "\n" + @@ -118,11 +120,40 @@ public class CleartextSignatureVerificationTest { PGPSignature signature = SignatureUtils.readSignatures(SIGNATURE).get(0); PGPPublicKey signingKey = signingKeys.getPublicKey(signature.getKeyID()); - /* SignatureVerifier.initializeSignatureAndUpdateWithSignedData(signature, new ByteArrayInputStream(MESSAGE_BODY.getBytes(StandardCharsets.UTF_8)), signingKey); - /*/ - ClearsignedMessageUtil.initializeSignature(signature, signingKey, new ByteArrayInputStream(MESSAGE_BODY.getBytes(StandardCharsets.UTF_8))); - //*/ + CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, signingKeys, PGPainless.getPolicy()); } + + @Test + @Disabled + public void print() throws IOException { + // CHECKSTYLE:OFF + PGPPublicKeyRing keys = TestKeys.getEmilPublicKeyRing(); + System.out.println(ArmorUtils.toAsciiArmoredString(keys)); + System.out.println(MESSAGE_SIGNED); + System.out.println(MESSAGE_BODY); + System.out.println(SIGNATURE); + // CHECKSTYLE:ON + } + + @Test + public void testOutputOfSigVerification() throws IOException, PGPException { + PGPSignature signature = SignatureUtils.readSignatures(SIGNATURE).get(0); + + ConsumerOptions options = new ConsumerOptions() + .addVerificationCert(TestKeys.getEmilPublicKeyRing()) + .addVerificationOfDetachedSignature(signature); + + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(new ByteArrayInputStream(MESSAGE_BODY.getBytes(StandardCharsets.UTF_8))) + .withOptions(options); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + + OpenPgpMetadata metadata = decryptionStream.getResult(); + assertEquals(1, metadata.getVerifiedSignatures().size()); + } }