From f68779d8a5da8b86906bec01193097d2056f7a9e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 30 Aug 2021 17:15:11 +0200 Subject: [PATCH] Add tests for pgpainless-sop --- .../key/collection/PGPKeyRingCollection.java | 4 +- .../java/org/pgpainless/sop/DecryptImpl.java | 2 +- .../sop/EncryptDecryptRoundTripTest.java | 247 ++++++++++++++++++ .../org/pgpainless/sop/ExtractCertTest.java | 99 +++++++ 4 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java create mode 100644 pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java index e705434f..473b392b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java @@ -105,11 +105,11 @@ public class PGPKeyRingCollection { pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); } - public PGPSecretKeyRingCollection getPGPSecretKeyRingCollection() { + public @Nonnull PGPSecretKeyRingCollection getPGPSecretKeyRingCollection() { return pgpSecretKeyRingCollection; } - public PGPPublicKeyRingCollection getPgpPublicKeyRingCollection() { + public @Nonnull PGPPublicKeyRingCollection getPgpPublicKeyRingCollection() { return pgpPublicKeyRingCollection; } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java index 734dbee1..1b476cd7 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java @@ -64,7 +64,7 @@ public class DecryptImpl implements Decrypt { try { PGPPublicKeyRingCollection certs = PGPainless.readKeyRing().keyRingCollection(certIn, false) .getPgpPublicKeyRingCollection(); - if (certs == null) { + if (certs.size() == 0) { throw new SOPGPException.BadData(new PGPException("No certificates provided.")); } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java new file mode 100644 index 00000000..a4048827 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java @@ -0,0 +1,247 @@ +/* + * 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.sop; + +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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import sop.ByteArrayAndResult; +import sop.DecryptionResult; +import sop.SOP; +import sop.exception.SOPGPException; + +public class EncryptDecryptRoundTripTest { + + private static SOP sop; + private static byte[] aliceKey; + private static byte[] aliceCert; + private static byte[] bobKey; + private static byte[] bobCert; + private static byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + @BeforeAll + public static void setup() throws IOException { + sop = new SOPImpl(); + aliceKey = sop.generateKey() + .userId("Alice ") + .generate() + .getBytes(); + aliceCert = sop.extractCert() + .key(new ByteArrayInputStream(aliceKey)) + .getBytes(); + bobKey = sop.generateKey() + .userId("Bob ") + .generate() + .getBytes(); + bobCert = sop.extractCert() + .key(new ByteArrayInputStream(bobKey)) + .getBytes(); + } + + @Test + public void basicRoundTripWithKey() throws IOException, SOPGPException.CertCannotSign { + byte[] encrypted = sop.encrypt() + .signWith(new ByteArrayInputStream(aliceKey)) + .withCert(new ByteArrayInputStream(aliceCert)) + .withCert(new ByteArrayInputStream(bobCert)) + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + ByteArrayAndResult bytesAndResult = sop.decrypt() + .withKey(new ByteArrayInputStream(bobKey)) + .verifyWithCert(new ByteArrayInputStream(aliceCert)) + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes(); + + byte[] decrypted = bytesAndResult.getBytes(); + assertArrayEquals(message, decrypted); + + DecryptionResult result = bytesAndResult.getResult(); + assertEquals(1, result.getVerifications().size()); + } + + @Test + public void basicRoundTripWithoutArmorUsingKey() throws IOException, SOPGPException.CertCannotSign { + byte[] aliceKeyNoArmor = sop.generateKey() + .userId("Alice ") + .noArmor() + .generate() + .getBytes(); + byte[] aliceCertNoArmor = sop.extractCert() + .noArmor() + .key(new ByteArrayInputStream(aliceKeyNoArmor)) + .getBytes(); + byte[] encrypted = sop.encrypt() + .signWith(new ByteArrayInputStream(aliceKeyNoArmor)) + .withCert(new ByteArrayInputStream(aliceCertNoArmor)) + .noArmor() + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + ByteArrayAndResult bytesAndResult = sop.decrypt() + .withKey(new ByteArrayInputStream(aliceKeyNoArmor)) + .verifyWithCert(new ByteArrayInputStream(aliceCertNoArmor)) + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes(); + + byte[] decrypted = bytesAndResult.getBytes(); + assertArrayEquals(message, decrypted); + + DecryptionResult result = bytesAndResult.getResult(); + assertEquals(1, result.getVerifications().size()); + } + + @Test + public void basicRoundTripWithPassword() throws IOException { + byte[] encrypted = sop.encrypt() + .withPassword("passphr4s3") + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + ByteArrayAndResult bytesAndResult = sop.decrypt() + .withPassword("passphr4s3") + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes(); + + byte[] decrypted = bytesAndResult.getBytes(); + assertArrayEquals(message, decrypted); + + DecryptionResult result = bytesAndResult.getResult(); + assertEquals(0, result.getVerifications().size()); + } + + @Test + public void roundTripWithDecryptionPasswordContainingWhitespace() throws IOException { + byte[] encrypted = sop.encrypt() + .withPassword("passphr4s3") + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + ByteArrayAndResult bytesAndResult = sop.decrypt() + .withPassword("passphr4s3 ") // whitespace is removed + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes(); + + byte[] decrypted = bytesAndResult.getBytes(); + assertArrayEquals(message, decrypted); + + DecryptionResult result = bytesAndResult.getResult(); + assertEquals(0, result.getVerifications().size()); + } + + @Test + public void roundTripWithEncryptionPasswordContainingWhitespace() throws IOException { + byte[] encrypted = sop.encrypt() + .withPassword("passphr4s3 ") + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + ByteArrayAndResult bytesAndResult = sop.decrypt() + .withPassword("passphr4s3 ") + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes(); + + byte[] decrypted = bytesAndResult.getBytes(); + assertArrayEquals(message, decrypted); + + DecryptionResult result = bytesAndResult.getResult(); + assertEquals(0, result.getVerifications().size()); + } + + @Test + public void encrypt_decryptAndVerifyYieldsNoSignatureException() throws IOException { + byte[] encrypted = sop.encrypt() + .withCert(new ByteArrayInputStream(bobCert)) + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + assertThrows(SOPGPException.NoSignature.class, () -> sop + .decrypt() + .withKey(new ByteArrayInputStream(bobKey)) + .verifyWithCert(new ByteArrayInputStream(aliceCert)) + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes()); + } + + @Test + public void encrypt_decryptWithoutKeyOrPassphraseYieldsMissingArgException() throws IOException { + byte[] encrypted = sop.encrypt() + .withCert(new ByteArrayInputStream(bobCert)) + .plaintext(new ByteArrayInputStream(message)) + .getBytes(); + + assertThrows(SOPGPException.MissingArg.class, () -> sop + .decrypt() + .ciphertext(new ByteArrayInputStream(encrypted)) + .toBytes()); + } + + @Test + public void decrypt_withKeyWithMultipleKeysFails() { + byte[] keys = new byte[aliceKey.length + bobKey.length]; + System.arraycopy(aliceKey, 0, keys, 0 , aliceKey.length); + System.arraycopy(bobKey, 0, keys, aliceKey.length, bobKey.length); + + assertThrows(SOPGPException.BadData.class, () -> sop.decrypt() + .withKey(new ByteArrayInputStream(keys))); + } + + @Test + public void decrypt_withKeyWithPasswordProtectionFails() { + String passwordProtectedKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 1777 B08F B9FE 25B8 E32C E2E2 5974 2F3E DEC0 1772\n" + + "Comment: Protected \n" + + "\n" + + "lIYEYSztkRYJKwYBBAHaRw8BAQdAMAcay1YOujgMVAXQsAz/QwL2PHc8EBroTQpe\n" + + "Skizmyf+CQMC03aJCFqkseNgXBlZLknishdIO2iMz5YT2ptOjDkYLenEc71LqDTZ\n" + + "abhG9vjfaMtfUvfF6qZurbvVAC0QBr/cSVREuEqDCxpbmYKeFrMz6LQkUHJvdGVj\n" + + "dGVkIDxwcm90ZWN0ZWRAcGdwYWlubGVzcy5vcmc+iHgEExYKACAFAmEs7ZECGwEF\n" + + "FgIDAQAECwkIBwUVCgkICwIeAQIZAQAKCRBZdC8+3sAXcksuAP961t8IhIHK7cG9\n" + + "O7DjNNi35rEgvtHK6yC529gCzE5cBAEAjEgCHSu9UK4SQOSCiQNPKIJ4UUTuCWm8\n" + + "bla6dtuB1QSciwRhLO2REgorBgEEAZdVAQUBAQdAqVXssfkOprxE8weHZoa7T/5f\n" + + "kbGOA/6hmzLoYfWURhEDAQgH/gkDAtN2iQhapLHjYEJJ+kwyW2SnEFhMoWKZBG94\n" + + "RV+S+rwq+ITz/CV53Qc/XcveX6x4QmoXqK6ges7dDLYog/iJ/tFAMeV//LJHpow/\n" + + "U2SA6XGIdQQYFgoAHQUCYSztkQIbDAUWAgMBAAQLCQgHBRUKCQgLAh4BAAoJEFl0\n" + + "Lz7ewBdyWJkA/j8zj+6AhhAJOdlfqA2empI+eZfZQg8D1uB/QfKNh+5CAP9VYUUf\n" + + "EZMNtnSCXP6ERFy1/CJLW4eqLL19oVBJ/mvMDJyGBGEs7ZEWCSsGAQQB2kcPAQEH\n" + + "QMZgsx/zrhfULWNsjs0ZREzEwLsPRzSgh9zKn53U/zlY/gkDAtN2iQhapLHjYHe7\n" + + "hmEPgxR7lsOZJazPnBzJGP6uRs4ts6m7e4MfEF2gk0N+iaQPowkypZS98pN5rsDg\n" + + "t9Yby6+IgqSQkMZitZAxnfepOCOI1QQYFgoAfQUCYSztkQIbAgUWAgMBAAQLCQgH\n" + + "BRUKCQgLAh4BXyAEGRYKAAYFAmEs7ZEACgkQpHV13KorLUkL3gEAnNx1GARit/FL\n" + + "4OoL0dINsmTCF8hQKe2OGgNhhkN8v90A/i2RifktEqcmMW1ezSRGlmn5hx5bTWRc\n" + + "99Ts4FiwRLQJAAoJEFl0Lz7ewBdyaysA/jk61StphMpfuRsuQwznH7L7SddNcZ1k\n" + + "l1wHK5kRJXyCAP0WUgkpEcM2bpwb4sxkCDxrfk/ixc47hGa68MPWwOJkAQ==\n" + + "=MUYS\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.decrypt() + .withKey(new ByteArrayInputStream(passwordProtectedKey.getBytes(StandardCharsets.UTF_8)))); + } + + @Test + public void verifyWith_noDataThrowsBadData() { + assertThrows(SOPGPException.BadData.class, () -> sop.decrypt() + .verifyWithCert(new ByteArrayInputStream(new byte[0]))); + } +} diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java new file mode 100644 index 00000000..1262c74a --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java @@ -0,0 +1,99 @@ +/* + * 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.sop; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import sop.SOP; +import sop.exception.SOPGPException; + +public class ExtractCertTest { + + public static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: A8D9 9FF4 C8DD BBA6 C610 A6B7 9ACB 2195 A9BC DF5B\n" + + "Comment: Alice \n" + + "\n" + + "lFgEYSzwMhYJKwYBBAHaRw8BAQdA60pbfTLh5MB1Ka5KfZqUzMhHzJHYBvXF68mW\n" + + "BMzgupIAAPsHWal9lDZzNXUE8Xnt00IUFYhOC5P73FMLGqdpsA+fQw51tBxBbGlj\n" + + "ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iHgEExYKACAFAmEs8DICGwEFFgIDAQAE\n" + + "CwkIBwUVCgkICwIeAQIZAQAKCRCayyGVqbzfW9EWAQDQbiKUpftQsK4IXJJ1d40H\n" + + "fe7Djhm0P08Oo73GeE8vLwEA0ArOcyBbOETBBIefigVWWay6JIt57DGxR6KWQABk\n" + + "AwKcXQRhLPAyEgorBgEEAZdVAQUBAQdAa3lioBiWVujoFINa2wVNPLjf/hc1aIPK\n" + + "sbAcs83uRysDAQgHAAD/QJGlp9SjIzT9o2e+x9jyndOhyMPSmlLljW9ZtSuzmrgU\n" + + "uYh1BBgWCgAdBQJhLPAyAhsMBRYCAwEABAsJCAcFFQoJCAsCHgEACgkQmsshlam8\n" + + "31u3awEA0b/hCRQNrtsITuQGS1ikzonhJITpbmrg/ZVOvBn+3jYBAIC5d4Hozn1O\n" + + "aBBl1ZiY3Bl2qIFYWzVR9vFOm+Va3lkEnFgEYSzwMhYJKwYBBAHaRw8BAQdAFfJi\n" + + "f64K8E2ZlBqAAxO0eG7nIlxRYlOvbN/1vP8grW8AAP0cKOpo2uFqLzTnmzJ+rpmV\n" + + "gwUW1FiHGpM/awfg+zzCgBAxiNUEGBYKAH0FAmEs8DICGwIFFgIDAQAECwkIBwUV\n" + + "CgkICwIeAV8gBBkWCgAGBQJhLPAyAAoJEOVqq3ZdDwXc3WAA/2UaO79+srF3p/f9\n" + + "scsmj7Rax8uXKw8sJPdgPMjmo404AQDor96bTeBiGOwPq0UfY4GGRmdkH8Z95PE+\n" + + "fKyEbkjFAgAKCRCayyGVqbzfWwmUAP9AtTcZZPHa/gMYr5KXI+L7VRie9iolKII7\n" + + "glyfG0/RUwEA3hTvAPRPAFG5WFNYaQprBAnAsefmdqwdDJGPfR7uGg0=\n" + + "=BVSY\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + public static final String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: A8D9 9FF4 C8DD BBA6 C610 A6B7 9ACB 2195 A9BC DF5B\n" + + "Comment: Alice \n" + + "\n" + + "mDMEYSzwMhYJKwYBBAHaRw8BAQdA60pbfTLh5MB1Ka5KfZqUzMhHzJHYBvXF68mW\n" + + "BMzgupK0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz6IeAQTFgoAIAUCYSzw\n" + + "MgIbAQUWAgMBAAQLCQgHBRUKCQgLAh4BAhkBAAoJEJrLIZWpvN9b0RYBANBuIpSl\n" + + "+1CwrghcknV3jQd97sOOGbQ/Tw6jvcZ4Ty8vAQDQCs5zIFs4RMEEh5+KBVZZrLok\n" + + "i3nsMbFHopZAAGQDArg4BGEs8DISCisGAQQBl1UBBQEBB0BreWKgGJZW6OgUg1rb\n" + + "BU08uN/+FzVog8qxsByzze5HKwMBCAeIdQQYFgoAHQUCYSzwMgIbDAUWAgMBAAQL\n" + + "CQgHBRUKCQgLAh4BAAoJEJrLIZWpvN9bt2sBANG/4QkUDa7bCE7kBktYpM6J4SSE\n" + + "6W5q4P2VTrwZ/t42AQCAuXeB6M59TmgQZdWYmNwZdqiBWFs1UfbxTpvlWt5ZBLgz\n" + + "BGEs8DIWCSsGAQQB2kcPAQEHQBXyYn+uCvBNmZQagAMTtHhu5yJcUWJTr2zf9bz/\n" + + "IK1viNUEGBYKAH0FAmEs8DICGwIFFgIDAQAECwkIBwUVCgkICwIeAV8gBBkWCgAG\n" + + "BQJhLPAyAAoJEOVqq3ZdDwXc3WAA/2UaO79+srF3p/f9scsmj7Rax8uXKw8sJPdg\n" + + "PMjmo404AQDor96bTeBiGOwPq0UfY4GGRmdkH8Z95PE+fKyEbkjFAgAKCRCayyGV\n" + + "qbzfWwmUAP9AtTcZZPHa/gMYr5KXI+L7VRie9iolKII7glyfG0/RUwEA3hTvAPRP\n" + + "AFG5WFNYaQprBAnAsefmdqwdDJGPfR7uGg0=\n" + + "=9qam\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + private static SOP sop; + + @BeforeAll + public static void setup() { + sop = new SOPImpl(); + } + + @Test + public void basicExtractCert() throws IOException { + assertArrayEquals( + cert.getBytes(StandardCharsets.UTF_8), + sop.extractCert() + .key(new ByteArrayInputStream(key.getBytes(StandardCharsets.UTF_8))) + .getBytes()); + } + + @Test + public void emptyKeyDataYieldsBadData() { + assertThrows(SOPGPException.BadData.class, () -> sop.extractCert() + .key(new ByteArrayInputStream(new byte[0]))); + } +}