From 5e850581c01f5120710c138d07c0b8dc8036df0c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 23 Aug 2022 15:19:01 +0200 Subject: [PATCH] Add some PGPCertificateDirectory tests --- pgp-cert-d-java/build.gradle | 3 + .../pgp/cert_d/PGPCertificateDirectories.java | 17 +- .../pgp/cert_d/PGPCertificateDirectory.java | 4 +- .../InMemoryCertificateDirectoryBackend.java | 13 +- .../cert_d/PGPCertificateDirectoryTest.java | 211 ++++++++++++++++++ .../pgp/cert_d/TestKeyMaterialMerger.java | 17 ++ .../cert_d/TestKeyMaterialReaderBackend.java | 141 ++++++++++++ version.gradle | 1 + 8 files changed, 399 insertions(+), 8 deletions(-) create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateDirectoryTest.java create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index fabb0a8..a4b97b3 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -30,6 +30,9 @@ dependencies { // SQL Subkey table testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") + + testImplementation "org.bouncycastle:bcprov-jdk15to18:$bouncycastleVersion" + testImplementation "org.bouncycastle:bcpg-jdk15to18:$bouncycastleVersion" } animalsniffer { diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java index e248e0c..50f6908 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java @@ -13,23 +13,32 @@ import pgp.certificate_store.exception.NotAStoreException; import java.io.File; +/** + * Static factory methods that return implementations of the {@link PGPCertificateDirectory} class. + */ public final class PGPCertificateDirectories { private PGPCertificateDirectories() { } - public static PGPCertificateDirectory inMemoryCertificateDirectory(KeyMaterialReaderBackend keyReader) { - return new PGPCertificateDirectory(new InMemoryCertificateDirectoryBackend(keyReader), new InMemorySubkeyLookup()); + public static PGPCertificateDirectory inMemoryCertificateDirectory( + KeyMaterialReaderBackend keyReader) { + return new PGPCertificateDirectory( + new InMemoryCertificateDirectoryBackend(keyReader), new InMemorySubkeyLookup()); } - public static PGPCertificateDirectory defaultFileBasedCertificateDirectory(KeyMaterialReaderBackend keyReader, SubkeyLookup subkeyLookup) + public static PGPCertificateDirectory defaultFileBasedCertificateDirectory( + KeyMaterialReaderBackend keyReader, + SubkeyLookup subkeyLookup) throws NotAStoreException { return fileBasedCertificateDirectory(keyReader, BaseDirectoryProvider.getDefaultBaseDir(), subkeyLookup); } public static PGPCertificateDirectory fileBasedCertificateDirectory( - KeyMaterialReaderBackend keyReader, File baseDirectory, SubkeyLookup subkeyLookup) + KeyMaterialReaderBackend keyReader, + File baseDirectory, + SubkeyLookup subkeyLookup) throws NotAStoreException { return new PGPCertificateDirectory( new FileBasedCertificateDirectoryBackend(baseDirectory, keyReader), subkeyLookup); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java index c84af4d..2a0d8f5 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java @@ -20,8 +20,8 @@ import java.util.Set; public class PGPCertificateDirectory implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory, SubkeyLookup { - private final Backend backend; - private final SubkeyLookup subkeyLookup; + final Backend backend; + final SubkeyLookup subkeyLookup; public PGPCertificateDirectory(Backend backend, SubkeyLookup subkeyLookup) { this.backend = backend; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java index caea4a8..26c6e2b 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java @@ -76,7 +76,10 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect @Override - public KeyMaterial readBySpecialName(String specialName) { + public KeyMaterial readBySpecialName(String specialName) throws BadNameException { + if (SpecialNames.lookupSpecialName(specialName) == null) { + throw new BadNameException("Invalid special name " + specialName); + } return keyMaterialSpecialNameMap.get(specialName); } @@ -89,7 +92,13 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException { KeyMaterial update = reader.read(data); - KeyMaterial existing = readBySpecialName(SpecialNames.TRUST_ROOT); + KeyMaterial existing = null; + try { + existing = readBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + // Does not happen + throw new RuntimeException(e); + } KeyMaterial merged = merge.merge(update, existing); keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged); return merged; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateDirectoryTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateDirectoryTest.java new file mode 100644 index 0000000..10fde1a --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateDirectoryTest.java @@ -0,0 +1,211 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.Key; +import pgp.certificate_store.certificate.KeyMaterial; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Iterator; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PGPCertificateDirectoryTest { + + private static final Charset UTF8 = Charset.forName("UTF8"); + + private static final String HARRY_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: 2357 8FD1 7F20 7FDF 62F7 976C 4E9D 9891 7AD8 4522\n" + + "Comment: Harry Potter \n" + + "\n" + + "xVgEYwTP0hYJKwYBBAHaRw8BAQdAPVcWeaMiUVG+vECWpoytSoF3wNJQG/JsnCbj\n" + + "uQtv0REAAP0cS3GCmrIMO/FqNm1FG1mKw4P+mvZ1JBFILN7Laooq7A/QwsARBB8W\n" + + "CgCDBYJjBM/SBYkFn6YAAwsJBwkQTp2YkXrYRSJHFAAAAAAAHgAgc2FsdEBub3Rh\n" + + "dGlvbnMuc2VxdW9pYS1wZ3Aub3JnRSvJhQu9P/3bpFqFdB2c5Mfg9JIdyic1tsAt\n" + + "lZ7o4k4DFQoIApsBAh4BFiEEI1eP0X8gf99i95dsTp2YkXrYRSIAAK2cAP9juDnY\n" + + "qB6XuXVx76MzDlFemqJ/r2TIlN22O33ITp23cQEAiMk/rULVdfmlFi3QBvXgtPI2\n" + + "QQYFI0UnyGLmJSa1cwzNIEhhcnJ5IFBvdHRlciA8aGFycnlAcG90dGVyLm1vcmU+\n" + + "wsAUBBMWCgCGBYJjBM/SBYkFn6YAAwsJBwkQTp2YkXrYRSJHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn0o9na1p+a9kY3y3+xUSFFnxbuxNM\n" + + "5zvth0SAfJIH2C8DFQoIApkBApsBAh4BFiEEI1eP0X8gf99i95dsTp2YkXrYRSIA\n" + + "AC1zAP0e2qRXH4zCnjvdYwGP0tIY3dwBsm1bvk+wVFHm8h68iwEAh2uyyQ+O5iQH\n" + + "7NN/lV5dUKKsKaimj/vVGpSW3NtFZQDHWARjBM/SFgkrBgEEAdpHDwEBB0BUqcZu\n" + + "VsEO6fmW8q3S5ll9WohcTOWRX7Spg5wS3DIqPgABALzJ9ZImb4U94WqRtftSSaeF\n" + + "0w6rHCn2DiTT8pxjefGQEW7CwMUEGBYKATcFgmMEz9IFiQWfpgAJEE6dmJF62EUi\n" + + "RxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ+HPX0u5kyKR\n" + + "5IwErbomgGKVCGuvR6oSKc7CDQYMJS9eApsCvqAEGRYKAG8FgmMEz9IJEKk0hrvR\n" + + "6Jc7RxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ8Chba26\n" + + "1nQ6ZEZ/rVH8wMhYznGNa/Ux28sodM04wU6dFiEEli7ijJ6quX9gSoSbqTSGu9Ho\n" + + "lzsAAG1wAQDVvKVWaMOBELROkF72oBH58X6lrOmr08W5FJQxehywhQEAwetpgL1V\n" + + "DNj4qcvuCJJ2agAM1tA22WMPpQQeA5CCgwcWIQQjV4/RfyB/32L3l2xOnZiRethF\n" + + "IgAAsWEA/RfOKexMYEtzlpM71MB9SL+emHXf+w1TNAvBxrifU8bMAPoDmWHkWjZQ\n" + + "N6upbHKssRywPLKCMPLnFYtBNxDrMYr0BMddBGMEz9ISCisGAQQBl1UBBQEBB0CR\n" + + "p5dCIlSpV/EvXX2+YZnZSRtc8eTFXkph8RArNi0QPAMBCAcAAP9seqRo6mbmvS4h\n" + + "fkxmV5zap3wIemzW4iabNU2VbWJbEBALwsAGBBgWCgB4BYJjBM/SBYkFn6YACRBO\n" + + "nZiRethFIkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmdx\n" + + "uRLJ/h81azzvGn5zgJ+jdfkdM6iO+f1CLgfnHUH9ugKbDBYhBCNXj9F/IH/fYveX\n" + + "bE6dmJF62EUiAACObgEAk4whKEo2nzpWht65tpFjrEXdakj00mA/P612P2CUdPQB\n" + + "ANNn+VUiu9rtnLcP4NlaUVOwsgN7yyed0orbmG1VvSMF\n" + + "=cBAn\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + private static final String HARRY_FP = "23578fd17f207fdf62f7976c4e9d98917ad84522"; + + private static final String RON_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: B798 AF18 6BFE 4C19 902D 4950 5647 F001 37EF 4C41\n" + + "Comment: Ron Weasley \n" + + "\n" + + "xjMEYwTRXBYJKwYBBAHaRw8BAQdAPHyiu4nwvo3OY3wLG1tUmS6qeTeT1zd3BrL+\n" + + "6/5Ys3jCwBEEHxYKAIMFgmME0VwFiQWfpgADCwkHCRBWR/ABN+9MQUcUAAAAAAAe\n" + + "ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfEPNi/1ObPMwDwS094Lcyq\n" + + "dRNRk2FRzvhoXKrqF/GHfQMVCggCmwECHgEWIQS3mK8Ya/5MGZAtSVBWR/ABN+9M\n" + + "QQAAR/oBAJWxxUJqOAzYG4uAd6SSF55LZVl00t3bGhgEyGmrB/ppAQCZTpWu0rwU\n" + + "GVv/MoeqRwX+P8sHS4FSu/hSYJpbNwysCM0gUm9uIFdlYXNsZXkgPHJvbkB3ZWFz\n" + + "bGV5LmJ1cnJvdz7CwBQEExYKAIYFgmME0VwFiQWfpgADCwkHCRBWR/ABN+9MQUcU\n" + + "AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmf43PjsP9w1eGYP\n" + + "CLm6O+K27EQPiCf2cW71QnQ0RunupgMVCggCmQECmwECHgEWIQS3mK8Ya/5MGZAt\n" + + "SVBWR/ABN+9MQQAA7rYA/3U2aaw5PFa9L90PbxygOwFrgIVWLiOpnKfjqDJqEgva\n" + + "AQDxTIbpUYEAYmTpmAm1tiQSlpp9P96vqCMIj2OqtYCNAs4zBGME0VwWCSsGAQQB\n" + + "2kcPAQEHQGzhRPzKRkkce0v1NjuTV2stn8CEMVgnUxsMPtd0h2M9wsDFBBgWCgE3\n" + + "BYJjBNFcBYkFn6YACRBWR/ABN+9MQUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5z\n" + + "ZXF1b2lhLXBncC5vcmd6UNkzsh0jKRPQAKX2PoUhMN4QfhTK9IC6L+QbyL1rFgKb\n" + + "Ar6gBBkWCgBvBYJjBNFcCRCuGMJD3GUsUUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcUTns9+sw7XKKO5ZOYQninRAchypKHbqV2LinV46Hi\n" + + "bxYhBI+SjTgn0fulukOYj64YwkPcZSxRAADZtAEApse3UJi1iuSFvnyXxuYIOm4d\n" + + "0sOaOtd18venqfWGyX4BALf7T7LknMY688vaW6/xkw2fonG6Y5VxreIHlMZAcX0H\n" + + "FiEEt5ivGGv+TBmQLUlQVkfwATfvTEEAAFQ3AQCGSLEt8wgJZXlljPdk1eQ3uvW3\n" + + "VHryNAc3/vbSOvByFAD/WKXY8Pqki2r9XVUW33Q88firoiKVuGmBxklEG3ACjALO\n" + + "OARjBNFcEgorBgEEAZdVAQUBAQdARnMlx3ST0EHPiErN7lOF+lhtJ8FmW9arc46u\n" + + "sHFMgUMDAQgHwsAGBBgWCgB4BYJjBNFcBYkFn6YACRBWR/ABN+9MQUcUAAAAAAAe\n" + + "ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfv1PKQX1GMihAdj3ftW/yS\n" + + "bnPYdE+0h5rGCuhYl7sjaQKbDBYhBLeYrxhr/kwZkC1JUFZH8AE370xBAABWugEA\n" + + "rWOEHQjzoQkxxsErVEVZjqr05SLMmo6+HMJ/4Sgur10A/0+4FSbaKKNGiCnCMRsZ\n" + + "BEswoD99mUaBXl1nPH+Hg38O\n" + + "=+pb5\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + private static final String RON_FP = "b798af186bfe4c19902d49505647f00137ef4c41"; + + private static final String CEDRIC_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: 5E75 BF20 646B C1A9 8D3B 1BC2 FE9C D472 987C 4021\n" + + "Comment: Cedric Diggory \n" + + "\n" + + "xjMEYwTIyhYJKwYBBAHaRw8BAQdA80cyaoAEfh/ENuHw8XtWqrxDoPQ/x44LQzyO\n" + + "TLhMN+PCwBEEHxYKAIMFgmMEyMoFiQWfpgADCwkHCRD+nNRymHxAIUcUAAAAAAAe\n" + + "ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmf0ckQJQzwOKkXPe8rFP5B+\n" + + "CbAshRG5OKD3Dp+hScGFXgMVCggCmwECHgEWIQRedb8gZGvBqY07G8L+nNRymHxA\n" + + "IQAA9WYBAP5rQCq/W3KV90T/wpxf5pcXoCB4tCC9Gi/1AiuGhQdAAP48PIX9fH+T\n" + + "g7N+tU0xzzCc2nWxG3cIuvGFsg94pKL8As0gQ2VkcmljIERpZ2dvcnkgPGNlZHJp\n" + + "Y0BkaWdnby5yeT7CwBQEExYKAIYFgmMEyMoFiQWfpgADCwkHCRD+nNRymHxAIUcU\n" + + "AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmdkUL5mF5SwIXja\n" + + "bCxhI3lvqiUURSoLY13K6YvHYLz7bwMVCggCmQECmwECHgEWIQRedb8gZGvBqY07\n" + + "G8L+nNRymHxAIQAA6SwA/jiM8k/Z0ljnHdFxsdoLhdnTZ0yJT/7RxreSZ3aITrDs\n" + + "AP9V8bAYy4hK0C7i4FmNcos3HQs2Si6ee2/EZjo8LqxeCc4zBGMEyMoWCSsGAQQB\n" + + "2kcPAQEHQIu0hKMngTnmIPXlZ/p9WOZmLB0s9v9yZJLdZ5ICKn7jwsDFBBgWCgE3\n" + + "BYJjBMjKBYkFn6YACRD+nNRymHxAIUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5z\n" + + "ZXF1b2lhLXBncC5vcmdCT1SyOVJwTPp4OEDWFNEgxKD12H+Dya9EzOMJ3I9frwKb\n" + + "Ar6gBBkWCgBvBYJjBMjKCRDNPli8d9EIkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmccLTSNIhZOiNFaTj76iAutuAkUCImFp5ptMICZRo7E\n" + + "TRYhBESzEAYRbxRfM3ub5c0+WLx30QiRAAAZtwD/WRJrSxzJRsnZs4w+QgZjqOZx\n" + + "bOGwGObfbEHaExG0cKEA/R+BFODg5oPOvK9W7n0Kt9O171Po+zXB0UDmBiEhh0YL\n" + + "FiEEXnW/IGRrwamNOxvC/pzUcph8QCEAAEneAQDnOv/cf1/qmjfLnorEi+Z4gRWQ\n" + + "fp3Rp/gI4SLUQxT0PQD/USZIP0bNMGGC1TRQa+8nK6opSqtIvsatt0tQuu178A7O\n" + + "OARjBMjKEgorBgEEAZdVAQUBAQdAazcEUsYtY9f9o4A+ePR7ACMIDScVEUWS83+I\n" + + "SwJQz3QDAQgHwsAGBBgWCgB4BYJjBMjKBYkFn6YACRD+nNRymHxAIUcUAAAAAAAe\n" + + "ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmc/qxMatwD+6zaKDZGlVdn/\n" + + "TWumSgLtuyYonaOupIfMEAKbDBYhBF51vyBka8GpjTsbwv6c1HKYfEAhAADPiwEA\n" + + "vQ7fTnAHcdZlMVnNPkc0pZSp1+kO5Z789I5Pp4HloNIBAMoC84ja83PjvcpIyxgR\n" + + "kspLC9BliezVbFSHIK9NQ/wC\n" + + "=VemI\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + private static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021"; + + @Test + public void lockDirectoryAndInsertWillFail() throws IOException, InterruptedException, BadDataException { + PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( + new TestKeyMaterialReaderBackend()); + + // Manually lock the dir + assertFalse(directory.backend.getLock().isLocked()); + directory.backend.getLock().lockDirectory(); + assertTrue(directory.backend.getLock().isLocked()); + assertFalse(directory.backend.getLock().tryLockDirectory()); + + Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + assertNull(inserted); + + directory.backend.getLock().releaseDirectory(); + inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + assertNotNull(inserted); + } + + @Test + public void getByInvalidNameFails() { + PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( + new TestKeyMaterialReaderBackend()); + + assertThrows(BadNameException.class, () -> directory.getBySpecialName("invalid")); + } + + @Test + public void testInsertAndGetSingleCert() throws BadDataException, IOException, InterruptedException, BadNameException { + PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( + new TestKeyMaterialReaderBackend()); + + assertNull(directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); + + ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)); + + Certificate certificate = directory.insert(bytesIn, new TestKeyMaterialMerger()); + assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match"); + + Certificate get = directory.getByFingerprint(CEDRIC_FP); + assertEquals(CEDRIC_FP, get.getFingerprint(), "Fingerprint of retrieved cert MUST match"); + + byte[] expected = CEDRIC_CERT.getBytes(UTF8); + ByteArrayOutputStream actual = new ByteArrayOutputStream(); + Streams.pipeAll(get.getInputStream(), actual); + assertArrayEquals(expected, actual.toByteArray(), "InputStream of cert MUST match what we gave in"); + } + + @Test + public void testInsertAndGetTrustRootAndCert() throws BadDataException, IOException, InterruptedException { + PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( + new TestKeyMaterialReaderBackend()); + + assertNull(directory.getTrustRoot()); + + KeyMaterial trustRootMaterial = directory.insertTrustRoot( + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), new TestKeyMaterialMerger()); + assertNotNull(trustRootMaterial); + assertTrue(trustRootMaterial instanceof Key); + assertEquals(HARRY_FP, trustRootMaterial.getFingerprint()); + + Key trustRoot = (Key) directory.getTrustRoot(); + assertEquals(HARRY_FP, trustRoot.getFingerprint()); + Certificate trustRootCert = directory.getTrustRootCertificate(); + assertEquals(HARRY_FP, trustRootCert.getFingerprint()); + + directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + + Iterator fingerprints = directory.fingerprints(); + assertEquals(RON_FP, fingerprints.next()); + assertEquals(CEDRIC_FP, fingerprints.next()); + assertFalse(fingerprints.hasNext()); + } +} diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java new file mode 100644 index 0000000..6810f0b --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.certificate.KeyMaterial; +import pgp.certificate_store.certificate.KeyMaterialMerger; + +import java.io.IOException; + +public class TestKeyMaterialMerger implements KeyMaterialMerger { + @Override + public KeyMaterial merge(KeyMaterial data, KeyMaterial existing) throws IOException { + return data; + } +} diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java new file mode 100644 index 0000000..d653837 --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.Streams; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.Key; +import pgp.certificate_store.certificate.KeyMaterial; +import pgp.certificate_store.certificate.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.BadDataException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend { + + KeyFingerPrintCalculator fpCalc = new BcKeyFingerprintCalculator(); + + @Override + public KeyMaterial read(InputStream data) throws IOException, BadDataException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(data, out); + + try { + Key key = readKey(new ByteArrayInputStream(out.toByteArray())); + return key; + } catch (IOException | PGPException e) { + try { + Certificate certificate = readCertificate(new ByteArrayInputStream(out.toByteArray())); + return certificate; + } catch (IOException e1) { + throw new BadDataException(); + } + } + } + + private Key readKey(InputStream inputStream) throws IOException, PGPException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + Streams.pipeAll(inputStream, buffer); + inputStream.close(); + + InputStream decoderStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(buffer.toByteArray())); + + PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(decoderStream, fpCalc); + PGPPublicKeyRing cert = extractCert(secretKeys); + ByteArrayInputStream encoded = new ByteArrayInputStream(cert.getEncoded()); + Certificate certificate = readCertificate(encoded); + + return new Key() { + @Override + public Certificate getCertificate() { + return certificate; + } + + @Override + public String getFingerprint() { + return certificate.getFingerprint(); + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(buffer.toByteArray()); + } + + @Override + public String getTag() throws IOException { + return null; + } + + @Override + public List getSubkeyIds() throws IOException { + return certificate.getSubkeyIds(); + } + }; + } + + private Certificate readCertificate(InputStream inputStream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + Streams.pipeAll(inputStream, buffer); + ByteArrayInputStream in = new ByteArrayInputStream(buffer.toByteArray()); + InputStream decoderStream = PGPUtil.getDecoderStream(in); + + PGPPublicKeyRing cert = new PGPPublicKeyRing(decoderStream, fpCalc); + return new Certificate() { + @Override + public String getFingerprint() { + return Hex.toHexString(cert.getPublicKey().getFingerprint()).toLowerCase(); + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(buffer.toByteArray()); + } + + @Override + public String getTag() throws IOException { + return null; + } + + @Override + public List getSubkeyIds() throws IOException { + return TestKeyMaterialReaderBackend.getSubkeyIds(cert); + } + }; + } + + private PGPPublicKeyRing extractCert(PGPSecretKeyRing secretKeys) { + List publicKeyList = new ArrayList<>(); + Iterator publicKeyIterator = secretKeys.getPublicKeys(); + while (publicKeyIterator.hasNext()) { + publicKeyList.add(publicKeyIterator.next()); + } + PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList); + return publicKeyRing; + } + + private static List getSubkeyIds(PGPKeyRing keyRing) { + List keyIds = new ArrayList<>(); + Iterator keys = keyRing.getPublicKeys(); + while (keys.hasNext()) { + keyIds.add(keys.next().getKeyID()); + } + return keyIds; + } +} diff --git a/version.gradle b/version.gradle index a924cd3..0287d07 100644 --- a/version.gradle +++ b/version.gradle @@ -8,6 +8,7 @@ allprojects { isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 + bouncycastleVersion = '1.71' slf4jVersion = '1.7.36' logbackVersion = '1.2.11' junitVersion = '5.8.2'