/* * 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.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.decryption_verification.cleartext_signatures.CleartextSignatureProcessor; import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy; import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; import org.pgpainless.encryption_signing.EncryptionStream; import org.pgpainless.encryption_signing.ProducerOptions; import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.exception.WrongConsumingMethodException; import org.pgpainless.key.TestKeys; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.CertificateValidator; import org.pgpainless.signature.SignatureUtils; import org.pgpainless.signature.SignatureVerifier; import org.pgpainless.util.ArmorUtils; import org.pgpainless.util.TestUtils; public class CleartextSignatureVerificationTest { public static final byte[] MESSAGE_BODY = ("Ah, Juliet, if the measure of thy joy\n" + "Be heaped like mine, and that thy skill be more\n" + "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.").getBytes(StandardCharsets.UTF_8); public static final byte[] MESSAGE_SIGNED = ("-----BEGIN PGP SIGNED MESSAGE-----\n" + "Hash: SHA512\n" + "\n" + "Ah, Juliet, if the measure of thy joy\n" + "Be heaped like mine, and that thy skill be more\n" + "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" + "-----BEGIN PGP SIGNATURE-----\n" + "\n" + "iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" + "DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" + "ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" + "=Z2SO\n" + "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); public static final byte[] SIGNATURE = ("-----BEGIN PGP SIGNATURE-----\n" + "\n" + "iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" + "DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" + "ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" + "=Z2SO\n" + "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); @Test public void cleartextSignVerification_InMemoryMultiPassStrategy() throws IOException, PGPException { PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing(); ConsumerOptions options = new ConsumerOptions() .addVerificationCert(signingKeys); InMemoryMultiPassStrategy multiPassStrategy = MultiPassStrategy.keepMessageInMemory(); CleartextSignatureProcessor processor = PGPainless.verifyCleartextSignedMessage() .onInputStream(new ByteArrayInputStream(MESSAGE_SIGNED)) .withStrategy(multiPassStrategy) .withOptions(options); DecryptionStream decryptionStream = processor.getVerificationStream(); ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.pipeAll(decryptionStream, out); decryptionStream.close(); OpenPgpMetadata result = decryptionStream.getResult(); assertTrue(result.isVerified()); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); assertArrayEquals(MESSAGE_BODY, out.toByteArray()); } @Test public void cleartextSignVerification_FileBasedMultiPassStrategy() throws IOException, PGPException { PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing(); ConsumerOptions options = new ConsumerOptions() .addVerificationCert(signingKeys); File tempDir = TestUtils.createTempDirectory(); File file = new File(tempDir, "file"); MultiPassStrategy multiPassStrategy = MultiPassStrategy.writeMessageToFile(file); CleartextSignatureProcessor processor = PGPainless.verifyCleartextSignedMessage() .onInputStream(new ByteArrayInputStream(MESSAGE_SIGNED)) .withStrategy(multiPassStrategy) .withOptions(options); DecryptionStream decryptionStream = processor.getVerificationStream(); ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.pipeAll(decryptionStream, out); decryptionStream.close(); OpenPgpMetadata result = decryptionStream.getResult(); assertTrue(result.isVerified()); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); FileInputStream fileIn = new FileInputStream(file); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); Streams.pipeAll(fileIn, bytes); fileIn.close(); assertArrayEquals(MESSAGE_BODY, bytes.toByteArray()); } @Test public void verifySignatureDetached() throws IOException, PGPException { PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing(); PGPSignature signature = SignatureUtils.readSignatures(SIGNATURE).get(0); PGPPublicKey signingKey = signingKeys.getPublicKey(signature.getKeyID()); SignatureVerifier.initializeSignatureAndUpdateWithSignedData(signature, new ByteArrayInputStream(MESSAGE_BODY), signingKey); CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, signingKeys, PGPainless.getPolicy()); } public static void main(String[] args) throws IOException { // CHECKSTYLE:OFF PGPPublicKeyRing keys = TestKeys.getEmilPublicKeyRing(); System.out.println(ArmorUtils.toAsciiArmoredString(keys)); System.out.println(new String(MESSAGE_SIGNED)); System.out.println(new String(MESSAGE_BODY)); System.out.println(new String(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)) .withOptions(options); ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.pipeAll(decryptionStream, out); decryptionStream.close(); OpenPgpMetadata metadata = decryptionStream.getResult(); assertEquals(1, metadata.getVerifiedSignatures().size()); } @Test public void consumingCleartextSignedMessageWithNormalAPIThrowsWrongConsumingMethodException() throws IOException, PGPException { PGPPublicKeyRing certificate = TestKeys.getEmilPublicKeyRing(); ConsumerOptions options = new ConsumerOptions() .addVerificationCert(certificate); assertThrows(WrongConsumingMethodException.class, () -> PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(MESSAGE_SIGNED)) .withOptions(options)); } @Test public void consumingInlineSignedMessageWithCleartextSignedVerificationApiThrowsWrongConsumingMethodException() throws PGPException, IOException { String inlineSignedMessage = "-----BEGIN PGP MESSAGE-----\n" + "Version: PGPainless\n" + "\n" + "kA0DAQoTVzbmkxrPNwwBy8BJYgAAAAAAQWgsIEp1bGlldCwgaWYgdGhlIG1lYXN1\n" + "cmUgb2YgdGh5IGpveQpCZSBoZWFwZWQgbGlrZSBtaW5lLCBhbmQgdGhhdCB0aHkg\n" + "c2tpbGwgYmUgbW9yZQpUbyBibGF6b24gaXQsIHRoZW4gc3dlZXRlbiB3aXRoIHRo\n" + "eSBicmVhdGgKVGhpcyBuZWlnaGJvciBhaXIsIGFuZCBsZXQgcmljaCBtdXNpY+KA\n" + "mXMgdG9uZ3VlClVuZm9sZCB0aGUgaW1hZ2luZWQgaGFwcGluZXNzIHRoYXQgYm90\n" + "aApSZWNlaXZlIGluIGVpdGhlciBieSB0aGlzIGRlYXIgZW5jb3VudGVyLoh1BAET\n" + "CgAGBQJhK2q9ACEJEFc25pMazzcMFiEET2ZcTcLEZgvGQl5BVzbmkxrPNwxr8gD+\n" + "MDfg+qccpsoJVgHIW8mRPBQowXDyw+oNHsf28ii+/pEBAO/RXhFkZBPzlfDJMJVT\n" + "UwJJeuna1R4yOoWjq0zqRvrg\n" + "=dBiV\n" + "-----END PGP MESSAGE-----\n"; PGPPublicKeyRing certificate = TestKeys.getEmilPublicKeyRing(); ConsumerOptions options = new ConsumerOptions() .addVerificationCert(certificate); assertThrows(WrongConsumingMethodException.class, () -> PGPainless.verifyCleartextSignedMessage() .onInputStream(new ByteArrayInputStream(inlineSignedMessage.getBytes(StandardCharsets.UTF_8))) .withStrategy(new InMemoryMultiPassStrategy()) .withOptions(options) .getVerificationStream()); } @Test public void getDecoderStreamMistakensPlaintextForBase64RegressionTest() throws PGPException, IOException { String message = "Foo\nBar"; // PGPUtil.getDecoderStream() would mistaken this for base64 data ByteArrayInputStream msgIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); PGPSecretKeyRing secretKey = TestKeys.getEmilSecretKeyRing(); ByteArrayOutputStream signedOut = new ByteArrayOutputStream(); EncryptionStream signingStream = PGPainless.encryptAndOrSign().onOutputStream(signedOut) .withOptions(ProducerOptions.sign(SigningOptions.get() .addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), secretKey, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)) .setCleartextSigned()); Streams.pipeAll(msgIn, signingStream); signingStream.close(); String signed = signedOut.toString(); ByteArrayInputStream signedIn = new ByteArrayInputStream(signed.getBytes(StandardCharsets.UTF_8)); DecryptionStream verificationStream = PGPainless.verifyCleartextSignedMessage() .onInputStream(signedIn) .withStrategy(new InMemoryMultiPassStrategy()) .withOptions(new ConsumerOptions() .addVerificationCert(TestKeys.getEmilPublicKeyRing())) .getVerificationStream(); ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); Streams.pipeAll(verificationStream, msgOut); verificationStream.close(); OpenPgpMetadata metadata = verificationStream.getResult(); assertTrue(metadata.isVerified()); } }