From eab31b8c1242196d13b228c3a0f504c2ede42e5c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 12:02:01 +0200 Subject: [PATCH 01/18] Cert-D-Java 0.2.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 8e3b938..db46d9a 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.2.0' - isSnapshot = false + shortVersion = '0.2.1' + isSnapshot = true minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From a248e0d7177c5174297a20a3558382f09a998ab3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 12:16:53 +0200 Subject: [PATCH 02/18] Throw NoSuchElementException for non-existent certificates/keys Fixes #2 --- .../pgp/cert_d/PGPCertificateDirectory.java | 20 +++++-- .../FileBasedCertificateDirectoryBackend.java | 3 +- .../cert_d/PGPCertificateDirectoryTest.java | 57 ++++++++++++++++++- .../PGPCertificateStoreAdapterTest.java | 5 +- 4 files changed, 75 insertions(+), 10 deletions(-) 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 1d54212..87d2512 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 @@ -15,6 +15,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; @@ -46,13 +48,17 @@ public class PGPCertificateDirectory if (!openPgpV4FingerprintPattern.matcher(fingerprint).matches()) { throw new BadNameException(); } - return backend.readByFingerprint(fingerprint); + Certificate certificate = backend.readByFingerprint(fingerprint); + if (certificate == null) { + throw new NoSuchElementException(); + } + return certificate; } @Override public Certificate getByFingerprintIfChanged(String fingerprint, long tag) throws IOException, BadNameException, BadDataException { - if (tag != backend.getTagForFingerprint(fingerprint)) { + if (!Objects.equals(tag, backend.getTagForFingerprint(fingerprint))) { return getByFingerprint(fingerprint); } return null; @@ -66,13 +72,13 @@ public class PGPCertificateDirectory if (keyMaterial != null) { return keyMaterial.asCertificate(); } - return null; + throw new NoSuchElementException(); } @Override public Certificate getBySpecialNameIfChanged(String specialName, long tag) throws IOException, BadNameException, BadDataException { - if (tag != backend.getTagForSpecialName(specialName)) { + if (!Objects.equals(tag, backend.getTagForSpecialName(specialName))) { return getBySpecialName(specialName); } return null; @@ -121,7 +127,11 @@ public class PGPCertificateDirectory @Override public KeyMaterial getTrustRoot() throws IOException, BadDataException { try { - return backend.readBySpecialName(SpecialNames.TRUST_ROOT); + KeyMaterial keyMaterial = backend.readBySpecialName(SpecialNames.TRUST_ROOT); + if (keyMaterial == null) { + throw new NoSuchElementException(); + } + return keyMaterial; } catch (BadNameException e) { throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is implementation MUST"); } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java index 89e52ba..08ac356 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.regex.Pattern; /** @@ -348,7 +349,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec private Long getTag(File file) throws IOException { if (!file.exists()) { - throw new IllegalArgumentException("File MUST exist."); + throw new NoSuchElementException(); } Path path = file.toPath(); BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); 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 index 4ffb865..93ff6db 100644 --- 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 @@ -31,6 +31,7 @@ import java.nio.file.Files; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Set; import java.util.stream.Stream; @@ -68,6 +69,55 @@ public class PGPCertificateDirectoryTest { Arguments.of(Named.of("FileBasedCertificateDirectory", fileBased))); } + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentCertByFingerprintThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getByFingerprint("0000000000000000000000000000000000000000")); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentCertByFingerprintIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getByFingerprintIfChanged("0000000000000000000000000000000000000000", 12)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentCertBySpecialNameThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getBySpecialName(SpecialNames.TRUST_ROOT)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentCertBySpecialNameIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, 12)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentTrustRootThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getTrustRoot()); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentTrustRootIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getTrustRootCertificateIfChanged(12)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getNonExistentTrustRootCertificateThrowsNoSuchElementException(PGPCertificateDirectory directory) { + assertThrows(NoSuchElementException.class, () -> + directory.getTrustRootCertificate()); + } + @ParameterizedTest @MethodSource("provideTestSubjects") public void lockDirectoryAndTryInsertWillFail(PGPCertificateDirectory directory) @@ -130,7 +180,7 @@ public class PGPCertificateDirectoryTest { @MethodSource("provideTestSubjects") public void testInsertAndGetSingleCert(PGPCertificateDirectory directory) throws BadDataException, IOException, InterruptedException, BadNameException { - assertNull(directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); + assertThrows(NoSuchElementException.class, () -> directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match"); @@ -148,7 +198,7 @@ public class PGPCertificateDirectoryTest { @MethodSource("provideTestSubjects") public void testInsertAndGetTrustRootAndCert(PGPCertificateDirectory directory) throws BadDataException, IOException, InterruptedException { - assertNull(directory.getTrustRoot()); + assertThrows(NoSuchElementException.class, () -> directory.getTrustRoot()); KeyMaterial trustRootMaterial = directory.insertTrustRoot( TestKeys.getHarryKey(), merger); @@ -188,6 +238,7 @@ public class PGPCertificateDirectoryTest { assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1)); Long oldTag = tag; + Thread.sleep(10); // "update" key trustRootMaterial = directory.insertTrustRoot( TestKeys.getHarryKey(), merger); @@ -241,10 +292,12 @@ public class PGPCertificateDirectoryTest { Long oldTag = tag; + Thread.sleep(10); // Change the file on disk directly, this invalidates the tag due to changed modification date File certFile = resolver.getCertFileByFingerprint(certificate.getFingerprint()); FileOutputStream fileOut = new FileOutputStream(certFile); Streams.pipeAll(certificate.getInputStream(), fileOut); + fileOut.write("\n".getBytes()); fileOut.close(); // Old invalidated tag indicates a change, so the modified certificate is returned diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java index 56fdac6..79b5fed 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java @@ -17,6 +17,7 @@ import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -52,7 +53,7 @@ public class PGPCertificateStoreAdapterTest { @Test public void testInsertGetCertificate() throws BadDataException, IOException, InterruptedException, BadNameException { - assertNull(adapter.getCertificate(TestKeys.CEDRIC_FP)); + assertThrows(NoSuchElementException.class, () -> adapter.getCertificate(TestKeys.CEDRIC_FP)); assertFalse(adapter.getCertificates().hasNext()); Certificate certificate = adapter.insertCertificate(TestKeys.getCedricCert(), merger); @@ -70,7 +71,7 @@ public class PGPCertificateStoreAdapterTest { @Test public void testInsertGetTrustRoot() throws BadDataException, BadNameException, IOException, InterruptedException { - assertNull(adapter.getCertificate(SpecialNames.TRUST_ROOT)); + assertThrows(NoSuchElementException.class, () -> adapter.getCertificate(SpecialNames.TRUST_ROOT)); Certificate certificate = adapter.insertCertificateBySpecialName( SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); From 24f4e2d771153023595749f54f6a113b73045180 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 12:36:07 +0200 Subject: [PATCH 03/18] Add test for creating stores in write-protected places --- CHANGELOG.md | 3 ++ .../cert_d/PGPCertificateDirectoryTest.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2475e..9b13195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog +## 0.2.1-SNAPSHOT +- Throw `NoSuchElementException` when querying non-existent certificates + ## 0.2.0 - `pgp-certificate-store`: - Rework `Certificate`, `Key` to inherit from `KeyMaterial` 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 index 93ff6db..5545807 100644 --- 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 @@ -14,10 +14,12 @@ import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; import pgp.cert_d.dummy.TestKeyMaterialMerger; import pgp.cert_d.dummy.TestKeyMaterialReaderBackend; import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookup; import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.certificate.Key; import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.certificate.KeyMaterialReaderBackend; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; @@ -43,6 +45,8 @@ 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; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static pgp.cert_d.TestKeys.CEDRIC_FP; import static pgp.cert_d.TestKeys.HARRY_FP; import static pgp.cert_d.TestKeys.RON_FP; @@ -309,4 +313,30 @@ public class PGPCertificateDirectoryTest { assertNotEquals(oldTag, tag); assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); } + + @Test + public void fileBasedStoreInWriteProtectedAreaThrows() { + File root = new File("/"); + assumeTrue(root.exists(), "This test only runs on unix-like systems"); + File baseDirectory = new File(root, "pgp.cert.d"); + assumeFalse(baseDirectory.mkdirs(), "This test assumes that we cannot create dirs in /"); + + KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); + SubkeyLookup lookup = new InMemorySubkeyLookup(); + assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( + reader, baseDirectory, lookup)); + } + + @Test + public void fileBasedStoreOnFileThrows() throws IOException { + File tempDir = Files.createTempDirectory("containsAFile").toFile(); + tempDir.deleteOnExit(); + File baseDir = new File(tempDir, "pgp.cert.d"); + baseDir.createNewFile(); // this is a file, not a dir + + KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); + SubkeyLookup lookup = new InMemorySubkeyLookup(); + assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( + reader, baseDir, lookup)); + } } From ceaa1e0c808e1fdcff24f4e15b61153bbbf2fe4e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 13:11:11 +0200 Subject: [PATCH 04/18] More tests --- .../FileBasedPGPCertificateDirectoryTest.java | 131 ++++++++++++++++++ .../cert_d/PGPCertificateDirectoryTest.java | 91 ++++-------- 2 files changed, 161 insertions(+), 61 deletions(-) create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/FileBasedPGPCertificateDirectoryTest.java diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/FileBasedPGPCertificateDirectoryTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/FileBasedPGPCertificateDirectoryTest.java new file mode 100644 index 0000000..8701e33 --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FileBasedPGPCertificateDirectoryTest.java @@ -0,0 +1,131 @@ +// 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.cert_d.backend.FileBasedCertificateDirectoryBackend; +import pgp.cert_d.dummy.TestKeyMaterialMerger; +import pgp.cert_d.dummy.TestKeyMaterialReaderBackend; +import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookup; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.certificate.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; +import pgp.certificate_store.exception.NotAStoreException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +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.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class FileBasedPGPCertificateDirectoryTest { + + private static final KeyMaterialMerger merger = new TestKeyMaterialMerger(); + @Test + public void testFileBasedCertificateDirectoryTagChangesWhenFileChanges() + throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException { + File tempDir = Files.createTempDirectory("file-based-changes").toFile(); + tempDir.deleteOnExit(); + PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory( + new TestKeyMaterialReaderBackend(), + tempDir, + new InMemorySubkeyLookup()); + FileBasedCertificateDirectoryBackend.FilenameResolver resolver = + new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir); + + // Insert certificate + Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); + Long tag = certificate.getTag(); + assertNotNull(tag); + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + + Long oldTag = tag; + + Thread.sleep(10); + // Change the file on disk directly, this invalidates the tag due to changed modification date + File certFile = resolver.getCertFileByFingerprint(certificate.getFingerprint()); + FileOutputStream fileOut = new FileOutputStream(certFile); + Streams.pipeAll(certificate.getInputStream(), fileOut); + fileOut.write("\n".getBytes()); + fileOut.close(); + + // Old invalidated tag indicates a change, so the modified certificate is returned + certificate = directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag); + assertNotNull(certificate); + + // new tag is valid + tag = certificate.getTag(); + assertNotEquals(oldTag, tag); + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + } + + @Test + public void fileBasedStoreInWriteProtectedAreaThrows() { + File root = new File("/"); + assumeTrue(root.exists(), "This test only runs on unix-like systems"); + File baseDirectory = new File(root, "pgp.cert.d"); + assumeFalse(baseDirectory.mkdirs(), "This test assumes that we cannot create dirs in /"); + + KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); + SubkeyLookup lookup = new InMemorySubkeyLookup(); + assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( + reader, baseDirectory, lookup)); + } + + @Test + public void fileBasedStoreOnFileThrows() + throws IOException { + File tempDir = Files.createTempDirectory("containsAFile").toFile(); + tempDir.deleteOnExit(); + File baseDir = new File(tempDir, "pgp.cert.d"); + baseDir.createNewFile(); // this is a file, not a dir + + KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); + SubkeyLookup lookup = new InMemorySubkeyLookup(); + assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( + reader, baseDir, lookup)); + } + + @Test + public void testCertificateStoredUnderWrongFingerprintThrowsBadData() + throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException { + File tempDir = Files.createTempDirectory("wrong-fingerprint").toFile(); + tempDir.deleteOnExit(); + PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory( + new TestKeyMaterialReaderBackend(), + tempDir, + new InMemorySubkeyLookup()); + FileBasedCertificateDirectoryBackend.FilenameResolver resolver = + new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir); + + // Insert Rons certificate + directory.insert(TestKeys.getRonCert(), merger); + + // Copy Rons cert to Cedrics cert file + File ronCert = resolver.getCertFileByFingerprint(TestKeys.RON_FP); + FileInputStream inputStream = new FileInputStream(ronCert); + File cedricCert = resolver.getCertFileByFingerprint(TestKeys.CEDRIC_FP); + cedricCert.getParentFile().mkdirs(); + cedricCert.createNewFile(); + FileOutputStream outputStream = new FileOutputStream(cedricCert); + Streams.pipeAll(inputStream, outputStream); + inputStream.close(); + outputStream.close(); + + // Reading cedrics cert will fail, as it has Rons fingerprint + assertThrows(BadDataException.class, () -> directory.getByFingerprint(TestKeys.CEDRIC_FP)); + } +} 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 index 5545807..f6bd858 100644 --- 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 @@ -6,27 +6,22 @@ package pgp.cert_d; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; import pgp.cert_d.dummy.TestKeyMaterialMerger; import pgp.cert_d.dummy.TestKeyMaterialReaderBackend; import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; -import pgp.cert_d.subkey_lookup.SubkeyLookup; import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.certificate.Key; import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterialMerger; -import pgp.certificate_store.certificate.KeyMaterialReaderBackend; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; @@ -45,8 +40,6 @@ 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; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import static pgp.cert_d.TestKeys.CEDRIC_FP; import static pgp.cert_d.TestKeys.HARRY_FP; import static pgp.cert_d.TestKeys.RON_FP; @@ -277,66 +270,42 @@ public class PGPCertificateDirectoryTest { assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1)); } - @Test - public void testFileBasedCertificateDirectoryTagChangesWhenFileChanges() throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException { - File tempDir = Files.createTempDirectory("file-based-changes").toFile(); - tempDir.deleteOnExit(); - PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory( - new TestKeyMaterialReaderBackend(), - tempDir, - new InMemorySubkeyLookup()); - FileBasedCertificateDirectoryBackend.FilenameResolver resolver = - new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir); + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testOverwriteTrustRoot(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException { + directory.insertTrustRoot(TestKeys.getHarryKey(), merger); + assertEquals(HARRY_FP, directory.getTrustRoot().getFingerprint()); + assertTrue(directory.getTrustRoot() instanceof Key); - // Insert certificate - Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); - Long tag = certificate.getTag(); - assertNotNull(tag); - assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); - - Long oldTag = tag; - - Thread.sleep(10); - // Change the file on disk directly, this invalidates the tag due to changed modification date - File certFile = resolver.getCertFileByFingerprint(certificate.getFingerprint()); - FileOutputStream fileOut = new FileOutputStream(certFile); - Streams.pipeAll(certificate.getInputStream(), fileOut); - fileOut.write("\n".getBytes()); - fileOut.close(); - - // Old invalidated tag indicates a change, so the modified certificate is returned - certificate = directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag); - assertNotNull(certificate); - - // new tag is valid - tag = certificate.getTag(); - assertNotEquals(oldTag, tag); - assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + directory.insertTrustRoot(TestKeys.getCedricCert(), merger); + assertEquals(CEDRIC_FP, directory.getTrustRoot().getFingerprint()); + assertTrue(directory.getTrustRoot() instanceof Certificate); } - @Test - public void fileBasedStoreInWriteProtectedAreaThrows() { - File root = new File("/"); - assumeTrue(root.exists(), "This test only runs on unix-like systems"); - File baseDirectory = new File(root, "pgp.cert.d"); - assumeFalse(baseDirectory.mkdirs(), "This test assumes that we cannot create dirs in /"); + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testOverwriteSpecialName(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException, BadNameException { + directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getRonCert(), merger); + KeyMaterial bySpecialName = directory.getBySpecialName(SpecialNames.TRUST_ROOT); + assertEquals(RON_FP, bySpecialName.getFingerprint()); - KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); - SubkeyLookup lookup = new InMemorySubkeyLookup(); - assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( - reader, baseDirectory, lookup)); + directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); + assertEquals(HARRY_FP, directory.getBySpecialName(SpecialNames.TRUST_ROOT).getFingerprint()); } - @Test - public void fileBasedStoreOnFileThrows() throws IOException { - File tempDir = Files.createTempDirectory("containsAFile").toFile(); - tempDir.deleteOnExit(); - File baseDir = new File(tempDir, "pgp.cert.d"); - baseDir.createNewFile(); // this is a file, not a dir + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testOverwriteByFingerprint(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException, BadNameException { + directory.insert(TestKeys.getRonCert(), merger); + Certificate extracted = directory.getByFingerprint(RON_FP); + assertEquals(RON_FP, extracted.getFingerprint()); - KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend(); - SubkeyLookup lookup = new InMemorySubkeyLookup(); - assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory( - reader, baseDir, lookup)); + directory.insert(TestKeys.getRonCert(), merger); + extracted = directory.getByFingerprint(RON_FP); + assertEquals(RON_FP, extracted.getFingerprint()); } + } From b82773989253d1b320ceb7108439797c15149347 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 13:20:58 +0200 Subject: [PATCH 05/18] Fix link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb3cb90..4dde81f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0 This repository contains a number of modules defining OpenPGP certificate storage for Java and Android applications. -The module [pgp-certificate-store](pgp-certificate-store] defines generalized +The module [pgp-certificate-store](pgp-certificate-store) defines generalized interfaces for OpenPGP Certificate storage. It can be used by applications and libraries such as [PGPainless](https://pgpainless.org/) for certificate management. From 08837f4999daed83986c7326503acfce1bc13d03 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 13:24:27 +0200 Subject: [PATCH 06/18] Cert-D-Java 0.2.1 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b13195..be93a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog -## 0.2.1-SNAPSHOT +## 0.2.1 - Throw `NoSuchElementException` when querying non-existent certificates ## 0.2.0 diff --git a/version.gradle b/version.gradle index db46d9a..ffcf6e3 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '0.2.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From 190194f932c5e7d44ecd5f8df0366aefbee34a03 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 13:26:26 +0200 Subject: [PATCH 07/18] Cert-D-Java 0.2.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index ffcf6e3..ce74cbd 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.2.1' - isSnapshot = false + shortVersion = '0.2.2' + isSnapshot = true minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From 0e8bf060f2cbb3f8c7308b73c690d0297899e354 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 27 Aug 2022 13:33:52 +0200 Subject: [PATCH 08/18] Update javadoc to reflect what happens when a cert is queried but not found --- .../cert_d/ReadOnlyPGPCertificateDirectory.java | 16 ++++++++++------ .../certificate_store/PGPCertificateStore.java | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java index 0b1416c..72b048b 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java @@ -10,6 +10,7 @@ import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.util.Iterator; +import java.util.NoSuchElementException; /** * Interface for a read-only OpenPGP certificate directory. @@ -19,12 +20,12 @@ public interface ReadOnlyPGPCertificateDirectory { /** * Get the trust-root certificate. This is a certificate which is stored under the special name *
trust-root
. - * If no such certificate is found,
null
is returned. * * @return trust-root certificate * * @throws IOException in case of an IO error * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getTrustRootCertificate() throws IOException, BadDataException; @@ -36,24 +37,25 @@ public interface ReadOnlyPGPCertificateDirectory { * Otherwise. the changed certificate is returned. * * @param tag tag - * @return changed certificate, or null if the certificate is unchanged or not found. + * @return changed certificate, or null if the certificate is unchanged. * * @throws IOException in case of an IO error * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getTrustRootCertificateIfChanged(long tag) throws IOException, BadDataException; /** * Get the certificate identified by the given fingerprint. - * If no such certificate is found, return
null
. * * @param fingerprint lower-case fingerprint of the certificate - * @return certificate or null if no such certificate has been found + * @return certificate * * @throws IOException in case of an IO error * @throws BadNameException if the fingerprint is malformed * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getByFingerprint(String fingerprint) throws IOException, BadNameException, BadDataException; @@ -66,18 +68,18 @@ public interface ReadOnlyPGPCertificateDirectory { * * @param fingerprint lower-case fingerprint of the certificate * @param tag tag - * @return certificate or null if the certificate has not been changed or has not been found + * @return certificate or null if the certificate has not been changed * * @throws IOException in case of an IO error * @throws BadNameException if the fingerprint is malformed * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getByFingerprintIfChanged(String fingerprint, long tag) throws IOException, BadNameException, BadDataException; /** * Get the certificate identified by the given special name. - * If no such certificate is found,
null
is returned. * * @param specialName special name * @return certificate or null @@ -85,6 +87,7 @@ public interface ReadOnlyPGPCertificateDirectory { * @throws IOException in case of an IO error * @throws BadNameException if the special name is not known * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getBySpecialName(String specialName) throws IOException, BadNameException, BadDataException; @@ -102,6 +105,7 @@ public interface ReadOnlyPGPCertificateDirectory { * @throws IOException in case of an IO error * @throws BadNameException if the special name is not known * @throws BadDataException if the certificate contains bad data + * @throws NoSuchElementException if no such certificate is found */ Certificate getBySpecialNameIfChanged(String specialName, long tag) throws IOException, BadNameException, BadDataException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java index d0915b0..251229f 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java @@ -12,6 +12,7 @@ import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; +import java.util.NoSuchElementException; /** * Interface for an OpenPGP certificate (public key) store. @@ -20,7 +21,6 @@ public interface PGPCertificateStore { /** * Return the certificate that matches the given identifier. - * If no matching certificate can be found, return null. * * @param identifier identifier for a certificate. * @return certificate or null @@ -28,6 +28,7 @@ public interface PGPCertificateStore { * @throws IOException in case of an IO-error * @throws BadNameException if the identifier is invalid * @throws BadDataException if the certificate file contains invalid data + * @throws NoSuchElementException if no such certificate is found */ Certificate getCertificate(String identifier) throws IOException, BadNameException, BadDataException; @@ -45,6 +46,7 @@ public interface PGPCertificateStore { * @throws IOException in case of an IO-error * @throws BadNameException if the identifier is invalid * @throws BadDataException if the certificate file contains invalid data + * @throws NoSuchElementException if no such certificate is found */ Certificate getCertificateIfChanged(String identifier, Long tag) throws IOException, BadNameException, BadDataException; From 6ae12c1262e441c7684968e59761c90541319608 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Nov 2022 16:04:14 +0100 Subject: [PATCH 09/18] Fix test running inside of flatpaked intellij resolving custom XDG_DATA_HOME --- .../src/test/java/pgp/cert_d/BaseDirectoryProviderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/BaseDirectoryProviderTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/BaseDirectoryProviderTest.java index 357ba3f..f6af1be 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/BaseDirectoryProviderTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/BaseDirectoryProviderTest.java @@ -18,7 +18,7 @@ public class BaseDirectoryProviderTest { public void testGetDefaultBaseDir_Linux() { assumeTrue(System.getProperty("os.name").equalsIgnoreCase("linux")); File baseDir = BaseDirectoryProvider.getDefaultBaseDirForOS("linux"); - assertTrue(baseDir.getAbsolutePath().endsWith("/.local/share/pgp.cert.d")); + assertTrue(baseDir.getAbsolutePath().endsWith("pgp.cert.d")); } @Test From aa521bac47d3042142bdca3d4852c4cf4419be57 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Nov 2022 16:04:36 +0100 Subject: [PATCH 10/18] Bump Bouncycastle to 1.72 --- pgp-cert-d-java/build.gradle | 2 +- version.gradle | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index d59867d..19f1329 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -32,7 +32,7 @@ dependencies { testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") testImplementation "org.bouncycastle:bcprov-jdk15to18:$bouncycastleVersion" - testImplementation "org.bouncycastle:bcpg-jdk15to18:$bouncycastleVersion" + testImplementation "org.bouncycastle:bcpg-jdk15to18:$bouncyPgVersion" } animalsniffer { diff --git a/version.gradle b/version.gradle index ce74cbd..1a47bb2 100644 --- a/version.gradle +++ b/version.gradle @@ -9,7 +9,8 @@ allprojects { minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 - bouncycastleVersion = '1.71' + bouncycastleVersion = '1.72' + bouncyPgVersion = '1.72.3' slf4jVersion = '1.7.36' logbackVersion = '1.2.11' junitVersion = '5.8.2' From 8a3337f1f59e22de3326d898515c1a919a073962 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Nov 2022 16:06:58 +0100 Subject: [PATCH 11/18] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be93a9d..07cd24f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog +## 0.2.2-SNAPSHOT +- Bump Bouncy Castle to `1.72` + ## 0.2.1 - Throw `NoSuchElementException` when querying non-existent certificates From ac5097482aea909f36a1325cdcdef2ab5b498d0f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 25 Jun 2023 11:19:49 +0200 Subject: [PATCH 12/18] Documentation is lying --- .../main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java index 72b048b..1b27ffb 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java @@ -82,7 +82,7 @@ public interface ReadOnlyPGPCertificateDirectory { * Get the certificate identified by the given special name. * * @param specialName special name - * @return certificate or null + * @return certificate * * @throws IOException in case of an IO error * @throws BadNameException if the special name is not known From fd43ef27ba46a1c7197a87cd59bf2b45c2d5308c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 11:42:02 +0200 Subject: [PATCH 13/18] Bump Bouncy Castle to 1.75 --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cd24f..614fefb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog ## 0.2.2-SNAPSHOT -- Bump Bouncy Castle to `1.72` +- Bump Bouncy Castle to `1.75` ## 0.2.1 - Throw `NoSuchElementException` when querying non-existent certificates diff --git a/version.gradle b/version.gradle index 1a47bb2..2436778 100644 --- a/version.gradle +++ b/version.gradle @@ -9,8 +9,8 @@ allprojects { minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 - bouncycastleVersion = '1.72' - bouncyPgVersion = '1.72.3' + bouncycastleVersion = '1.75' + bouncyPgVersion = "$bouncycastleVersion" slf4jVersion = '1.7.36' logbackVersion = '1.2.11' junitVersion = '5.8.2' From 7d670297482671d4f57cfdc9df7996e4bc8458df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 12:00:25 +0200 Subject: [PATCH 14/18] Bump sqlite-jdbc from 3.36.0.3 to 3.42.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 2436778..956dfc0 100644 --- a/version.gradle +++ b/version.gradle @@ -14,6 +14,6 @@ allprojects { slf4jVersion = '1.7.36' logbackVersion = '1.2.11' junitVersion = '5.8.2' - sqliteJdbcVersion = '3.36.0.3' + sqliteJdbcVersion = '3.42.0.0' } } From 8b14f76add4802c591d87e61fe292d3735744917 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 12:00:38 +0200 Subject: [PATCH 15/18] Fix version name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614fefb..4532094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog -## 0.2.2-SNAPSHOT +## 0.2.2 - Bump Bouncy Castle to `1.75` ## 0.2.1 From 26666fa3e6dd348842562d843817919f85feeebf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 12:01:41 +0200 Subject: [PATCH 16/18] Cert-D-Java 0.2.2 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 956dfc0..600f6e2 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '0.2.2' - isSnapshot = true + isSnapshot = false minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From 9d20355a589b8ed92bc3fc174c28471cd8e3192d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 12:04:56 +0200 Subject: [PATCH 17/18] Cert-D-Java 0.2.3-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 600f6e2..2903026 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.2.2' - isSnapshot = false + shortVersion = '0.2.3' + isSnapshot = true minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From e4128a002acb695803443897452439488d4dbfbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Jul 2023 12:05:07 +0200 Subject: [PATCH 18/18] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4532094..f2bfcca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ SPDX-License-Identifier: CC0-1.0 ## 0.2.2 - Bump Bouncy Castle to `1.75` +- Bump `sqlite-jdbc` to `3.42.0.0` ## 0.2.1 - Throw `NoSuchElementException` when querying non-existent certificates