From 5222c545367b252b0c18c4a1df6f7e3db8def8cd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 1 Mar 2022 16:13:59 +0100 Subject: [PATCH 01/63] Cert-D-Java 0.1.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index ca8cdd4..f776c6a 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.1.0' - isSnapshot = false + shortVersion = '0.1.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 } From 9efcae77de0422a656e0a8a7ecb438fb25e14ca0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 1 Mar 2022 17:13:08 +0100 Subject: [PATCH 02/63] Add badges and info to readme files --- pgp-cert-d-java-jdbc-sqlite-lookup/README.md | 12 ++++++++++++ pgp-cert-d-java/README.md | 7 ++++++- pgp-certificate-store/README.md | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 pgp-cert-d-java-jdbc-sqlite-lookup/README.md diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/README.md b/pgp-cert-d-java-jdbc-sqlite-lookup/README.md new file mode 100644 index 0000000..79a9113 --- /dev/null +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/README.md @@ -0,0 +1,12 @@ + + +# SQLite backed Subkey-ID Lookup + +[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgp-cert-d-java-jdbc-sqlite-lookup/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgp-cert-d-java-jdbc-sqlite-lookup) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgp-cert-d-java-jdbc-sqlite-lookup)](https://search.maven.org/artifact/org.pgpainless/pgp-cert-d-java-jdbc-sqlite-lookup) + +Implementation of `pgp-certificate-store`'s `SubkeyLookup` class using an SQLite database accessed through `sqlite-jdbc`. \ No newline at end of file diff --git a/pgp-cert-d-java/README.md b/pgp-cert-d-java/README.md index cb28958..58e29d7 100644 --- a/pgp-cert-d-java/README.md +++ b/pgp-cert-d-java/README.md @@ -6,6 +6,9 @@ SPDX-License-Identifier: Apache-2.0 # Shared PGP Certificate Directory for Java +[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgp-cert-d-java/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgp-cert-d-java) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgp-cert-d-java)](https://search.maven.org/artifact/org.pgpainless/pgp-cert-d-java) + Backend-agnostic implementation of the [Shared PGP Certificate Directory Specification](https://sequoia-pgp.gitlab.io/pgp-cert-d/). This module implements the non-OpenPGP parts of the spec, e.g. locating the directory, resolving certificate file paths, locking the directory for writes etc. @@ -13,4 +16,6 @@ locking the directory for writes etc. To get a useful implementation, a backend implementation such as `pgpainless-cert-d` is required, which needs to provide support for reading and merging certificates. -`pgp-cert-d-java` can be used as an implementation of `pgp-certificate-store`. \ No newline at end of file +`pgp-cert-d-java` can be used as an implementation of `pgp-certificate-store`. + +Note: This is a library module. For a command line interface, see [pgpainless-cert-d-cli](https://github.com/pgpainless/cert-d-pgpainless/tree/main/pgpainless-cert-d-cli). \ No newline at end of file diff --git a/pgp-certificate-store/README.md b/pgp-certificate-store/README.md index f6a0eba..58f9af9 100644 --- a/pgp-certificate-store/README.md +++ b/pgp-certificate-store/README.md @@ -6,5 +6,8 @@ SPDX-License-Identifier: Apache-2.0 # PGP Certificate Store Definitions +[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgp-certificate-store/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgp-certificate-store) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgp-certificate-store)](https://search.maven.org/artifact/org.pgpainless/pgp-certificate-store) + This module contains API definitions for an OpenPGP certificate store. A certificate store is used to store public key certificates only. From 540150cca9f293eeca74e7eac326824242fde12e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Apr 2022 21:37:20 +0200 Subject: [PATCH 03/63] Move dependency versions into version.gradle --- build.gradle | 3 --- version.gradle | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 78d1525..020268e 100644 --- a/build.gradle +++ b/build.gradle @@ -59,9 +59,6 @@ allprojects { } project.ext { - slf4jVersion = '1.7.32' - logbackVersion = '1.2.9' - junitVersion = '5.8.2' rootConfigDir = new File(rootDir, 'config') gitCommit = getGitCommit() isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI')) diff --git a/version.gradle b/version.gradle index f776c6a..5510b67 100644 --- a/version.gradle +++ b/version.gradle @@ -8,5 +8,8 @@ allprojects { isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 + slf4jVersion = '1.7.32' + logbackVersion = '1.2.9' + junitVersion = '5.8.2' } } From 50ab9208e6d9d9cbb96413ff4b8b990af1ffc192 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Apr 2022 01:33:48 +0200 Subject: [PATCH 04/63] Bump slf4j to 1.7.36 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 5510b67..b0f6a5d 100644 --- a/version.gradle +++ b/version.gradle @@ -8,7 +8,7 @@ allprojects { isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 - slf4jVersion = '1.7.32' + slf4jVersion = '1.7.36' logbackVersion = '1.2.9' junitVersion = '5.8.2' } From ba3d8b0230da1f974718557203a7a75746824843 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Apr 2022 01:34:12 +0200 Subject: [PATCH 05/63] Bump logback version to 1.2.11 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index b0f6a5d..cfeb65c 100644 --- a/version.gradle +++ b/version.gradle @@ -9,7 +9,7 @@ allprojects { minAndroidSdk = 10 javaSourceCompatibility = 1.8 slf4jVersion = '1.7.36' - logbackVersion = '1.2.9' + logbackVersion = '1.2.11' junitVersion = '5.8.2' } } From 42ecab5aff0243705918cb8402525a29a90c0543 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Apr 2022 01:35:40 +0200 Subject: [PATCH 06/63] Move sqlite-jdbc version to version.gradle --- pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle | 2 +- version.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle b/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle index 48e37bc..02c9cc5 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle @@ -21,7 +21,7 @@ dependencies { testImplementation "ch.qos.logback:logback-classic:$logbackVersion" implementation project(":pgp-cert-d-java") - api 'org.xerial:sqlite-jdbc:3.36.0.3' + api "org.xerial:sqlite-jdbc:$sqliteJdbcVersion" } test { diff --git a/version.gradle b/version.gradle index cfeb65c..01420a9 100644 --- a/version.gradle +++ b/version.gradle @@ -11,5 +11,6 @@ allprojects { slf4jVersion = '1.7.36' logbackVersion = '1.2.11' junitVersion = '5.8.2' + sqliteJdbcVersion = '3.36.0.3' } } From 0fee958740e771491d07284e031a6b1d220d8bb5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Apr 2022 01:38:13 +0200 Subject: [PATCH 07/63] Document build.gradle files --- pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle | 4 ++++ pgp-cert-d-java/build.gradle | 5 ++++- pgp-certificate-store/build.gradle | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle b/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle index 02c9cc5..8dfc516 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/build.gradle @@ -13,6 +13,7 @@ repositories { } dependencies { + // JUnit for testing testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" @@ -20,7 +21,10 @@ dependencies { // Logging testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + // pgp.cert.d cert store implementation project(":pgp-cert-d-java") + + // SQLite api "org.xerial:sqlite-jdbc:$sqliteJdbcVersion" } diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index baca518..db5ba5c 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -15,9 +15,10 @@ repositories { apply plugin: 'ru.vyarus.animalsniffer' dependencies { - // animal sniffer + // animal sniffer for ensuring Android API compatibility signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" + // JUnit testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" @@ -25,8 +26,10 @@ dependencies { // Logging testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + // SQL Subkey table testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") + // Certificate store api project(":pgp-certificate-store") } diff --git a/pgp-certificate-store/build.gradle b/pgp-certificate-store/build.gradle index 3bafe81..f2d7d7a 100644 --- a/pgp-certificate-store/build.gradle +++ b/pgp-certificate-store/build.gradle @@ -15,9 +15,10 @@ repositories { apply plugin: 'ru.vyarus.animalsniffer' dependencies { - // animal sniffer + // animal sniffer for ensuring Android API compatibility signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" + // JUnit for testing testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" From ce1469948f0cf54141f7a45b2fc47d190c009702 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Apr 2022 01:39:24 +0200 Subject: [PATCH 08/63] Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 352f9db..73243f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,5 +5,10 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog -# 0.1.0 +## 0.1.1-SNAPSHOT +- Bump `slf4j` to `1.7.36` +- Bump `logback` to `1.2.11` +- + +## 0.1.0 - Initial Release \ No newline at end of file From 27d48824b35678d3696fbff42a9bc89cdee2e98b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Apr 2022 16:31:49 +0200 Subject: [PATCH 09/63] Fix javadoc warnings --- CHANGELOG.md | 7 +++---- .../src/main/java/pgp/cert_d/FilenameResolver.java | 12 +++++++++++- .../src/main/java/pgp/cert_d/LockingMechanism.java | 7 +++++++ .../java/pgp/certificate_store/Certificate.java | 8 ++++++++ .../pgp/certificate_store/CertificateDirectory.java | 13 +++++++++++++ .../java/pgp/certificate_store/MergeCallback.java | 2 ++ .../java/pgp/certificate_store/SubkeyLookup.java | 3 +++ 7 files changed, 47 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73243f2..a78481a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,9 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog -## 0.1.1-SNAPSHOT +## 0.1.1 - Bump `slf4j` to `1.7.36` -- Bump `logback` to `1.2.11` -- +- Bump `logback` to `1.2.11` ## 0.1.0 -- Initial Release \ No newline at end of file +- Initial Release diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java index a753fd3..81ffc86 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java @@ -28,7 +28,8 @@ public class FilenameResolver { * * @param fingerprint fingerprint * @return absolute certificate file location - * @throws BadNameException + * + * @throws BadNameException if the given fingerprint string is not a fingerprint */ public File getCertFileByFingerprint(String fingerprint) throws BadNameException { if (!isFingerprint(fingerprint)) { @@ -41,6 +42,15 @@ public class FilenameResolver { return file; } + /** + * Calculate the file location for the certification addressed using the given special name. + * For known special names, see {@link SpecialNames}. + * + * @param specialName special name (e.g. "trust-root") + * @return absolute certificate file location + * + * @throws BadNameException in case the given special name is not known + */ public File getCertFileBySpecialName(String specialName) throws BadNameException { if (!isSpecialName(specialName)) { throw new BadNameException(); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java index 520a857..92e196d 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java @@ -11,6 +11,9 @@ public interface LockingMechanism { /** * Lock the store for writes. * Readers can continue to use the store and will always see consistent certs. + * + * @throws IOException in case of an IO error + * @throws InterruptedException if the thread gets interrupted */ void lockDirectory() throws IOException, InterruptedException; @@ -19,11 +22,15 @@ public interface LockingMechanism { * Return false without locking the store in case the store was already locked. * * @return true if locking succeeded, false otherwise + * + * @throws IOException in case of an IO error */ boolean tryLockDirectory() throws IOException; /** * Release the directory write-lock acquired via {@link #lockDirectory()}. + * + * @throws IOException in case of an IO error */ void releaseDirectory() throws IOException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java index b7aca12..ea3f363 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java @@ -21,6 +21,7 @@ public abstract class Certificate { * Return an {@link InputStream} of the binary representation of the certificate. * * @return input stream + * @throws IOException in case of an IO error */ public abstract InputStream getInputStream() throws IOException; @@ -29,8 +30,15 @@ public abstract class Certificate { * The tag is a checksum calculated over the binary representation of the certificate. * * @return tag + * @throws IOException in case of an IO error */ public abstract String getTag() throws IOException; + /** + * Return a {@link Set} containing key-ids of subkeys. + * + * @return subkeys + * @throws IOException in case of an IO error + */ public abstract Set getSubkeyIds() throws IOException; } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java index 29f5998..006970c 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java @@ -30,6 +30,8 @@ public interface CertificateDirectory { * @return certificate or null * * @throws IOException in case of an IO-error + * @throws BadNameException if the identifier is invalid + * @throws BadDataException if the certificate file contains invalid data */ Certificate getCertificate(String identifier) throws IOException, BadNameException, BadDataException; @@ -44,6 +46,8 @@ public interface CertificateDirectory { * @return changed certificate or null * * @throws IOException in case of an IO-error + * @throws BadNameException if the identifier is invalid + * @throws BadDataException if the certificate file contains invalid data */ Certificate getCertificateIfChanged(String identifier, String tag) throws IOException, BadNameException, BadDataException; @@ -63,6 +67,7 @@ public interface CertificateDirectory { * * @throws IOException in case of an IO-error * @throws InterruptedException in case the inserting thread gets interrupted + * @throws BadDataException if the data stream does not contain valid OpenPGP data */ Certificate insertCertificate(InputStream data, MergeCallback merge) throws IOException, InterruptedException, BadDataException; @@ -83,6 +88,7 @@ public interface CertificateDirectory { * @return merged certificate or null if the store cannot be locked * * @throws IOException in case of an IO-error + * @throws BadDataException if the data stream does not contain valid OpenPGP data */ Certificate tryInsertCertificate(InputStream data, MergeCallback merge) throws IOException, BadDataException; @@ -98,11 +104,15 @@ public interface CertificateDirectory { * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, MergeCallback)} instead. * + * @param specialName special name of the certificate * @param data input stream containing the new certificate instance * @param merge callback for merging with an existing certificate instance * @return merged certificate or null if the store cannot be locked * * @throws IOException in case of an IO-error + * @throws InterruptedException if the thread is interrupted + * @throws BadDataException if the certificate file does not contain valid OpenPGP data + * @throws BadNameException if the special name is unknown */ Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException, InterruptedException, BadDataException, BadNameException; @@ -120,11 +130,14 @@ public interface CertificateDirectory { * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock * and return the written certificate. * + * @param specialName special name for the certificate * @param data input stream containing the new certificate instance * @param merge callback for merging with an existing certificate instance * @return merged certificate or null if the store cannot be locked * * @throws IOException in case of an IO-error + * @throws BadDataException if the data stream does not contain valid OpenPGP data + * @throws BadNameException if the special name is not known */ Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java index a7cee53..9c9f162 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java @@ -19,6 +19,8 @@ public interface MergeCallback { * @param data certificate * @param existing optional already existing copy of the certificate * @return merged certificate + * + * @throws IOException in case of an IO error */ Certificate merge(Certificate data, Certificate existing) throws IOException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java index 73e396b..55d03e6 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java @@ -16,6 +16,8 @@ public interface SubkeyLookup { * * @param subkeyId subkey id * @return fingerprint of the certificate + * + * @throws IOException in case of an IO error */ Set getCertificateFingerprintsForSubkeyId(long subkeyId) throws IOException; @@ -25,6 +27,7 @@ public interface SubkeyLookup { * * @param certificate certificate fingerprint * @param subkeyIds subkey ids + * * @throws IOException in case of an IO error */ void storeCertificateSubkeyIds(String certificate, List subkeyIds) throws IOException; From 0b29624b43a68af658f626cd229dec65033a33b1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Apr 2022 16:34:15 +0200 Subject: [PATCH 10/63] Cert-D-Java 0.1.1 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 01420a9..8731281 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '0.1.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 slf4jVersion = '1.7.36' From dd2877ccee13ca98edd3fab7a0c32697c4d5638e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Apr 2022 16:36:01 +0200 Subject: [PATCH 11/63] Cert-D-Java 0.1.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 8731281..a924cd3 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.1.1' - isSnapshot = false + shortVersion = '0.1.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 slf4jVersion = '1.7.36' From 96b22ff40ae501905345495def22ff1271eb593e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 18:48:19 +0200 Subject: [PATCH 12/63] Rename MergeCallback to CertificateMerger --- .../main/java/pgp/cert_d/BackendProvider.java | 4 ++-- ...gSharedPGPCertificateDirectoryWrapper.java | 10 +++++----- .../cert_d/SharedPGPCertificateDirectory.java | 10 +++++----- .../SharedPGPCertificateDirectoryImpl.java | 14 ++++++------- .../CertificateDirectory.java | 20 +++++++++---------- ...geCallback.java => CertificateMerger.java} | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) rename pgp-certificate-store/src/main/java/pgp/certificate_store/{MergeCallback.java => CertificateMerger.java} (95%) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java index 70bc93c..be0d710 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java @@ -5,12 +5,12 @@ package pgp.cert_d; import pgp.certificate_store.CertificateReaderBackend; -import pgp.certificate_store.MergeCallback; +import pgp.certificate_store.CertificateMerger; public abstract class BackendProvider { public abstract CertificateReaderBackend provideCertificateReaderBackend(); - public abstract MergeCallback provideDefaultMergeCallback(); + public abstract CertificateMerger provideDefaultMergeCallback(); } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java index 820d55d..332ebd6 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java @@ -7,7 +7,7 @@ package pgp.cert_d; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.Certificate; -import pgp.certificate_store.MergeCallback; +import pgp.certificate_store.CertificateMerger; import java.io.IOException; import java.io.InputStream; @@ -127,7 +127,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper } @Override - public Certificate insert(InputStream data, MergeCallback merge) + public Certificate insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException, InterruptedException { Certificate certificate = underlyingCertificateDirectory.insert(data, merge); remember(certificate.getFingerprint(), certificate); @@ -135,7 +135,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper } @Override - public Certificate tryInsert(InputStream data, MergeCallback merge) + public Certificate tryInsert(InputStream data, CertificateMerger merge) throws IOException, BadDataException { Certificate certificate = underlyingCertificateDirectory.tryInsert(data, merge); if (certificate != null) { @@ -145,7 +145,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper } @Override - public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException, InterruptedException { Certificate certificate = underlyingCertificateDirectory.insertWithSpecialName(specialName, data, merge); remember(specialName, certificate); @@ -153,7 +153,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper } @Override - public Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + public Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException { Certificate certificate = underlyingCertificateDirectory.tryInsertWithSpecialName(specialName, data, merge); if (certificate != null) { diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java index 9c80965..7952529 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java @@ -11,7 +11,7 @@ import java.util.Iterator; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.Certificate; -import pgp.certificate_store.MergeCallback; +import pgp.certificate_store.CertificateMerger; public interface SharedPGPCertificateDirectory { @@ -29,16 +29,16 @@ public interface SharedPGPCertificateDirectory { Certificate getBySpecialNameIfChanged(String specialName, String tag) throws IOException, BadNameException, BadDataException; - Certificate insert(InputStream data, MergeCallback merge) + Certificate insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException, InterruptedException; - Certificate tryInsert(InputStream data, MergeCallback merge) + Certificate tryInsert(InputStream data, CertificateMerger merge) throws IOException, BadDataException; - Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException, InterruptedException; - Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException; Iterator items(); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java index 7b0a917..096f1b5 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java @@ -21,7 +21,7 @@ import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; import pgp.certificate_store.Certificate; import pgp.certificate_store.CertificateReaderBackend; -import pgp.certificate_store.MergeCallback; +import pgp.certificate_store.CertificateMerger; public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory { @@ -131,7 +131,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate insert(InputStream data, MergeCallback merge) + public Certificate insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException, InterruptedException { writeLock.lockDirectory(); @@ -142,7 +142,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate tryInsert(InputStream data, MergeCallback merge) + public Certificate tryInsert(InputStream data, CertificateMerger merge) throws IOException, BadDataException { if (!writeLock.tryLockDirectory()) { return null; @@ -154,7 +154,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } - private Certificate _insert(InputStream data, MergeCallback merge) + private Certificate _insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException { Certificate newCertificate = certificateReaderBackend.readCertificate(data); Certificate existingCertificate; @@ -196,7 +196,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadNameException, BadDataException, InterruptedException { writeLock.lockDirectory(); @@ -207,7 +207,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + public Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadNameException, BadDataException { if (!writeLock.tryLockDirectory()) { return null; @@ -219,7 +219,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } - private Certificate _insertSpecial(String specialName, InputStream data, MergeCallback merge) + private Certificate _insertSpecial(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadNameException, BadDataException { Certificate newCertificate = certificateReaderBackend.readCertificate(data); Certificate existingCertificate = getBySpecialName(specialName); diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java index 006970c..f638127 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java @@ -54,12 +54,12 @@ public interface CertificateDirectory { /** * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link MergeCallback} will be + * If an instance of the certificate is already present in the store, the given {@link CertificateMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificate(InputStream, MergeCallback)} instead. + * consider to use {@link #tryInsertCertificate(InputStream, CertificateMerger)} instead. * * @param data input stream containing the new certificate instance * @param merge callback for merging with an existing certificate instance @@ -69,12 +69,12 @@ public interface CertificateDirectory { * @throws InterruptedException in case the inserting thread gets interrupted * @throws BadDataException if the data stream does not contain valid OpenPGP data */ - Certificate insertCertificate(InputStream data, MergeCallback merge) + Certificate insertCertificate(InputStream data, CertificateMerger merge) throws IOException, InterruptedException, BadDataException; /** * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link MergeCallback} will be + * If an instance of the certificate is already present in the store, the given {@link CertificateMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * @@ -90,19 +90,19 @@ public interface CertificateDirectory { * @throws IOException in case of an IO-error * @throws BadDataException if the data stream does not contain valid OpenPGP data */ - Certificate tryInsertCertificate(InputStream data, MergeCallback merge) + Certificate tryInsertCertificate(InputStream data, CertificateMerger merge) throws IOException, BadDataException; /** * Insert a certificate into the store. * The certificate will be stored under the given special name instead of its fingerprint. * - * If an instance of the certificate is already present under the special name in the store, the given {@link MergeCallback} will be + * If an instance of the certificate is already present under the special name in the store, the given {@link CertificateMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, MergeCallback)} instead. + * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, CertificateMerger)} instead. * * @param specialName special name of the certificate * @param data input stream containing the new certificate instance @@ -114,14 +114,14 @@ public interface CertificateDirectory { * @throws BadDataException if the certificate file does not contain valid OpenPGP data * @throws BadNameException if the special name is unknown */ - Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) + Certificate insertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, InterruptedException, BadDataException, BadNameException; /** * Insert a certificate into the store. * The certificate will be stored under the given special name instead of its fingerprint. * - * If an instance of the certificate is already present under the special name in the store, the given {@link MergeCallback} will be + * If an instance of the certificate is already present under the special name in the store, the given {@link CertificateMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * @@ -139,7 +139,7 @@ public interface CertificateDirectory { * @throws BadDataException if the data stream does not contain valid OpenPGP data * @throws BadNameException if the special name is not known */ - Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) + Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException; /** diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java similarity index 95% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java index 9c9f162..9659003 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/MergeCallback.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java @@ -9,7 +9,7 @@ import java.io.IOException; /** * Merge a given certificate (update) with an existing certificate. */ -public interface MergeCallback { +public interface CertificateMerger { /** * Merge the given certificate data with the existing certificate and return the result. From bc20b958399bec2b05637bc8c9fedddccffc9267 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 18:48:49 +0200 Subject: [PATCH 13/63] Make trust-root constant in SpecialNames --- pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java index 682d834..622b2de 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java @@ -9,11 +9,14 @@ import java.util.Map; public class SpecialNames { + public static final String TRUST_ROOT = "trust-root"; + + // Map to allow for potentially upper- and lowercase variants of the same special name private static final Map SPECIAL_NAMES = new HashMap<>(); static { - SPECIAL_NAMES.put("TRUST-ROOT", "trust-root"); // TODO: Remove - SPECIAL_NAMES.put("trust-root", "trust-root"); + SPECIAL_NAMES.put("TRUST-ROOT", TRUST_ROOT); // TODO: Remove + SPECIAL_NAMES.put(TRUST_ROOT, TRUST_ROOT); } public static String lookupSpecialName(String specialName) { From 0846528072edda0dc2f24df7f9e311f364561093 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 19:38:01 +0200 Subject: [PATCH 14/63] Prepare for integration of key storing into the cert store --- .../main/java/pgp/certificate_store/Key.java | 32 ++++++++ .../java/pgp/certificate_store/KeyMerger.java | 25 ++++++ .../certificate_store/KeyReaderBackend.java | 24 ++++++ .../pgp/certificate_store/TrustRootStore.java | 82 +++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java new file mode 100644 index 0000000..00cc5c6 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import java.io.IOException; +import java.io.InputStream; + +/** + * OpenPGP key (secret key). + */ +public abstract class Key { + + /** + * Return the certificate part of this OpenPGP key. + * + * @return OpenPGP certificate + */ + public abstract Certificate getCertificate(); + + /** + * Return an {@link InputStream} of the binary representation of the secret key. + * + * @return input stream + * @throws IOException in case of an IO error + */ + public abstract InputStream getInputStream() throws IOException; + + public abstract String getTag() throws IOException; + +} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java new file mode 100644 index 0000000..3a7a7c5 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import java.io.IOException; + +/** + * Merge a given {@link Key} (update) with an existing {@link Key}. + */ +public interface KeyMerger { + + /** + * Merge the given key data with the existing {@link Key} and return the result. + * If no existing {@link Key} is found (i.e. if existing is null), this method returns the unmodified data. + * + * @param data key + * @param existing optional already existing copy of the key + * @return merged key + * + * @throws IOException in case of an IO error + */ + Key merge(Key data, Key existing) throws IOException; +} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java new file mode 100644 index 0000000..f774150 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import pgp.certificate_store.exception.BadDataException; + +import java.io.IOException; +import java.io.InputStream; + +public interface KeyReaderBackend { + + /** + * Read a {@link Key} from the given {@link InputStream}. + * + * @param data input stream containing the binary representation of the key. + * @return key object + * + * @throws IOException in case of an IO error + * @throws BadDataException in case that the data stream does not contain a valid OpenPGP key + */ + Key readKey(InputStream data) throws IOException, BadDataException; +} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java new file mode 100644 index 0000000..4e9f118 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import pgp.certificate_store.exception.BadDataException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Secret key store definition for trust-root keys. + */ +public interface TrustRootStore { + + /** + * Return the current trust-root key. + * If no trust-root key is present, return null. + * + * @return trust-root key + * + * @throws IOException in case of an IO error + * @throws BadDataException if the key datum contains invalid data + */ + Key getTrustRoot() + throws IOException, BadDataException; + + /** + * Return the current trust-root key, but only iff it changed since the last invocation of this method. + * To compare the key against its last returned result, the given tag is used. + * If the tag of the currently found key matches the given argument, return null. + * + * @param tag tag to compare freshness + * @return changed key or null + * + * @throws IOException in case of an IO error + * @throws BadDataException if the key datum contains invalid data + */ + Key getTrustRootIfChanged(String tag) + throws IOException, BadDataException; + + /** + * Insert the given trust-root key into the store. + * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * the two instances into one {@link Key}. The result will be stored in the store and returned. + * + * This method will not block. Instead, if the store is already write-locked, this method will simply return null + * without writing anything. + * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock + * and return the written key. + * + * @param data input stream containing the new trust-root key + * @param keyMerger callback for merging with an existing key instance + * @return merged key + * + * @throws IOException in case of an IO error + * @throws InterruptedException in case the inserting thread gets interrupted + * @throws BadDataException if the data stream does not contain a valid OpenPGP key + */ + Key insertTrustRoot(InputStream data, KeyMerger keyMerger) + throws IOException, InterruptedException, BadDataException; + + /** + * Insert the given trust-root key into the store. + * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * the two instances into one {@link Key}. The result will be stored in the store and returned. + * + * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, + * consider using {@link #tryInsertTrustRoot(InputStream, KeyMerger)} instead. + * + * @param data input stream containing the new trust-root key + * @param keyMerger callback for merging with an existing key instance + * @return merged key + * + * @throws IOException in case of an IO error + * @throws InterruptedException in case the inserting thread gets interrupted + * @throws BadDataException if the data stream does not contain a valid OpenPGP key + */ + Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) + throws IOException, BadDataException; +} From 1b63a4ac9a9fad98bc04b976175dea9aaa9b938a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 19:40:26 +0200 Subject: [PATCH 15/63] Throw BadDataException when reading certificate --- .../java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java | 4 ++-- .../java/pgp/certificate_store/CertificateReaderBackend.java | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java index 096f1b5..c46d1f6 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java @@ -97,7 +97,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi @Override public Certificate getBySpecialName(String specialName) - throws IOException, BadNameException { + throws IOException, BadNameException, BadDataException { File certFile = resolver.getCertFileBySpecialName(specialName); if (!certFile.exists()) { return null; @@ -122,7 +122,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi @Override public Certificate getBySpecialNameIfChanged(String specialName, String tag) - throws IOException, BadNameException { + throws IOException, BadNameException, BadDataException { Certificate certificate = getBySpecialName(specialName); if (certificate.getTag().equals(tag)) { return null; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java index c16b111..b3060f5 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java @@ -4,6 +4,8 @@ package pgp.certificate_store; +import pgp.certificate_store.exception.BadDataException; + import java.io.IOException; import java.io.InputStream; @@ -20,7 +22,8 @@ public interface CertificateReaderBackend { * @return certificate object * * @throws IOException in case of an IO error + * @throws BadDataException in case that the input stream does not contain OpenPGP certificate data */ - Certificate readCertificate(InputStream inputStream) throws IOException; + Certificate readCertificate(InputStream inputStream) throws IOException, BadDataException; } From 304d6c29e400a5898878df0045411c213a85170b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 19:41:02 +0200 Subject: [PATCH 16/63] Throw detailled error message when bad data is encountered --- .../src/main/java/pgp/cert_d/FilenameResolver.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java index 81ffc86..d27fce9 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java @@ -51,9 +51,10 @@ public class FilenameResolver { * * @throws BadNameException in case the given special name is not known */ - public File getCertFileBySpecialName(String specialName) throws BadNameException { + public File getCertFileBySpecialName(String specialName) + throws BadNameException { if (!isSpecialName(specialName)) { - throw new BadNameException(); + throw new BadNameException(String.format("%s is not a known special name", specialName)); } return new File(getBaseDirectory(), specialName); From 17d2f45e83b74ccc330680635eba735a38814490 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 19:42:02 +0200 Subject: [PATCH 17/63] Implement storing of trust-root keys --- .../main/java/pgp/cert_d/BackendProvider.java | 3 + ...gSharedPGPCertificateDirectoryWrapper.java | 75 +++++++++++++-- .../java/pgp/cert_d/FilenameResolver.java | 18 ++++ .../cert_d/SharedPGPCertificateDirectory.java | 14 +++ .../SharedPGPCertificateDirectoryImpl.java | 96 +++++++++++++++++-- .../pgp/certificate_store/Certificate.java | 3 + .../certificate_store/CertificateStore.java | 2 +- 7 files changed, 192 insertions(+), 19 deletions(-) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java index be0d710..66df316 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java @@ -6,11 +6,14 @@ package pgp.cert_d; import pgp.certificate_store.CertificateReaderBackend; import pgp.certificate_store.CertificateMerger; +import pgp.certificate_store.KeyReaderBackend; public abstract class BackendProvider { public abstract CertificateReaderBackend provideCertificateReaderBackend(); + public abstract KeyReaderBackend provideKeyReaderBackend(); + public abstract CertificateMerger provideDefaultMergeCallback(); } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java index 332ebd6..d8bb635 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java @@ -4,6 +4,8 @@ package pgp.cert_d; +import pgp.certificate_store.Key; +import pgp.certificate_store.KeyMerger; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.Certificate; @@ -21,8 +23,10 @@ import java.util.Map; public class CachingSharedPGPCertificateDirectoryWrapper implements SharedPGPCertificateDirectory { - private static final Map tagMap = new HashMap<>(); + private static final Map certTagMap = new HashMap<>(); + private static final Map keyTagMap = new HashMap<>(); private static final Map certificateMap = new HashMap<>(); + private static final Map keyMap = new HashMap<>(); private final SharedPGPCertificateDirectory underlyingCertificateDirectory; public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) { @@ -38,12 +42,26 @@ public class CachingSharedPGPCertificateDirectoryWrapper private void remember(String identifier, Certificate certificate) { certificateMap.put(identifier, certificate); try { - tagMap.put(identifier, certificate.getTag()); + certTagMap.put(identifier, certificate.getTag()); } catch (IOException e) { - tagMap.put(identifier, null); + certTagMap.put(identifier, null); } } + /** + * Store the given key under the given identifier into the cache. + * + * @param identifier fingerprint or special name + * @param key key + */ + private void remember(String identifier, Key key) { + keyMap.put(identifier, key); + try { + keyTagMap.put(identifier, key.getTag()); + } catch (IOException e) { + keyTagMap.put(identifier, null); + } + } /** * Returns true, if the cached tag differs from the provided tag. * @@ -51,8 +69,13 @@ public class CachingSharedPGPCertificateDirectoryWrapper * @param tag tag * @return true if cached tag differs, false otherwise */ - private boolean tagChanged(String identifier, String tag) { - String tack = tagMap.get(identifier); + private boolean certTagChanged(String identifier, String tag) { + String tack = certTagMap.get(identifier); + return !tagEquals(tag, tack); + } + + private boolean keyTagChanged(String identifier, String tag) { + String tack = keyTagMap.get(identifier); return !tagEquals(tag, tack); } @@ -72,7 +95,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper */ public void invalidate() { certificateMap.clear(); - tagMap.clear(); + certTagMap.clear(); } @Override @@ -111,7 +134,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper @Override public Certificate getByFingerprintIfChanged(String fingerprint, String tag) throws IOException, BadNameException, BadDataException { - if (tagChanged(fingerprint, tag)) { + if (certTagChanged(fingerprint, tag)) { return getByFingerprint(fingerprint); } return null; @@ -120,12 +143,32 @@ public class CachingSharedPGPCertificateDirectoryWrapper @Override public Certificate getBySpecialNameIfChanged(String specialName, String tag) throws IOException, BadNameException, BadDataException { - if (tagChanged(specialName, tag)) { + if (certTagChanged(specialName, tag)) { return getBySpecialName(specialName); } return null; } + @Override + public Key getTrustRoot() throws IOException, BadDataException { + Key key = keyMap.get(SpecialNames.TRUST_ROOT); + if (key == null) { + key = underlyingCertificateDirectory.getTrustRoot(); + if (key != null) { + remember(SpecialNames.TRUST_ROOT, key); + } + } + return key; + } + + @Override + public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException { + if (keyTagChanged(SpecialNames.TRUST_ROOT, tag)) { + return getTrustRoot(); + } + return null; + } + @Override public Certificate insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException, InterruptedException { @@ -144,6 +187,22 @@ public class CachingSharedPGPCertificateDirectoryWrapper return certificate; } + @Override + public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException { + Key key = underlyingCertificateDirectory.insertTrustRoot(data, merge); + remember(SpecialNames.TRUST_ROOT, key); + return key; + } + + @Override + public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException { + Key key = underlyingCertificateDirectory.tryInsertTrustRoot(data, merge); + if (key != null) { + remember(SpecialNames.TRUST_ROOT, key); + } + return key; + } + @Override public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadDataException, BadNameException, InterruptedException { diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java index d27fce9..0bbdbc1 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java @@ -60,6 +60,24 @@ public class FilenameResolver { return new File(getBaseDirectory(), specialName); } + /** + * Calculate the file location for the key addressed using the given special name. + * For known special names, see {@link SpecialNames}. + * + * @param specialName special name (e.g. "trust-root") + * @return absolute key file location + * + * @throws BadNameException in case the given special name is not known + */ + public File getKeyFileBySpecialName(String specialName) + throws BadNameException { + if (!isSpecialName(specialName)) { + throw new BadNameException(String.format("%s is not a known special name", specialName)); + } + + return new File(getBaseDirectory(), specialName + ".key"); + } + private boolean isFingerprint(String fingerprint) { return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java index 7952529..b6b83f4 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java @@ -8,6 +8,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Iterator; +import pgp.certificate_store.Key; +import pgp.certificate_store.KeyMerger; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.Certificate; @@ -23,6 +25,18 @@ public interface SharedPGPCertificateDirectory { Certificate getBySpecialName(String specialName) throws IOException, BadNameException, BadDataException; + Key getTrustRoot() + throws IOException, BadDataException; + + Key getTrustRootIfChanged(String tag) + throws IOException, BadDataException; + + Key insertTrustRoot(InputStream data, KeyMerger merge) + throws IOException, BadDataException, InterruptedException; + + Key tryInsertTrustRoot(InputStream data, KeyMerger merge) + throws IOException, BadDataException; + Certificate getByFingerprintIfChanged(String fingerprint, String tag) throws IOException, BadNameException, BadDataException; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java index c46d1f6..3e8ebe9 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java @@ -16,6 +16,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import pgp.certificate_store.Key; +import pgp.certificate_store.KeyMerger; +import pgp.certificate_store.KeyReaderBackend; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; @@ -28,33 +31,41 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi private final FilenameResolver resolver; private final LockingMechanism writeLock; private final CertificateReaderBackend certificateReaderBackend; + private final KeyReaderBackend keyReaderBackend; public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider) throws NotAStoreException { - this(backendProvider.provideCertificateReaderBackend()); + this(backendProvider.provideCertificateReaderBackend(), backendProvider.provideKeyReaderBackend()); } - public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend) + public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend, + KeyReaderBackend keyReaderBackend) throws NotAStoreException { this( BaseDirectoryProvider.getDefaultBaseDir(), - certificateReaderBackend); + certificateReaderBackend, + keyReaderBackend); } - public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend) + public SharedPGPCertificateDirectoryImpl(File baseDirectory, + CertificateReaderBackend certificateReaderBackend, + KeyReaderBackend keyReaderBackend) throws NotAStoreException { this( certificateReaderBackend, + keyReaderBackend, new FilenameResolver(baseDirectory), FileLockingMechanism.defaultDirectoryFileLock(baseDirectory)); } public SharedPGPCertificateDirectoryImpl( CertificateReaderBackend certificateReaderBackend, + KeyReaderBackend keyReaderBackend, FilenameResolver filenameResolver, LockingMechanism writeLock) throws NotAStoreException { this.certificateReaderBackend = certificateReaderBackend; + this.keyReaderBackend = keyReaderBackend; this.resolver = filenameResolver; this.writeLock = writeLock; @@ -130,6 +141,36 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } + @Override + public Key getTrustRoot() throws IOException, BadDataException { + File keyFile; + try { + keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + throw new RuntimeException("trust-root MUST be a known special name", e); + } + + if (!keyFile.exists()) { + return null; + } + FileInputStream fileIn = new FileInputStream(keyFile); + BufferedInputStream bufferedInputStream = new BufferedInputStream(fileIn); + Key key = keyReaderBackend.readKey(bufferedInputStream); + + return key; + } + + @Override + public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException { + // TODO: The tag is likely intended for performance improvements, + // so really we should look it up somewhere without the need to parse the whole key. + Key key = getTrustRoot(); + if (key.getTag().equals(tag)) { + return null; + } + return key; + } + @Override public Certificate insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException, InterruptedException { @@ -170,28 +211,63 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi newCertificate = merge.merge(newCertificate, existingCertificate); } - writeCertificate(newCertificate, certFile); + writeToFile(newCertificate.getInputStream(), certFile); return newCertificate; } - private void writeCertificate(Certificate certificate, File certFile) + private Key _insertTrustRoot(InputStream data, KeyMerger merge) + throws IOException, BadDataException { + Key newKey = keyReaderBackend.readKey(data); + Key existingKey; + File keyFile; + try { + existingKey = getTrustRoot(); + keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + throw new RuntimeException(String.format("trust-root MUST be known special name.", e)); + } + + if (existingKey != null && !existingKey.getTag().equals(newKey.getTag())) { + newKey = merge.merge(newKey, existingKey); + } + + writeToFile(newKey.getInputStream(), keyFile); + + return newKey; + } + + @Override + public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException { + writeLock.lockDirectory(); + + Key key = _insertTrustRoot(data, merge); + + writeLock.releaseDirectory(); + return key; + } + + @Override + public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException { + return null; + } + + private void writeToFile(InputStream inputStream, File certFile) throws IOException { certFile.getParentFile().mkdirs(); if (!certFile.exists() && !certFile.createNewFile()) { throw new IOException("Could not create cert file " + certFile.getAbsolutePath()); } - InputStream certIn = certificate.getInputStream(); FileOutputStream fileOut = new FileOutputStream(certFile); byte[] buffer = new byte[4096]; int read; - while ((read = certIn.read(buffer)) != -1) { + while ((read = inputStream.read(buffer)) != -1) { fileOut.write(buffer, 0, read); } - certIn.close(); + inputStream.close(); fileOut.close(); } @@ -229,7 +305,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi newCertificate = merge.merge(newCertificate, existingCertificate); } - writeCertificate(newCertificate, certFile); + writeToFile(newCertificate.getInputStream(), certFile); return newCertificate; } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java index ea3f363..530e51c 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java @@ -8,6 +8,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.Set; +/** + * OpenPGP certificate (public key). + */ public abstract class Certificate { /** * Return the fingerprint of the certificate as 40 lowercase hex characters. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java index a8325ee..594e3f6 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java @@ -4,6 +4,6 @@ package pgp.certificate_store; -public interface CertificateStore extends CertificateDirectory, SubkeyLookup { +public interface CertificateStore extends CertificateDirectory, SubkeyLookup, TrustRootStore { } From 533d0be2d09e1a0a32a275a0b60bc1ef7cdcc76b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 19:45:01 +0200 Subject: [PATCH 18/63] Fix javadoc issue --- .../src/main/java/pgp/certificate_store/TrustRootStore.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java index 4e9f118..fadc862 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java @@ -74,7 +74,6 @@ public interface TrustRootStore { * @return merged key * * @throws IOException in case of an IO error - * @throws InterruptedException in case the inserting thread gets interrupted * @throws BadDataException if the data stream does not contain a valid OpenPGP key */ Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) From 2222d95702d595687d72c01305e0ef6624011c4c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Jul 2022 20:10:55 +0200 Subject: [PATCH 19/63] Move TrustRootStore methods into CertificateDirectory interface --- .../CertificateDirectory.java | 65 +++++++++++++++ .../certificate_store/CertificateStore.java | 2 +- .../pgp/certificate_store/TrustRootStore.java | 81 ------------------- 3 files changed, 66 insertions(+), 82 deletions(-) delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java index f638127..a2a66df 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java @@ -157,4 +157,69 @@ public interface CertificateDirectory { * @return fingerprints */ Iterator getFingerprints(); + + /** + * Return the current trust-root key. + * If no trust-root key is present, return null. + * + * @return trust-root key + * + * @throws IOException in case of an IO error + * @throws BadDataException if the key datum contains invalid data + */ + Key getTrustRoot() + throws IOException, BadDataException; + + /** + * Return the current trust-root key, but only iff it changed since the last invocation of this method. + * To compare the key against its last returned result, the given tag is used. + * If the tag of the currently found key matches the given argument, return null. + * + * @param tag tag to compare freshness + * @return changed key or null + * + * @throws IOException in case of an IO error + * @throws BadDataException if the key datum contains invalid data + */ + Key getTrustRootIfChanged(String tag) + throws IOException, BadDataException; + + /** + * Insert the given trust-root key into the store. + * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * the two instances into one {@link Key}. The result will be stored in the store and returned. + * + * This method will not block. Instead, if the store is already write-locked, this method will simply return null + * without writing anything. + * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock + * and return the written key. + * + * @param data input stream containing the new trust-root key + * @param keyMerger callback for merging with an existing key instance + * @return merged key + * + * @throws IOException in case of an IO error + * @throws InterruptedException in case the inserting thread gets interrupted + * @throws BadDataException if the data stream does not contain a valid OpenPGP key + */ + Key insertTrustRoot(InputStream data, KeyMerger keyMerger) + throws IOException, InterruptedException, BadDataException; + + /** + * Insert the given trust-root key into the store. + * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * the two instances into one {@link Key}. The result will be stored in the store and returned. + * + * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, + * consider using {@link #tryInsertTrustRoot(InputStream, KeyMerger)} instead. + * + * @param data input stream containing the new trust-root key + * @param keyMerger callback for merging with an existing key instance + * @return merged key + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream does not contain a valid OpenPGP key + */ + Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) + throws IOException, BadDataException; } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java index 594e3f6..a8325ee 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java @@ -4,6 +4,6 @@ package pgp.certificate_store; -public interface CertificateStore extends CertificateDirectory, SubkeyLookup, TrustRootStore { +public interface CertificateStore extends CertificateDirectory, SubkeyLookup { } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java deleted file mode 100644 index fadc862..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/TrustRootStore.java +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import pgp.certificate_store.exception.BadDataException; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Secret key store definition for trust-root keys. - */ -public interface TrustRootStore { - - /** - * Return the current trust-root key. - * If no trust-root key is present, return null. - * - * @return trust-root key - * - * @throws IOException in case of an IO error - * @throws BadDataException if the key datum contains invalid data - */ - Key getTrustRoot() - throws IOException, BadDataException; - - /** - * Return the current trust-root key, but only iff it changed since the last invocation of this method. - * To compare the key against its last returned result, the given tag is used. - * If the tag of the currently found key matches the given argument, return null. - * - * @param tag tag to compare freshness - * @return changed key or null - * - * @throws IOException in case of an IO error - * @throws BadDataException if the key datum contains invalid data - */ - Key getTrustRootIfChanged(String tag) - throws IOException, BadDataException; - - /** - * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge - * the two instances into one {@link Key}. The result will be stored in the store and returned. - * - * This method will not block. Instead, if the store is already write-locked, this method will simply return null - * without writing anything. - * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock - * and return the written key. - * - * @param data input stream containing the new trust-root key - * @param keyMerger callback for merging with an existing key instance - * @return merged key - * - * @throws IOException in case of an IO error - * @throws InterruptedException in case the inserting thread gets interrupted - * @throws BadDataException if the data stream does not contain a valid OpenPGP key - */ - Key insertTrustRoot(InputStream data, KeyMerger keyMerger) - throws IOException, InterruptedException, BadDataException; - - /** - * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge - * the two instances into one {@link Key}. The result will be stored in the store and returned. - * - * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider using {@link #tryInsertTrustRoot(InputStream, KeyMerger)} instead. - * - * @param data input stream containing the new trust-root key - * @param keyMerger callback for merging with an existing key instance - * @return merged key - * - * @throws IOException in case of an IO error - * @throws BadDataException if the data stream does not contain a valid OpenPGP key - */ - Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) - throws IOException, BadDataException; -} From 21fee6253e39c124523685632fee63b3343f24c7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:01:08 +0200 Subject: [PATCH 20/63] Add woodpecker CI --- .reuse/dep5 | 4 ++++ .woodpecker/.build.yml | 12 ++++++++++++ .woodpecker/.reuse.yml | 7 +++++++ 3 files changed, 23 insertions(+) create mode 100644 .woodpecker/.build.yml create mode 100644 .woodpecker/.reuse.yml diff --git a/.reuse/dep5 b/.reuse/dep5 index 2853568..3bc4763 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -14,3 +14,7 @@ Files: gradle* Copyright: 2015 the original author or authors. License: Apache-2.0 +# Woodpecker build files +Files: .woodpecker/* +Copyright: 2022 the original author or authors. +License: Apache-2.0 diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml new file mode 100644 index 0000000..f504b44 --- /dev/null +++ b/.woodpecker/.build.yml @@ -0,0 +1,12 @@ +pipeline: + run: + image: gradle:7.5-jdk8 + commands: + - git checkout $CI_COMMIT_BRANCH + # Code works + - gradle test + # Code is clean + - gradle check javadocAll + # Code has coverage + - gradle jacocoRootReport coveralls + secrets: [COVERALLS_REPO_TOKEN] diff --git a/.woodpecker/.reuse.yml b/.woodpecker/.reuse.yml new file mode 100644 index 0000000..58f17e6 --- /dev/null +++ b/.woodpecker/.reuse.yml @@ -0,0 +1,7 @@ +# Code is licensed properly +# See https://reuse.software/ +pipeline: + reuse: + image: fsfe/reuse:latest + commands: + - reuse lint \ No newline at end of file From 0c416bd16641877ceeefe70b2b8c22ce9ec34b94 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:07:17 +0200 Subject: [PATCH 21/63] Add badges for CI and Coveralls --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 588d836..21ce33f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0 --> # Shared PGP Certificate Directory for Java +[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/cert-d-java/status.svg?branch=main)](https://ci.codeberg.org/PGPainless/cert-d-java) +[![Coverage Status](https://coveralls.io/repos/github/pgpainless/cert-d-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/cert-d-java?branch=main) This repository contains a number of modules defining OpenPGP certificate storage for Java and Android applications. From 2b5da18fc63b071b92c1ef3145d594c175195a38 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:10:27 +0200 Subject: [PATCH 22/63] Add reuse badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21ce33f..fb3cb90 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # Shared PGP Certificate Directory for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/cert-d-java/status.svg?branch=main)](https://ci.codeberg.org/PGPainless/cert-d-java) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/cert-d-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/cert-d-java?branch=main) +[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/cert-d-java)](https://api.reuse.software/info/github.com/pgpainless/cert-d-java) This repository contains a number of modules defining OpenPGP certificate storage for Java and Android applications. From 942b287bebaf8ab7b18e561871d711bf9ff6c90e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 8 Aug 2022 13:50:59 +0200 Subject: [PATCH 23/63] Make Key and Certificate extend KeyMaterial, get rid of CertificateReader --- .../main/java/pgp/cert_d/BackendProvider.java | 3 -- .../SharedPGPCertificateDirectoryImpl.java | 49 ++++++++++++------- .../pgp/certificate_store/Certificate.java | 9 +--- .../CertificateReaderBackend.java | 29 ----------- .../main/java/pgp/certificate_store/Key.java | 2 +- .../pgp/certificate_store/KeyMaterial.java | 17 +++++++ .../certificate_store/KeyReaderBackend.java | 8 +-- 7 files changed, 55 insertions(+), 62 deletions(-) delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java index 66df316..ff93dd2 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java @@ -4,14 +4,11 @@ package pgp.cert_d; -import pgp.certificate_store.CertificateReaderBackend; import pgp.certificate_store.CertificateMerger; import pgp.certificate_store.KeyReaderBackend; public abstract class BackendProvider { - public abstract CertificateReaderBackend provideCertificateReaderBackend(); - public abstract KeyReaderBackend provideKeyReaderBackend(); public abstract CertificateMerger provideDefaultMergeCallback(); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java index 3e8ebe9..b44bdcf 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java @@ -17,54 +17,47 @@ import java.util.Iterator; import java.util.List; import pgp.certificate_store.Key; +import pgp.certificate_store.KeyMaterial; import pgp.certificate_store.KeyMerger; import pgp.certificate_store.KeyReaderBackend; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; import pgp.certificate_store.Certificate; -import pgp.certificate_store.CertificateReaderBackend; import pgp.certificate_store.CertificateMerger; public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory { private final FilenameResolver resolver; private final LockingMechanism writeLock; - private final CertificateReaderBackend certificateReaderBackend; private final KeyReaderBackend keyReaderBackend; public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider) throws NotAStoreException { - this(backendProvider.provideCertificateReaderBackend(), backendProvider.provideKeyReaderBackend()); + this(backendProvider.provideKeyReaderBackend()); } - public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend, - KeyReaderBackend keyReaderBackend) + public SharedPGPCertificateDirectoryImpl(KeyReaderBackend keyReaderBackend) throws NotAStoreException { this( BaseDirectoryProvider.getDefaultBaseDir(), - certificateReaderBackend, keyReaderBackend); } public SharedPGPCertificateDirectoryImpl(File baseDirectory, - CertificateReaderBackend certificateReaderBackend, KeyReaderBackend keyReaderBackend) throws NotAStoreException { this( - certificateReaderBackend, keyReaderBackend, new FilenameResolver(baseDirectory), FileLockingMechanism.defaultDirectoryFileLock(baseDirectory)); } public SharedPGPCertificateDirectoryImpl( - CertificateReaderBackend certificateReaderBackend, KeyReaderBackend keyReaderBackend, FilenameResolver filenameResolver, LockingMechanism writeLock) throws NotAStoreException { - this.certificateReaderBackend = certificateReaderBackend; this.keyReaderBackend = keyReaderBackend; this.resolver = filenameResolver; this.writeLock = writeLock; @@ -96,8 +89,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = certificateReaderBackend.readCertificate(bufferedIn); + Certificate certificate = readCertificate(bufferedIn); if (!certificate.getFingerprint().equals(fingerprint)) { // TODO: Figure out more suitable exception throw new BadDataException(); @@ -106,6 +99,28 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } + private Certificate readCertificate(InputStream inputStream) throws BadDataException, IOException { + KeyMaterial record = keyReaderBackend.read(inputStream); + Certificate certificate = null; + if (record instanceof Certificate) { + certificate = (Certificate) record; + } else if (record instanceof Key) { + Key key = (Key) record; + certificate = key.getCertificate(); + } else { + throw new BadDataException(); + } + return certificate; + } + + private Key readKey(InputStream inputStream) throws BadDataException, IOException { + KeyMaterial record = keyReaderBackend.read(inputStream); + if (record instanceof Key) { + return (Key) record; + } + throw new BadDataException(); + } + @Override public Certificate getBySpecialName(String specialName) throws IOException, BadNameException, BadDataException { @@ -116,7 +131,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = certificateReaderBackend.readCertificate(bufferedIn); + Certificate certificate = readCertificate(bufferedIn); return certificate; } @@ -155,7 +170,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } FileInputStream fileIn = new FileInputStream(keyFile); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileIn); - Key key = keyReaderBackend.readKey(bufferedInputStream); + Key key = readKey(bufferedInputStream); return key; } @@ -197,7 +212,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi private Certificate _insert(InputStream data, CertificateMerger merge) throws IOException, BadDataException { - Certificate newCertificate = certificateReaderBackend.readCertificate(data); + Certificate newCertificate = readCertificate(data); Certificate existingCertificate; File certFile; try { @@ -218,7 +233,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi private Key _insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException { - Key newKey = keyReaderBackend.readKey(data); + Key newKey = readKey(data); Key existingKey; File keyFile; try { @@ -297,7 +312,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi private Certificate _insertSpecial(String specialName, InputStream data, CertificateMerger merge) throws IOException, BadNameException, BadDataException { - Certificate newCertificate = certificateReaderBackend.readCertificate(data); + Certificate newCertificate = readCertificate(data); Certificate existingCertificate = getBySpecialName(specialName); File certFile = resolver.getCertFileBySpecialName(specialName); @@ -338,7 +353,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi @Override Certificate get() throws BadDataException { try { - Certificate certificate = certificateReaderBackend.readCertificate(new FileInputStream(certFile)); + Certificate certificate = readCertificate(new FileInputStream(certFile)); if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) { throw new BadDataException(); } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java index 530e51c..f6501e8 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java @@ -11,14 +11,7 @@ import java.util.Set; /** * OpenPGP certificate (public key). */ -public abstract class Certificate { - /** - * Return the fingerprint of the certificate as 40 lowercase hex characters. - * TODO: Allow OpenPGP V5 fingerprints - * - * @return fingerprint - */ - public abstract String getFingerprint(); +public abstract class Certificate implements KeyMaterial { /** * Return an {@link InputStream} of the binary representation of the certificate. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java deleted file mode 100644 index b3060f5..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import pgp.certificate_store.exception.BadDataException; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Interface definition for a class that can read {@link Certificate Certificates} from binary - * {@link InputStream InputStreams}. - */ -public interface CertificateReaderBackend { - - /** - * Read a {@link Certificate} from the given {@link InputStream}. - * - * @param inputStream input stream containing the binary representation of the certificate. - * @return certificate object - * - * @throws IOException in case of an IO error - * @throws BadDataException in case that the input stream does not contain OpenPGP certificate data - */ - Certificate readCertificate(InputStream inputStream) throws IOException, BadDataException; - -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java index 00cc5c6..6425468 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java @@ -10,7 +10,7 @@ import java.io.InputStream; /** * OpenPGP key (secret key). */ -public abstract class Key { +public abstract class Key implements KeyMaterial { /** * Return the certificate part of this OpenPGP key. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java new file mode 100644 index 0000000..d940a25 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +public interface KeyMaterial { + + /** + * Return the fingerprint of the certificate as 40 lowercase hex characters. + * TODO: Allow OpenPGP V5 fingerprints + * + * @return fingerprint + */ + String getFingerprint(); + +} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java index f774150..d99d8e2 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java @@ -12,13 +12,13 @@ import java.io.InputStream; public interface KeyReaderBackend { /** - * Read a {@link Key} from the given {@link InputStream}. + * Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}. * * @param data input stream containing the binary representation of the key. - * @return key object + * @return key or certificate object * * @throws IOException in case of an IO error - * @throws BadDataException in case that the data stream does not contain a valid OpenPGP key + * @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate */ - Key readKey(InputStream data) throws IOException, BadDataException; + KeyMaterial read(InputStream data) throws IOException, BadDataException; } From 60779b921e351b1621197d35c789fbf6b7c4c64b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 8 Aug 2022 15:07:45 +0200 Subject: [PATCH 24/63] Add suppression for Java 8 Map API --- .../src/main/java/pgp/cert_d/InMemorySubkeyLookup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java index 1cd7862..ec67ee0 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java @@ -30,6 +30,7 @@ public class InMemorySubkeyLookup implements SubkeyLookup { public void storeCertificateSubkeyIds(String certificate, List subkeyIds) { for (long subkeyId : subkeyIds) { Set certificates = subkeyMap.get(subkeyId); + // noinspection Java8MapApi if (certificates == null) { certificates = new HashSet<>(); subkeyMap.put(subkeyId, certificates); From 7c39781d15f6f360a58a2a7a5c4bb197d0f0ba3d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 9 Aug 2022 17:50:15 +0200 Subject: [PATCH 25/63] Rewrite of PGPCertificateDirectory using more flexible backend --- .../main/java/pgp/cert_d/BackendProvider.java | 16 - ...gSharedPGPCertificateDirectoryWrapper.java | 249 ----------- .../FileBasedCertificateDirectoryBackend.java | 405 ++++++++++++++++++ .../java/pgp/cert_d/FileLockingMechanism.java | 96 ----- .../java/pgp/cert_d/FilenameResolver.java | 89 ---- .../InMemoryCertificateDirectoryBackend.java | 116 +++++ .../java/pgp/cert_d/LockingMechanism.java | 37 -- .../pgp/cert_d/PGPCertificateDirectories.java | 33 ++ .../pgp/cert_d/PGPCertificateDirectory.java | 194 +++++++++ .../ReadOnlyPGPCertificateDirectory.java | 28 ++ .../cert_d/SharedPGPCertificateDirectory.java | 61 --- .../SharedPGPCertificateDirectoryImpl.java | 405 ------------------ .../WritingPGPCertificateDirectory.java | 39 ++ .../java/pgp/cert_d/FilenameResolverTest.java | 4 +- .../pgp/certificate_store/Certificate.java | 32 +- .../CertificateDirectory.java | 30 +- .../certificate_store/CertificateMerger.java | 27 -- .../main/java/pgp/certificate_store/Key.java | 16 +- .../pgp/certificate_store/KeyMaterial.java | 23 + .../certificate_store/KeyMaterialMerger.java | 25 ++ ...end.java => KeyMaterialReaderBackend.java} | 2 +- .../java/pgp/certificate_store/KeyMerger.java | 25 -- 22 files changed, 889 insertions(+), 1063 deletions(-) delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/FileLockingMechanism.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java delete mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java rename pgp-certificate-store/src/main/java/pgp/certificate_store/{KeyReaderBackend.java => KeyMaterialReaderBackend.java} (94%) delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java deleted file mode 100644 index ff93dd2..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BackendProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import pgp.certificate_store.CertificateMerger; -import pgp.certificate_store.KeyReaderBackend; - -public abstract class BackendProvider { - - public abstract KeyReaderBackend provideKeyReaderBackend(); - - public abstract CertificateMerger provideDefaultMergeCallback(); - -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java deleted file mode 100644 index d8bb635..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import pgp.certificate_store.Key; -import pgp.certificate_store.KeyMerger; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.CertificateMerger; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * Caching wrapper for {@link SharedPGPCertificateDirectory} implementations. - */ -public class CachingSharedPGPCertificateDirectoryWrapper - implements SharedPGPCertificateDirectory { - - private static final Map certTagMap = new HashMap<>(); - private static final Map keyTagMap = new HashMap<>(); - private static final Map certificateMap = new HashMap<>(); - private static final Map keyMap = new HashMap<>(); - private final SharedPGPCertificateDirectory underlyingCertificateDirectory; - - public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) { - this.underlyingCertificateDirectory = wrapped; - } - - /** - * Store the given certificate under the given identifier into the cache. - * - * @param identifier fingerprint or special name - * @param certificate certificate - */ - private void remember(String identifier, Certificate certificate) { - certificateMap.put(identifier, certificate); - try { - certTagMap.put(identifier, certificate.getTag()); - } catch (IOException e) { - certTagMap.put(identifier, null); - } - } - - /** - * Store the given key under the given identifier into the cache. - * - * @param identifier fingerprint or special name - * @param key key - */ - private void remember(String identifier, Key key) { - keyMap.put(identifier, key); - try { - keyTagMap.put(identifier, key.getTag()); - } catch (IOException e) { - keyTagMap.put(identifier, null); - } - } - /** - * Returns true, if the cached tag differs from the provided tag. - * - * @param identifier fingerprint or special name - * @param tag tag - * @return true if cached tag differs, false otherwise - */ - private boolean certTagChanged(String identifier, String tag) { - String tack = certTagMap.get(identifier); - return !tagEquals(tag, tack); - } - - private boolean keyTagChanged(String identifier, String tag) { - String tack = keyTagMap.get(identifier); - return !tagEquals(tag, tack); - } - - /** - * Return true, if tag and tack are equal, false otherwise. - * @param tag tag - * @param tack other tag - * @return true if equal - */ - private static boolean tagEquals(String tag, String tack) { - return (tag == null && tack == null) - || tag != null && tag.equals(tack); - } - - /** - * Clear the cache. - */ - public void invalidate() { - certificateMap.clear(); - certTagMap.clear(); - } - - @Override - public LockingMechanism getLock() { - return underlyingCertificateDirectory.getLock(); - } - - @Override - public Certificate getByFingerprint(String fingerprint) - throws IOException, BadNameException, BadDataException { - Certificate certificate = certificateMap.get(fingerprint); - if (certificate == null) { - certificate = underlyingCertificateDirectory.getByFingerprint(fingerprint); - if (certificate != null) { - remember(fingerprint, certificate); - } - } - - return certificate; - } - - @Override - public Certificate getBySpecialName(String specialName) - throws IOException, BadNameException, BadDataException { - Certificate certificate = certificateMap.get(specialName); - if (certificate == null) { - certificate = underlyingCertificateDirectory.getBySpecialName(specialName); - if (certificate != null) { - remember(specialName, certificate); - } - } - - return certificate; - } - - @Override - public Certificate getByFingerprintIfChanged(String fingerprint, String tag) - throws IOException, BadNameException, BadDataException { - if (certTagChanged(fingerprint, tag)) { - return getByFingerprint(fingerprint); - } - return null; - } - - @Override - public Certificate getBySpecialNameIfChanged(String specialName, String tag) - throws IOException, BadNameException, BadDataException { - if (certTagChanged(specialName, tag)) { - return getBySpecialName(specialName); - } - return null; - } - - @Override - public Key getTrustRoot() throws IOException, BadDataException { - Key key = keyMap.get(SpecialNames.TRUST_ROOT); - if (key == null) { - key = underlyingCertificateDirectory.getTrustRoot(); - if (key != null) { - remember(SpecialNames.TRUST_ROOT, key); - } - } - return key; - } - - @Override - public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException { - if (keyTagChanged(SpecialNames.TRUST_ROOT, tag)) { - return getTrustRoot(); - } - return null; - } - - @Override - public Certificate insert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException, InterruptedException { - Certificate certificate = underlyingCertificateDirectory.insert(data, merge); - remember(certificate.getFingerprint(), certificate); - return certificate; - } - - @Override - public Certificate tryInsert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException { - Certificate certificate = underlyingCertificateDirectory.tryInsert(data, merge); - if (certificate != null) { - remember(certificate.getFingerprint(), certificate); - } - return certificate; - } - - @Override - public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException { - Key key = underlyingCertificateDirectory.insertTrustRoot(data, merge); - remember(SpecialNames.TRUST_ROOT, key); - return key; - } - - @Override - public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException { - Key key = underlyingCertificateDirectory.tryInsertTrustRoot(data, merge); - if (key != null) { - remember(SpecialNames.TRUST_ROOT, key); - } - return key; - } - - @Override - public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadDataException, BadNameException, InterruptedException { - Certificate certificate = underlyingCertificateDirectory.insertWithSpecialName(specialName, data, merge); - remember(specialName, certificate); - return certificate; - } - - @Override - public Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadDataException, BadNameException { - Certificate certificate = underlyingCertificateDirectory.tryInsertWithSpecialName(specialName, data, merge); - if (certificate != null) { - remember(specialName, certificate); - } - return certificate; - } - - @Override - public Iterator items() { - - Iterator iterator = underlyingCertificateDirectory.items(); - - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Certificate next() { - Certificate certificate = iterator.next(); - remember(certificate.getFingerprint(), certificate); - return certificate; - } - }; - } - - @Override - public Iterator fingerprints() { - return underlyingCertificateDirectory.fingerprints(); - } - -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java new file mode 100644 index 0000000..8b112cb --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java @@ -0,0 +1,405 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.Certificate; +import pgp.certificate_store.KeyMaterial; +import pgp.certificate_store.KeyMaterialMerger; +import pgp.certificate_store.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; +import pgp.certificate_store.exception.NotAStoreException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirectory.Backend { + + private abstract static class Lazy { + abstract E get() throws BadDataException; + } + + private static class FileLockingMechanism implements PGPCertificateDirectory.LockingMechanism { + + private final File lockFile; + private RandomAccessFile randomAccessFile; + private FileLock fileLock; + + FileLockingMechanism(File lockFile) { + this.lockFile = lockFile; + } + + public static FileLockingMechanism defaultDirectoryFileLock(File baseDirectory) { + return new FileLockingMechanism(new File(baseDirectory, "writelock")); + } + + @Override + public synchronized void lockDirectory() throws IOException, InterruptedException { + if (randomAccessFile != null) { + // we own the lock already. Let's wait... + this.wait(); + } + + try { + randomAccessFile = new RandomAccessFile(lockFile, "rw"); + } catch (FileNotFoundException e) { + lockFile.createNewFile(); + randomAccessFile = new RandomAccessFile(lockFile, "rw"); + } + + fileLock = randomAccessFile.getChannel().lock(); + } + + @Override + public synchronized boolean tryLockDirectory() throws IOException { + if (randomAccessFile != null) { + // We already locked the directory for another write operation. + // We fail, since we have not yet released the lock from the other operation. + return false; + } + + try { + randomAccessFile = new RandomAccessFile(lockFile, "rw"); + } catch (FileNotFoundException e) { + lockFile.createNewFile(); + randomAccessFile = new RandomAccessFile(lockFile, "rw"); + } + + try { + fileLock = randomAccessFile.getChannel().tryLock(); + if (fileLock == null) { + // try-lock failed, file is locked by another process. + randomAccessFile.close(); + randomAccessFile = null; + return false; + } + } catch (OverlappingFileLockException e) { + // Some other object is holding the lock. + randomAccessFile.close(); + randomAccessFile = null; + return false; + } + return true; + } + + @Override + public boolean isLocked() { + return randomAccessFile != null; + } + + @Override + public synchronized void releaseDirectory() throws IOException { + // unlock file + if (fileLock != null) { + fileLock.release(); + fileLock = null; + } + // close file + if (randomAccessFile != null) { + randomAccessFile.close(); + randomAccessFile = null; + } + // delete file + if (lockFile.exists()) { + lockFile.delete(); + } + // notify waiters + this.notify(); + } + } + + private final File baseDirectory; + private final PGPCertificateDirectory.LockingMechanism lock; + private final FilenameResolver resolver; + private final KeyMaterialReaderBackend reader; + + public FileBasedCertificateDirectoryBackend(File baseDirectory, KeyMaterialReaderBackend reader) throws NotAStoreException { + this.baseDirectory = baseDirectory; + this.resolver = new FilenameResolver(baseDirectory); + + if (!baseDirectory.exists()) { + if (!baseDirectory.mkdirs()) { + throw new NotAStoreException("Cannot create base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "'"); + } + } else { + if (baseDirectory.isFile()) { + throw new NotAStoreException("Base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "' appears to be a file."); + } + } + this.lock = FileLockingMechanism.defaultDirectoryFileLock(baseDirectory); + this.reader = reader; + } + + @Override + public PGPCertificateDirectory.LockingMechanism getLock() { + return lock; + } + + @Override + public Certificate readByFingerprint(String fingerprint) throws BadNameException, IOException, BadDataException { + File certFile = resolver.getCertFileByFingerprint(fingerprint); + if (!certFile.exists()) { + return null; + } + + FileInputStream fileIn = new FileInputStream(certFile); + BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); + + Certificate certificate = reader.read(bufferedIn).asCertificate(); + if (!certificate.getFingerprint().equals(fingerprint)) { + // TODO: Figure out more suitable exception + throw new BadDataException(); + } + + return certificate; + } + + @Override + public KeyMaterial readBySpecialName(String specialName) throws BadNameException, IOException, BadDataException { + File certFile = resolver.getCertFileBySpecialName(specialName); + if (!certFile.exists()) { + return null; + } + + FileInputStream fileIn = new FileInputStream(certFile); + BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); + KeyMaterial keyMaterial = reader.read(bufferedIn); + + return keyMaterial; + } + + @Override + public Iterator readItems() { + return new Iterator() { + + private final List> certificateQueue = Collections.synchronizedList(new ArrayList<>()); + + // Constructor... wtf. + { + File[] subdirectories = baseDirectory.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory() && file.getName().matches("^[a-f0-9]{2}$"); + } + }); + + for (File subdirectory : subdirectories) { + File[] files = subdirectory.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isFile() && file.getName().matches("^[a-f0-9]{38}$"); + } + }); + + for (File certFile : files) { + certificateQueue.add(new Lazy() { + @Override + Certificate get() throws BadDataException { + try { + Certificate certificate = reader.read(new FileInputStream(certFile)).asCertificate(); + if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) { + throw new BadDataException(); + } + return certificate; + } catch (IOException e) { + throw new AssertionError("File got deleted."); + } + } + }); + } + } + } + + @Override + public boolean hasNext() { + return !certificateQueue.isEmpty(); + } + + @Override + public Certificate next() { + try { + return certificateQueue.remove(0).get(); + } catch (BadDataException e) { + throw new AssertionError("Could not retrieve item: " + e.getMessage()); + } + } + }; + } + + @Override + public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException { + KeyMaterial newCertificate = reader.read(data); + KeyMaterial existingCertificate; + File certFile; + try { + existingCertificate = readBySpecialName(SpecialNames.TRUST_ROOT); + certFile = resolver.getCertFileBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + throw new BadDataException(); + } + + if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + newCertificate = merge.merge(newCertificate, existingCertificate); + } + + writeToFile(newCertificate.getInputStream(), certFile); + + return newCertificate; + } + + @Override + public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException { + KeyMaterial newCertificate = reader.read(data); + Certificate existingCertificate; + File certFile; + try { + existingCertificate = readByFingerprint(newCertificate.getFingerprint()); + certFile = resolver.getCertFileByFingerprint(newCertificate.getFingerprint()); + } catch (BadNameException e) { + throw new BadDataException(); + } + + if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + newCertificate = merge.merge(newCertificate, existingCertificate); + } + + writeToFile(newCertificate.getInputStream(), certFile); + + return newCertificate.asCertificate(); + } + + @Override + public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException { + KeyMaterial newCertificate = reader.read(data); + KeyMaterial existingCertificate; + File certFile; + try { + existingCertificate = readBySpecialName(specialName); + certFile = resolver.getCertFileBySpecialName(specialName); + } catch (BadNameException e) { + throw new BadDataException(); + } + + if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + newCertificate = merge.merge(newCertificate, existingCertificate); + } + + writeToFile(newCertificate.getInputStream(), certFile); + + return newCertificate.asCertificate(); + } + + private void writeToFile(InputStream inputStream, File certFile) + throws IOException { + certFile.getParentFile().mkdirs(); + if (!certFile.exists() && !certFile.createNewFile()) { + throw new IOException("Could not create cert file " + certFile.getAbsolutePath()); + } + + FileOutputStream fileOut = new FileOutputStream(certFile); + + byte[] buffer = new byte[4096]; + int read; + while ((read = inputStream.read(buffer)) != -1) { + fileOut.write(buffer, 0, read); + } + + inputStream.close(); + fileOut.close(); + } + + public static class FilenameResolver { + + private final File baseDirectory; + private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); + + public FilenameResolver(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + public File getBaseDirectory() { + return baseDirectory; + } + + /** + * Calculate the file location for the certificate addressed by the given + * lowercase hexadecimal OpenPGP fingerprint. + * + * @param fingerprint fingerprint + * @return absolute certificate file location + * + * @throws BadNameException if the given fingerprint string is not a fingerprint + */ + public File getCertFileByFingerprint(String fingerprint) throws BadNameException { + if (!isFingerprint(fingerprint)) { + throw new BadNameException(); + } + + // is fingerprint + File subdirectory = new File(getBaseDirectory(), fingerprint.substring(0, 2)); + File file = new File(subdirectory, fingerprint.substring(2)); + return file; + } + + /** + * Calculate the file location for the certification addressed using the given special name. + * For known special names, see {@link SpecialNames}. + * + * @param specialName special name (e.g. "trust-root") + * @return absolute certificate file location + * + * @throws BadNameException in case the given special name is not known + */ + public File getCertFileBySpecialName(String specialName) + throws BadNameException { + if (!isSpecialName(specialName)) { + throw new BadNameException(String.format("%s is not a known special name", specialName)); + } + + return new File(getBaseDirectory(), specialName); + } + + /** + * Calculate the file location for the key addressed using the given special name. + * For known special names, see {@link SpecialNames}. + * + * @param specialName special name (e.g. "trust-root") + * @return absolute key file location + * + * @throws BadNameException in case the given special name is not known + */ + public File getKeyFileBySpecialName(String specialName) + throws BadNameException { + if (!isSpecialName(specialName)) { + throw new BadNameException(String.format("%s is not a known special name", specialName)); + } + + return new File(getBaseDirectory(), specialName + ".key"); + } + + private boolean isFingerprint(String fingerprint) { + return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); + } + + private boolean isSpecialName(String specialName) { + return SpecialNames.lookupSpecialName(specialName) != null; + } + + } +} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileLockingMechanism.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileLockingMechanism.java deleted file mode 100644 index 2d87c04..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileLockingMechanism.java +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; - -public class FileLockingMechanism implements LockingMechanism { - - private final File lockFile; - private RandomAccessFile randomAccessFile; - private FileLock fileLock; - - public FileLockingMechanism(File lockFile) { - this.lockFile = lockFile; - } - - public static FileLockingMechanism defaultDirectoryFileLock(File baseDirectory) { - return new FileLockingMechanism(new File(baseDirectory, "writelock")); - } - - @Override - public synchronized void lockDirectory() throws IOException, InterruptedException { - if (randomAccessFile != null) { - // we own the lock already. Let's wait... - this.wait(); - } - - try { - randomAccessFile = new RandomAccessFile(lockFile, "rw"); - } catch (FileNotFoundException e) { - lockFile.createNewFile(); - randomAccessFile = new RandomAccessFile(lockFile, "rw"); - } - - fileLock = randomAccessFile.getChannel().lock(); - } - - @Override - public synchronized boolean tryLockDirectory() throws IOException { - if (randomAccessFile != null) { - // We already locked the directory for another write operation. - // We fail, since we have not yet released the lock from the other operation. - return false; - } - - try { - randomAccessFile = new RandomAccessFile(lockFile, "rw"); - } catch (FileNotFoundException e) { - lockFile.createNewFile(); - randomAccessFile = new RandomAccessFile(lockFile, "rw"); - } - - try { - fileLock = randomAccessFile.getChannel().tryLock(); - if (fileLock == null) { - // try-lock failed, file is locked by another process. - randomAccessFile.close(); - randomAccessFile = null; - return false; - } - } catch (OverlappingFileLockException e) { - // Some other object is holding the lock. - randomAccessFile.close(); - randomAccessFile = null; - return false; - } - return true; - } - - @Override - public synchronized void releaseDirectory() throws IOException { - // unlock file - if (fileLock != null) { - fileLock.release(); - fileLock = null; - } - // close file - if (randomAccessFile != null) { - randomAccessFile.close(); - randomAccessFile = null; - } - // delete file - if (lockFile.exists()) { - lockFile.delete(); - } - // notify waiters - this.notify(); - } -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java deleted file mode 100644 index 0bbdbc1..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import pgp.certificate_store.exception.BadNameException; - -import java.io.File; -import java.util.regex.Pattern; - -public class FilenameResolver { - - private final File baseDirectory; - private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); - - public FilenameResolver(File baseDirectory) { - this.baseDirectory = baseDirectory; - } - - public File getBaseDirectory() { - return baseDirectory; - } - - /** - * Calculate the file location for the certificate addressed by the given - * lowercase hexadecimal OpenPGP fingerprint. - * - * @param fingerprint fingerprint - * @return absolute certificate file location - * - * @throws BadNameException if the given fingerprint string is not a fingerprint - */ - public File getCertFileByFingerprint(String fingerprint) throws BadNameException { - if (!isFingerprint(fingerprint)) { - throw new BadNameException(); - } - - // is fingerprint - File subdirectory = new File(getBaseDirectory(), fingerprint.substring(0, 2)); - File file = new File(subdirectory, fingerprint.substring(2)); - return file; - } - - /** - * Calculate the file location for the certification addressed using the given special name. - * For known special names, see {@link SpecialNames}. - * - * @param specialName special name (e.g. "trust-root") - * @return absolute certificate file location - * - * @throws BadNameException in case the given special name is not known - */ - public File getCertFileBySpecialName(String specialName) - throws BadNameException { - if (!isSpecialName(specialName)) { - throw new BadNameException(String.format("%s is not a known special name", specialName)); - } - - return new File(getBaseDirectory(), specialName); - } - - /** - * Calculate the file location for the key addressed using the given special name. - * For known special names, see {@link SpecialNames}. - * - * @param specialName special name (e.g. "trust-root") - * @return absolute key file location - * - * @throws BadNameException in case the given special name is not known - */ - public File getKeyFileBySpecialName(String specialName) - throws BadNameException { - if (!isSpecialName(specialName)) { - throw new BadNameException(String.format("%s is not a known special name", specialName)); - } - - return new File(getBaseDirectory(), specialName + ".key"); - } - - private boolean isFingerprint(String fingerprint) { - return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); - } - - private boolean isSpecialName(String specialName) { - return SpecialNames.lookupSpecialName(specialName) != null; - } - -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java new file mode 100644 index 0000000..fe7cc89 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.Certificate; +import pgp.certificate_store.KeyMaterial; +import pgp.certificate_store.KeyMaterialMerger; +import pgp.certificate_store.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirectory.Backend { + + protected static class ObjectLockingMechanism implements PGPCertificateDirectory.LockingMechanism { + + private boolean locked = false; + + @Override + public synchronized void lockDirectory() throws InterruptedException { + if (isLocked()) { + wait(); + } + locked = true; + } + + @Override + public synchronized boolean tryLockDirectory() { + if (isLocked()) { + return false; + } + locked = true; + return true; + } + + @Override + public synchronized boolean isLocked() { + return locked; + } + + @Override + public synchronized void releaseDirectory() { + locked = false; + notify(); + } + } + + + private final Map certificateFingerprintMap = new HashMap<>(); + private final Map keyMaterialSpecialNameMap = new HashMap<>(); + private final PGPCertificateDirectory.LockingMechanism lock = new ObjectLockingMechanism(); + private final KeyMaterialReaderBackend reader; + + public InMemoryCertificateDirectoryBackend(KeyMaterialReaderBackend reader) { + this.reader = reader; + } + + @Override + public PGPCertificateDirectory.LockingMechanism getLock() { + return lock; + } + + @Override + public Certificate readByFingerprint(String fingerprint) { + return certificateFingerprintMap.get(fingerprint); + } + + + @Override + public KeyMaterial readBySpecialName(String specialName) { + return keyMaterialSpecialNameMap.get(specialName); + } + + @Override + public Iterator readItems() { + return certificateFingerprintMap.values().iterator(); + } + + @Override + public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws BadDataException, IOException { + KeyMaterial update = reader.read(data); + KeyMaterial existing = readBySpecialName(SpecialNames.TRUST_ROOT); + KeyMaterial merged = merge.merge(update, existing); + keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged); + return merged; + } + + + @Override + public Certificate doInsert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException { + KeyMaterial update = reader.read(data); + Certificate existing = readByFingerprint(update.getFingerprint()); + Certificate merged = merge.merge(update, existing).asCertificate(); + certificateFingerprintMap.put(update.getFingerprint(), merged); + return merged; + } + + @Override + public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException { + KeyMaterial keyMaterial = reader.read(data); + KeyMaterial existing = readBySpecialName(specialName); + KeyMaterial merged = merge.merge(keyMaterial, existing); + keyMaterialSpecialNameMap.put(specialName, merged); + return merged.asCertificate(); + } +} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java deleted file mode 100644 index 92e196d..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/LockingMechanism.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import java.io.IOException; - -public interface LockingMechanism { - - /** - * Lock the store for writes. - * Readers can continue to use the store and will always see consistent certs. - * - * @throws IOException in case of an IO error - * @throws InterruptedException if the thread gets interrupted - */ - void lockDirectory() throws IOException, InterruptedException; - - /** - * Try top lock the store for writes. - * Return false without locking the store in case the store was already locked. - * - * @return true if locking succeeded, false otherwise - * - * @throws IOException in case of an IO error - */ - boolean tryLockDirectory() throws IOException; - - /** - * Release the directory write-lock acquired via {@link #lockDirectory()}. - * - * @throws IOException in case of an IO error - */ - void releaseDirectory() throws IOException; - -} 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 new file mode 100644 index 0000000..b75ac43 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectories.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.NotAStoreException; + +import java.io.File; + +public final class PGPCertificateDirectories { + + private PGPCertificateDirectories() { + + } + + public static PGPCertificateDirectory inMemoryCertificateDirectory(KeyMaterialReaderBackend keyReader) { + return new PGPCertificateDirectory(new InMemoryCertificateDirectoryBackend(keyReader)); + } + + public static PGPCertificateDirectory defaultFileBasedCertificateDirectory(KeyMaterialReaderBackend keyReader) + throws NotAStoreException { + return fileBasedCertificateDirectory(keyReader, BaseDirectoryProvider.getDefaultBaseDir()); + } + + public static PGPCertificateDirectory fileBasedCertificateDirectory( + KeyMaterialReaderBackend keyReader, File baseDirectory) + throws NotAStoreException { + return new PGPCertificateDirectory( + new FileBasedCertificateDirectoryBackend(baseDirectory, keyReader)); + } +} 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 new file mode 100644 index 0000000..ef7fc5d --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateDirectory.java @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.Certificate; +import pgp.certificate_store.KeyMaterial; +import pgp.certificate_store.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +public class PGPCertificateDirectory + implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory { + + private final Backend backend; + + public PGPCertificateDirectory(Backend backend) { + this.backend = backend; + } + + @Override + public Certificate getByFingerprint(String fingerprint) throws BadDataException, BadNameException, IOException { + return backend.readByFingerprint(fingerprint); + } + + @Override + public Certificate getBySpecialName(String specialName) + throws BadNameException, BadDataException, IOException { + KeyMaterial keyMaterial = backend.readBySpecialName(specialName); + if (keyMaterial != null) { + return keyMaterial.asCertificate(); + } + return null; + } + + @Override + public Certificate getTrustRootCertificate() + throws IOException, BadDataException { + try { + return getBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is an implementation MUST"); + } + } + + @Override + public Iterator items() { + return backend.readItems(); + } + + @Override + public Iterator fingerprints() { + Iterator certs = items(); + return new Iterator() { + @Override + public boolean hasNext() { + return certs.hasNext(); + } + + @Override + public String next() { + return certs.next().getFingerprint(); + } + }; + } + + @Override + public KeyMaterial getTrustRoot() throws IOException, BadDataException { + try { + return backend.readBySpecialName(SpecialNames.TRUST_ROOT); + } catch (BadNameException e) { + throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is implementation MUST"); + } + } + + @Override + public KeyMaterial insertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, InterruptedException { + backend.getLock().lockDirectory(); + KeyMaterial inserted = backend.doInsertTrustRoot(data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + @Override + public KeyMaterial tryInsertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException { + if (!backend.getLock().tryLockDirectory()) { + return null; + } + KeyMaterial inserted = backend.doInsertTrustRoot(data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + + + @Override + public Certificate insert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, InterruptedException { + backend.getLock().lockDirectory(); + Certificate inserted = backend.doInsert(data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + @Override + public Certificate tryInsert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException { + if (!backend.getLock().tryLockDirectory()) { + return null; + } + Certificate inserted = backend.doInsert(data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + @Override + public Certificate insertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException, InterruptedException { + backend.getLock().lockDirectory(); + Certificate inserted = backend.doInsertWithSpecialName(specialName, data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + @Override + public Certificate tryInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException { + if (!backend.getLock().tryLockDirectory()) { + return null; + } + Certificate inserted = backend.doInsertWithSpecialName(specialName, data, merge); + backend.getLock().releaseDirectory(); + return inserted; + } + + public interface Backend { + + LockingMechanism getLock(); + + Certificate readByFingerprint(String fingerprint) throws BadNameException, IOException, BadDataException; + + KeyMaterial readBySpecialName(String specialName) throws BadNameException, IOException, BadDataException; + + Iterator readItems(); + + KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws BadDataException, IOException; + + Certificate doInsert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException; + + Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException; + } + + public interface LockingMechanism { + + /** + * Lock the store for writes. + * Readers can continue to use the store and will always see consistent certs. + * + * @throws IOException in case of an IO error + * @throws InterruptedException if the thread gets interrupted + */ + void lockDirectory() throws IOException, InterruptedException; + + /** + * Try top lock the store for writes. + * Return false without locking the store in case the store was already locked. + * + * @return true if locking succeeded, false otherwise + * + * @throws IOException in case of an IO error + */ + boolean tryLockDirectory() throws IOException; + + boolean isLocked(); + + /** + * Release the directory write-lock acquired via {@link #lockDirectory()}. + * + * @throws IOException in case of an IO error + */ + void releaseDirectory() throws IOException; + + } +} 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 new file mode 100644 index 0000000..07ad7de --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.Certificate; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.util.Iterator; + +public interface ReadOnlyPGPCertificateDirectory { + + Certificate getTrustRootCertificate() + throws IOException, BadDataException; + + Certificate getByFingerprint(String fingerprint) + throws IOException, BadNameException, BadDataException; + + Certificate getBySpecialName(String specialName) + throws IOException, BadNameException, BadDataException; + + Iterator items(); + + Iterator fingerprints(); +} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java deleted file mode 100644 index b6b83f4..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; - -import pgp.certificate_store.Key; -import pgp.certificate_store.KeyMerger; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.CertificateMerger; - -public interface SharedPGPCertificateDirectory { - - LockingMechanism getLock(); - - Certificate getByFingerprint(String fingerprint) - throws IOException, BadNameException, BadDataException; - - Certificate getBySpecialName(String specialName) - throws IOException, BadNameException, BadDataException; - - Key getTrustRoot() - throws IOException, BadDataException; - - Key getTrustRootIfChanged(String tag) - throws IOException, BadDataException; - - Key insertTrustRoot(InputStream data, KeyMerger merge) - throws IOException, BadDataException, InterruptedException; - - Key tryInsertTrustRoot(InputStream data, KeyMerger merge) - throws IOException, BadDataException; - - Certificate getByFingerprintIfChanged(String fingerprint, String tag) - throws IOException, BadNameException, BadDataException; - - Certificate getBySpecialNameIfChanged(String specialName, String tag) - throws IOException, BadNameException, BadDataException; - - Certificate insert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException, InterruptedException; - - Certificate tryInsert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException; - - Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadDataException, BadNameException, InterruptedException; - - Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadDataException, BadNameException; - - Iterator items(); - - Iterator fingerprints(); -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java deleted file mode 100644 index b44bdcf..0000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java +++ /dev/null @@ -1,405 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import pgp.certificate_store.Key; -import pgp.certificate_store.KeyMaterial; -import pgp.certificate_store.KeyMerger; -import pgp.certificate_store.KeyReaderBackend; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; -import pgp.certificate_store.exception.NotAStoreException; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.CertificateMerger; - -public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory { - - private final FilenameResolver resolver; - private final LockingMechanism writeLock; - private final KeyReaderBackend keyReaderBackend; - - public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider) - throws NotAStoreException { - this(backendProvider.provideKeyReaderBackend()); - } - - public SharedPGPCertificateDirectoryImpl(KeyReaderBackend keyReaderBackend) - throws NotAStoreException { - this( - BaseDirectoryProvider.getDefaultBaseDir(), - keyReaderBackend); - } - - public SharedPGPCertificateDirectoryImpl(File baseDirectory, - KeyReaderBackend keyReaderBackend) - throws NotAStoreException { - this( - keyReaderBackend, - new FilenameResolver(baseDirectory), - FileLockingMechanism.defaultDirectoryFileLock(baseDirectory)); - } - - public SharedPGPCertificateDirectoryImpl( - KeyReaderBackend keyReaderBackend, - FilenameResolver filenameResolver, - LockingMechanism writeLock) - throws NotAStoreException { - this.keyReaderBackend = keyReaderBackend; - this.resolver = filenameResolver; - this.writeLock = writeLock; - - File baseDirectory = resolver.getBaseDirectory(); - if (!baseDirectory.exists()) { - if (!baseDirectory.mkdirs()) { - throw new NotAStoreException("Cannot create base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "'"); - } - } else { - if (baseDirectory.isFile()) { - throw new NotAStoreException("Base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "' appears to be a file."); - } - } - } - - @Override - public LockingMechanism getLock() { - return writeLock; - } - - @Override - public Certificate getByFingerprint(String fingerprint) - throws IOException, BadNameException, BadDataException { - File certFile = resolver.getCertFileByFingerprint(fingerprint); - if (!certFile.exists()) { - return null; - } - - FileInputStream fileIn = new FileInputStream(certFile); - BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - - Certificate certificate = readCertificate(bufferedIn); - if (!certificate.getFingerprint().equals(fingerprint)) { - // TODO: Figure out more suitable exception - throw new BadDataException(); - } - - return certificate; - } - - private Certificate readCertificate(InputStream inputStream) throws BadDataException, IOException { - KeyMaterial record = keyReaderBackend.read(inputStream); - Certificate certificate = null; - if (record instanceof Certificate) { - certificate = (Certificate) record; - } else if (record instanceof Key) { - Key key = (Key) record; - certificate = key.getCertificate(); - } else { - throw new BadDataException(); - } - return certificate; - } - - private Key readKey(InputStream inputStream) throws BadDataException, IOException { - KeyMaterial record = keyReaderBackend.read(inputStream); - if (record instanceof Key) { - return (Key) record; - } - throw new BadDataException(); - } - - @Override - public Certificate getBySpecialName(String specialName) - throws IOException, BadNameException, BadDataException { - File certFile = resolver.getCertFileBySpecialName(specialName); - if (!certFile.exists()) { - return null; - } - - FileInputStream fileIn = new FileInputStream(certFile); - BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = readCertificate(bufferedIn); - - return certificate; - } - - @Override - public Certificate getByFingerprintIfChanged(String fingerprint, String tag) - throws IOException, BadNameException, BadDataException { - Certificate certificate = getByFingerprint(fingerprint); - if (certificate.getTag().equals(tag)) { - return null; - } - return certificate; - } - - @Override - public Certificate getBySpecialNameIfChanged(String specialName, String tag) - throws IOException, BadNameException, BadDataException { - Certificate certificate = getBySpecialName(specialName); - if (certificate.getTag().equals(tag)) { - return null; - } - return certificate; - } - - @Override - public Key getTrustRoot() throws IOException, BadDataException { - File keyFile; - try { - keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT); - } catch (BadNameException e) { - throw new RuntimeException("trust-root MUST be a known special name", e); - } - - if (!keyFile.exists()) { - return null; - } - FileInputStream fileIn = new FileInputStream(keyFile); - BufferedInputStream bufferedInputStream = new BufferedInputStream(fileIn); - Key key = readKey(bufferedInputStream); - - return key; - } - - @Override - public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException { - // TODO: The tag is likely intended for performance improvements, - // so really we should look it up somewhere without the need to parse the whole key. - Key key = getTrustRoot(); - if (key.getTag().equals(tag)) { - return null; - } - return key; - } - - @Override - public Certificate insert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException, InterruptedException { - writeLock.lockDirectory(); - - Certificate certificate = _insert(data, merge); - - writeLock.releaseDirectory(); - return certificate; - } - - @Override - public Certificate tryInsert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException { - if (!writeLock.tryLockDirectory()) { - return null; - } - - Certificate certificate = _insert(data, merge); - - writeLock.releaseDirectory(); - return certificate; - } - - private Certificate _insert(InputStream data, CertificateMerger merge) - throws IOException, BadDataException { - Certificate newCertificate = readCertificate(data); - Certificate existingCertificate; - File certFile; - try { - existingCertificate = getByFingerprint(newCertificate.getFingerprint()); - certFile = resolver.getCertFileByFingerprint(newCertificate.getFingerprint()); - } catch (BadNameException e) { - throw new BadDataException(); - } - - if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) { - newCertificate = merge.merge(newCertificate, existingCertificate); - } - - writeToFile(newCertificate.getInputStream(), certFile); - - return newCertificate; - } - - private Key _insertTrustRoot(InputStream data, KeyMerger merge) - throws IOException, BadDataException { - Key newKey = readKey(data); - Key existingKey; - File keyFile; - try { - existingKey = getTrustRoot(); - keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT); - } catch (BadNameException e) { - throw new RuntimeException(String.format("trust-root MUST be known special name.", e)); - } - - if (existingKey != null && !existingKey.getTag().equals(newKey.getTag())) { - newKey = merge.merge(newKey, existingKey); - } - - writeToFile(newKey.getInputStream(), keyFile); - - return newKey; - } - - @Override - public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException { - writeLock.lockDirectory(); - - Key key = _insertTrustRoot(data, merge); - - writeLock.releaseDirectory(); - return key; - } - - @Override - public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException { - return null; - } - - private void writeToFile(InputStream inputStream, File certFile) - throws IOException { - certFile.getParentFile().mkdirs(); - if (!certFile.exists() && !certFile.createNewFile()) { - throw new IOException("Could not create cert file " + certFile.getAbsolutePath()); - } - - FileOutputStream fileOut = new FileOutputStream(certFile); - - byte[] buffer = new byte[4096]; - int read; - while ((read = inputStream.read(buffer)) != -1) { - fileOut.write(buffer, 0, read); - } - - inputStream.close(); - fileOut.close(); - } - - @Override - public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadNameException, BadDataException, InterruptedException { - writeLock.lockDirectory(); - - Certificate certificate = _insertSpecial(specialName, data, merge); - - writeLock.releaseDirectory(); - return certificate; - } - - @Override - public Certificate tryInsertWithSpecialName(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadNameException, BadDataException { - if (!writeLock.tryLockDirectory()) { - return null; - } - - Certificate certificate = _insertSpecial(specialName, data, merge); - - writeLock.releaseDirectory(); - return certificate; - } - - private Certificate _insertSpecial(String specialName, InputStream data, CertificateMerger merge) - throws IOException, BadNameException, BadDataException { - Certificate newCertificate = readCertificate(data); - Certificate existingCertificate = getBySpecialName(specialName); - File certFile = resolver.getCertFileBySpecialName(specialName); - - if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) { - newCertificate = merge.merge(newCertificate, existingCertificate); - } - - writeToFile(newCertificate.getInputStream(), certFile); - - return newCertificate; - } - - @Override - public Iterator items() { - return new Iterator() { - - private final List> certificateQueue = Collections.synchronizedList(new ArrayList<>()); - - // Constructor... wtf. - { - File[] subdirectories = resolver.getBaseDirectory().listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return file.isDirectory() && file.getName().matches("^[a-f0-9]{2}$"); - } - }); - - for (File subdirectory : subdirectories) { - File[] files = subdirectory.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return file.isFile() && file.getName().matches("^[a-f0-9]{38}$"); - } - }); - - for (File certFile : files) { - certificateQueue.add(new Lazy() { - @Override - Certificate get() throws BadDataException { - try { - Certificate certificate = readCertificate(new FileInputStream(certFile)); - if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) { - throw new BadDataException(); - } - return certificate; - } catch (IOException e) { - throw new AssertionError("File got deleted."); - } - } - }); - } - } - } - - @Override - public boolean hasNext() { - return !certificateQueue.isEmpty(); - } - - @Override - public Certificate next() { - try { - return certificateQueue.remove(0).get(); - } catch (BadDataException e) { - throw new AssertionError("Could not retrieve item: " + e.getMessage()); - } - } - }; - } - - private abstract static class Lazy { - abstract E get() throws BadDataException; - } - - @Override - public Iterator fingerprints() { - Iterator certificates = items(); - return new Iterator() { - @Override - public boolean hasNext() { - return certificates.hasNext(); - } - - @Override - public String next() { - return certificates.next().getFingerprint(); - } - }; - } -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java new file mode 100644 index 0000000..2ac60f7 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.Certificate; +import pgp.certificate_store.KeyMaterial; +import pgp.certificate_store.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.io.InputStream; + +public interface WritingPGPCertificateDirectory { + + KeyMaterial getTrustRoot() + throws IOException, BadDataException; + + KeyMaterial insertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, InterruptedException; + + KeyMaterial tryInsertTrustRoot(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException; + + Certificate insert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, InterruptedException; + + Certificate tryInsert(InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException; + + Certificate insertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException, InterruptedException; + + Certificate tryInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, BadDataException, BadNameException; + +} diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java index 1d534a4..ee617b3 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java @@ -18,13 +18,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class FilenameResolverTest { private File baseDir; - private FilenameResolver resolver; + private FileBasedCertificateDirectoryBackend.FilenameResolver resolver; @BeforeEach public void setup() throws IOException { baseDir = Files.createTempDirectory("filenameresolver").toFile(); baseDir.deleteOnExit(); - resolver = new FilenameResolver(baseDir); + resolver = new FileBasedCertificateDirectoryBackend.FilenameResolver(baseDir); } @Test diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java index f6501e8..444f2ee 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java @@ -4,37 +4,13 @@ package pgp.certificate_store; -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; - /** * OpenPGP certificate (public key). */ public abstract class Certificate implements KeyMaterial { - /** - * Return an {@link InputStream} of the binary representation of the certificate. - * - * @return input stream - * @throws IOException in case of an IO error - */ - public abstract InputStream getInputStream() throws IOException; - - /** - * Return a tag of the certificate. - * The tag is a checksum calculated over the binary representation of the certificate. - * - * @return tag - * @throws IOException in case of an IO error - */ - public abstract String getTag() throws IOException; - - /** - * Return a {@link Set} containing key-ids of subkeys. - * - * @return subkeys - * @throws IOException in case of an IO error - */ - public abstract Set getSubkeyIds() throws IOException; + @Override + public Certificate asCertificate() { + return this; + } } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java index a2a66df..1bb9eb8 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java @@ -54,12 +54,12 @@ public interface CertificateDirectory { /** * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link CertificateMerger} will be + * If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificate(InputStream, CertificateMerger)} instead. + * consider to use {@link #tryInsertCertificate(InputStream, KeyMaterialMerger)} instead. * * @param data input stream containing the new certificate instance * @param merge callback for merging with an existing certificate instance @@ -69,12 +69,12 @@ public interface CertificateDirectory { * @throws InterruptedException in case the inserting thread gets interrupted * @throws BadDataException if the data stream does not contain valid OpenPGP data */ - Certificate insertCertificate(InputStream data, CertificateMerger merge) + Certificate insertCertificate(InputStream data, KeyMaterialMerger merge) throws IOException, InterruptedException, BadDataException; /** * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link CertificateMerger} will be + * If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * @@ -90,19 +90,19 @@ public interface CertificateDirectory { * @throws IOException in case of an IO-error * @throws BadDataException if the data stream does not contain valid OpenPGP data */ - Certificate tryInsertCertificate(InputStream data, CertificateMerger merge) + Certificate tryInsertCertificate(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException; /** * Insert a certificate into the store. * The certificate will be stored under the given special name instead of its fingerprint. * - * If an instance of the certificate is already present under the special name in the store, the given {@link CertificateMerger} will be + * If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, CertificateMerger)} instead. + * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, KeyMaterialMerger)} instead. * * @param specialName special name of the certificate * @param data input stream containing the new certificate instance @@ -114,14 +114,14 @@ public interface CertificateDirectory { * @throws BadDataException if the certificate file does not contain valid OpenPGP data * @throws BadNameException if the special name is unknown */ - Certificate insertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge) + Certificate insertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, InterruptedException, BadDataException, BadNameException; /** * Insert a certificate into the store. * The certificate will be stored under the given special name instead of its fingerprint. * - * If an instance of the certificate is already present under the special name in the store, the given {@link CertificateMerger} will be + * If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate * will be stored in the store and returned. * @@ -139,7 +139,7 @@ public interface CertificateDirectory { * @throws BadDataException if the data stream does not contain valid OpenPGP data * @throws BadNameException if the special name is not known */ - Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge) + Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException; /** @@ -186,7 +186,7 @@ public interface CertificateDirectory { /** * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge * the two instances into one {@link Key}. The result will be stored in the store and returned. * * This method will not block. Instead, if the store is already write-locked, this method will simply return null @@ -202,16 +202,16 @@ public interface CertificateDirectory { * @throws InterruptedException in case the inserting thread gets interrupted * @throws BadDataException if the data stream does not contain a valid OpenPGP key */ - Key insertTrustRoot(InputStream data, KeyMerger keyMerger) + Key insertTrustRoot(InputStream data, KeyMaterialMerger keyMerger) throws IOException, InterruptedException, BadDataException; /** * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMerger} callback will be used to merge + * If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge * the two instances into one {@link Key}. The result will be stored in the store and returned. * * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider using {@link #tryInsertTrustRoot(InputStream, KeyMerger)} instead. + * consider using {@link #tryInsertTrustRoot(InputStream, KeyMaterialMerger)} instead. * * @param data input stream containing the new trust-root key * @param keyMerger callback for merging with an existing key instance @@ -220,6 +220,6 @@ public interface CertificateDirectory { * @throws IOException in case of an IO error * @throws BadDataException if the data stream does not contain a valid OpenPGP key */ - Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) + Key tryInsertTrustRoot(InputStream data, KeyMaterialMerger keyMerger) throws IOException, BadDataException; } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java deleted file mode 100644 index 9659003..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateMerger.java +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import java.io.IOException; - -/** - * Merge a given certificate (update) with an existing certificate. - */ -public interface CertificateMerger { - - /** - * Merge the given certificate data with the existing certificate and return the result. - * - * If no existing certificate is found (i.e. existing is null), this method returns the unmodified data. - * - * @param data certificate - * @param existing optional already existing copy of the certificate - * @return merged certificate - * - * @throws IOException in case of an IO error - */ - Certificate merge(Certificate data, Certificate existing) throws IOException; - -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java index 6425468..75a93f6 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java @@ -4,9 +4,6 @@ package pgp.certificate_store; -import java.io.IOException; -import java.io.InputStream; - /** * OpenPGP key (secret key). */ @@ -19,14 +16,9 @@ public abstract class Key implements KeyMaterial { */ public abstract Certificate getCertificate(); - /** - * Return an {@link InputStream} of the binary representation of the secret key. - * - * @return input stream - * @throws IOException in case of an IO error - */ - public abstract InputStream getInputStream() throws IOException; - - public abstract String getTag() throws IOException; + @Override + public Certificate asCertificate() { + return getCertificate(); + } } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java index d940a25..7edce0c 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java @@ -4,6 +4,10 @@ package pgp.certificate_store; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + public interface KeyMaterial { /** @@ -14,4 +18,23 @@ public interface KeyMaterial { */ String getFingerprint(); + Certificate asCertificate(); + + /** + * Return an {@link InputStream} of the binary representation of the secret key. + * + * @return input stream + * @throws IOException in case of an IO error + */ + InputStream getInputStream() throws IOException; + + String getTag() throws IOException; + + /** + * Return a {@link Set} containing key-ids of subkeys. + * + * @return subkeys + * @throws IOException in case of an IO error + */ + Set getSubkeyIds() throws IOException; } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java new file mode 100644 index 0000000..4cef3c8 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import java.io.IOException; + +/** + * Merge a given {@link Key} (update) with an existing {@link Key}. + */ +public interface KeyMaterialMerger { + + /** + * Merge the given key material with an existing copy and return the result. + * If no existing {@link KeyMaterial} is found (i.e. if existing is null), this method returns the unmodified data. + * + * @param data key material + * @param existing optional already existing copy of the key material + * @return merged key material + * + * @throws IOException in case of an IO error + */ + KeyMaterial merge(KeyMaterial data, KeyMaterial existing) throws IOException; +} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java similarity index 94% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java index d99d8e2..c45f774 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java @@ -9,7 +9,7 @@ import pgp.certificate_store.exception.BadDataException; import java.io.IOException; import java.io.InputStream; -public interface KeyReaderBackend { +public interface KeyMaterialReaderBackend { /** * Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java deleted file mode 100644 index 3a7a7c5..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMerger.java +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import java.io.IOException; - -/** - * Merge a given {@link Key} (update) with an existing {@link Key}. - */ -public interface KeyMerger { - - /** - * Merge the given key data with the existing {@link Key} and return the result. - * If no existing {@link Key} is found (i.e. if existing is null), this method returns the unmodified data. - * - * @param data key - * @param existing optional already existing copy of the key - * @return merged key - * - * @throws IOException in case of an IO error - */ - Key merge(Key data, Key existing) throws IOException; -} From 7cc0ef5037b631b3bcef04811682b4e7b988fb7e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 9 Aug 2022 18:00:27 +0200 Subject: [PATCH 26/63] Get rid of certificate-store abstraction --- .../jdbc/sqlite/DatabaseSubkeyLookup.java | 2 +- .../pgp/cert_d/jdbc/sqlite/package-info.java | 2 +- pgp-cert-d-java/build.gradle | 3 - .../java/pgp/cert_d}/BadDataException.java | 2 +- .../java/pgp/cert_d}/BadNameException.java | 2 +- .../FileBasedCertificateDirectoryBackend.java | 11 +- .../InMemoryCertificateDirectoryBackend.java | 10 +- .../java/pgp/cert_d/InMemorySubkeyLookup.java | 2 - .../java/pgp/cert_d}/NotAStoreException.java | 2 +- .../pgp/cert_d/PGPCertificateDirectories.java | 3 +- .../pgp/cert_d/PGPCertificateDirectory.java | 8 +- .../ReadOnlyPGPCertificateDirectory.java | 4 +- .../main/java/pgp/cert_d}/SubkeyLookup.java | 2 +- .../WritingPGPCertificateDirectory.java | 8 +- .../java/pgp/certificate}/Certificate.java | 2 +- .../src/main/java/pgp/certificate}/Key.java | 2 +- .../java/pgp/certificate}/KeyMaterial.java | 2 +- .../pgp/certificate}/KeyMaterialMerger.java | 2 +- .../KeyMaterialReaderBackend.java | 4 +- .../java/pgp/certificate}/package-info.java | 4 +- .../java/pgp/cert_d/FilenameResolverTest.java | 1 - .../java/pgp/cert_d/SubkeyLookupTest.java | 1 - pgp-certificate-store/README.md | 13 - pgp-certificate-store/build.gradle | 36 --- .../AbstractCertificateStore.java | 39 --- .../CertificateDirectory.java | 225 ------------------ .../certificate_store/CertificateStore.java | 9 - .../exception/package-info.java | 10 - .../java/pgp/certificate_store/DummyTest.java | 16 -- settings.gradle | 3 +- 30 files changed, 31 insertions(+), 399 deletions(-) rename {pgp-certificate-store/src/main/java/pgp/certificate_store/exception => pgp-cert-d-java/src/main/java/pgp/cert_d}/BadDataException.java (85%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store/exception => pgp-cert-d-java/src/main/java/pgp/cert_d}/BadNameException.java (90%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store/exception => pgp-cert-d-java/src/main/java/pgp/cert_d}/NotAStoreException.java (89%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/cert_d}/SubkeyLookup.java (97%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/Certificate.java (90%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/Key.java (93%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/KeyMaterial.java (96%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/KeyMaterialMerger.java (96%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/KeyMaterialReaderBackend.java (89%) rename {pgp-certificate-store/src/main/java/pgp/certificate_store => pgp-cert-d-java/src/main/java/pgp/certificate}/package-info.java (57%) delete mode 100644 pgp-certificate-store/README.md delete mode 100644 pgp-certificate-store/build.gradle delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/AbstractCertificateStore.java delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java delete mode 100644 pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java index f35d7c8..5e3b58e 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java @@ -11,7 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import pgp.certificate_store.SubkeyLookup; +import pgp.cert_d.SubkeyLookup; public class DatabaseSubkeyLookup implements SubkeyLookup { diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java index cf7f9af..08114e4 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java @@ -3,6 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Implementation of a {@link pgp.certificate_store.SubkeyLookup} mechanism using an SQLite Database. + * Implementation of a {@link pgp.cert_d.SubkeyLookup} mechanism using an SQLite Database. */ package pgp.cert_d.jdbc.sqlite; diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index db5ba5c..9513682 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -28,9 +28,6 @@ dependencies { // SQL Subkey table testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") - - // Certificate store - api project(":pgp-certificate-store") } animalsniffer { diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java similarity index 85% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java index 3bb7019..6dbbcde 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store.exception; +package pgp.cert_d; /** * The data was not a valid OpenPGP cert or key in binary format. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java similarity index 90% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java index 957126e..4386506 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store.exception; +package pgp.cert_d; /** * Provided name was neither a valid fingerprint, nor a known special name. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java index 8b112cb..469457f 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java @@ -4,13 +4,10 @@ package pgp.cert_d; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.KeyMaterial; -import pgp.certificate_store.KeyMaterialMerger; -import pgp.certificate_store.KeyMaterialReaderBackend; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; -import pgp.certificate_store.exception.NotAStoreException; +import pgp.certificate.Certificate; +import pgp.certificate.KeyMaterial; +import pgp.certificate.KeyMaterialMerger; +import pgp.certificate.KeyMaterialReaderBackend; import java.io.BufferedInputStream; import java.io.File; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java index fe7cc89..ff7c401 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java @@ -4,12 +4,10 @@ package pgp.cert_d; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.KeyMaterial; -import pgp.certificate_store.KeyMaterialMerger; -import pgp.certificate_store.KeyMaterialReaderBackend; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; +import pgp.certificate.Certificate; +import pgp.certificate.KeyMaterial; +import pgp.certificate.KeyMaterialMerger; +import pgp.certificate.KeyMaterialReaderBackend; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java index ec67ee0..400b0c1 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java @@ -11,8 +11,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import pgp.certificate_store.SubkeyLookup; - public class InMemorySubkeyLookup implements SubkeyLookup { private static final Map> subkeyMap = new HashMap<>(); diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java similarity index 89% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java index a19aa9c..4abd1f0 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store.exception; +package pgp.cert_d; /** * The base dir cannot possibly contain a store. 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 b75ac43..de185b0 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 @@ -4,8 +4,7 @@ package pgp.cert_d; -import pgp.certificate_store.KeyMaterialReaderBackend; -import pgp.certificate_store.exception.NotAStoreException; +import pgp.certificate.KeyMaterialReaderBackend; import java.io.File; 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 ef7fc5d..f3a753d 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 @@ -4,11 +4,9 @@ package pgp.cert_d; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.KeyMaterial; -import pgp.certificate_store.KeyMaterialMerger; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; +import pgp.certificate.Certificate; +import pgp.certificate.KeyMaterial; +import pgp.certificate.KeyMaterialMerger; import java.io.IOException; import java.io.InputStream; 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 07ad7de..03a563b 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 @@ -4,9 +4,7 @@ package pgp.cert_d; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; +import pgp.certificate.Certificate; import java.io.IOException; import java.util.Iterator; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java similarity index 97% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java index 55d03e6..58101bd 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.cert_d; import java.io.IOException; import java.util.List; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java index 2ac60f7..57ce4c8 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java @@ -4,11 +4,9 @@ package pgp.cert_d; -import pgp.certificate_store.Certificate; -import pgp.certificate_store.KeyMaterial; -import pgp.certificate_store.KeyMaterialMerger; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; +import pgp.certificate.Certificate; +import pgp.certificate.KeyMaterial; +import pgp.certificate.KeyMaterialMerger; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java b/pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java similarity index 90% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java index 444f2ee..969064d 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Certificate.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.certificate; /** * OpenPGP certificate (public key). diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java b/pgp-cert-d-java/src/main/java/pgp/certificate/Key.java similarity index 93% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/Key.java index 75a93f6..80c9636 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/Key.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/Key.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.certificate; /** * OpenPGP key (secret key). diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java similarity index 96% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java index 7edce0c..87c309e 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterial.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.certificate; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java similarity index 96% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java index 4cef3c8..bd70bc8 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialMerger.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.certificate; import java.io.IOException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java similarity index 89% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java index c45f774..b1996f9 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/KeyMaterialReaderBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate_store; +package pgp.certificate; -import pgp.certificate_store.exception.BadDataException; +import pgp.cert_d.BadDataException; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java b/pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java similarity index 57% rename from pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java rename to pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java index 39164d4..ac5b0f4 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java @@ -3,6 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Abstract definitions of an OpenPGP certificate store. + * General OpenPGP Certificate Storage related classes. */ -package pgp.certificate_store; +package pgp.certificate; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java index ee617b3..513bcad 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java @@ -6,7 +6,6 @@ package pgp.cert_d; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import pgp.certificate_store.exception.BadNameException; import java.io.File; import java.io.IOException; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java index 03ad9c8..979f190 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java @@ -24,7 +24,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import pgp.cert_d.jdbc.sqlite.DatabaseSubkeyLookup; import pgp.cert_d.jdbc.sqlite.SqliteSubkeyLookupDaoImpl; -import pgp.certificate_store.SubkeyLookup; public class SubkeyLookupTest { diff --git a/pgp-certificate-store/README.md b/pgp-certificate-store/README.md deleted file mode 100644 index 58f9af9..0000000 --- a/pgp-certificate-store/README.md +++ /dev/null @@ -1,13 +0,0 @@ - - -# PGP Certificate Store Definitions - -[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgp-certificate-store/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgp-certificate-store) -[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgp-certificate-store)](https://search.maven.org/artifact/org.pgpainless/pgp-certificate-store) - -This module contains API definitions for an OpenPGP certificate store. -A certificate store is used to store public key certificates only. diff --git a/pgp-certificate-store/build.gradle b/pgp-certificate-store/build.gradle deleted file mode 100644 index f2d7d7a..0000000 --- a/pgp-certificate-store/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id 'java-library' -} - -group 'org.pgpainless' - -repositories { - mavenCentral() -} - -apply plugin: 'ru.vyarus.animalsniffer' - -dependencies { - // animal sniffer for ensuring Android API compatibility - signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" - - // JUnit for testing - testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - - // Logging - api "org.slf4j:slf4j-api:$slf4jVersion" - testImplementation "ch.qos.logback:logback-classic:$logbackVersion" -} - -animalsniffer { - sourceSets = [sourceSets.main] -} - -test { - useJUnitPlatform() -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/AbstractCertificateStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/AbstractCertificateStore.java deleted file mode 100644 index 3bd394e..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/AbstractCertificateStore.java +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public abstract class AbstractCertificateStore implements CertificateStore { - - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCertificateStore.class); - - public Set getCertificatesBySubkeyId(long subkeyId) - throws IOException { - Set identifiers = getCertificateFingerprintsForSubkeyId(subkeyId); - if (identifiers.isEmpty()) { - return Collections.emptySet(); - } - - Set certificates = new HashSet<>(); - for (String identifier : identifiers) { - try { - certificates.add(getCertificate(identifier)); - } catch (BadNameException | BadDataException e) { - LOGGER.warn("Could not read certificate.", e); - } - } - - return certificates; - } -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java deleted file mode 100644 index 1bb9eb8..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; - -/** - * Certificate storage definition. - * This interface defines methods to insert and retrieve {@link Certificate Certificates} to and from a store. - * - * {@link Certificate Certificates} are hereby identified by identifiers. An identifier can either be a fingerprint - * or a special name. Special names are implementation-defined identifiers for certificates. - * - * Fingerprints are expected to be hexadecimal lowercase character sequences. - */ -public interface CertificateDirectory { - - /** - * 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 - * - * @throws IOException in case of an IO-error - * @throws BadNameException if the identifier is invalid - * @throws BadDataException if the certificate file contains invalid data - */ - Certificate getCertificate(String identifier) - throws IOException, BadNameException, BadDataException; - - /** - * Return the certificate that matches the given identifier, but only iff it changed since the last invocation. - * To compare the certificate against its last returned result, the given tag is used. - * If the tag of the currently found certificate matches the given argument, return null. - * - * @param identifier identifier for a certificate - * @param tag tag to compare freshness - * @return changed certificate or null - * - * @throws IOException in case of an IO-error - * @throws BadNameException if the identifier is invalid - * @throws BadDataException if the certificate file contains invalid data - */ - Certificate getCertificateIfChanged(String identifier, String tag) - throws IOException, BadNameException, BadDataException; - - /** - * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be - * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate - * will be stored in the store and returned. - * - * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificate(InputStream, KeyMaterialMerger)} instead. - * - * @param data input stream containing the new certificate instance - * @param merge callback for merging with an existing certificate instance - * @return merged certificate - * - * @throws IOException in case of an IO-error - * @throws InterruptedException in case the inserting thread gets interrupted - * @throws BadDataException if the data stream does not contain valid OpenPGP data - */ - Certificate insertCertificate(InputStream data, KeyMaterialMerger merge) - throws IOException, InterruptedException, BadDataException; - - /** - * Insert a certificate into the store. - * If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be - * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate - * will be stored in the store and returned. - * - * This method will not block. Instead, if the store is already write-locked, this method will simply return null - * without any writing. - * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock - * and return the written certificate. - * - * @param data input stream containing the new certificate instance - * @param merge callback for merging with an existing certificate instance - * @return merged certificate or null if the store cannot be locked - * - * @throws IOException in case of an IO-error - * @throws BadDataException if the data stream does not contain valid OpenPGP data - */ - Certificate tryInsertCertificate(InputStream data, KeyMaterialMerger merge) - throws IOException, BadDataException; - - /** - * Insert a certificate into the store. - * The certificate will be stored under the given special name instead of its fingerprint. - * - * If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be - * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate - * will be stored in the store and returned. - * - * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, KeyMaterialMerger)} instead. - * - * @param specialName special name of the certificate - * @param data input stream containing the new certificate instance - * @param merge callback for merging with an existing certificate instance - * @return merged certificate or null if the store cannot be locked - * - * @throws IOException in case of an IO-error - * @throws InterruptedException if the thread is interrupted - * @throws BadDataException if the certificate file does not contain valid OpenPGP data - * @throws BadNameException if the special name is unknown - */ - Certificate insertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) - throws IOException, InterruptedException, BadDataException, BadNameException; - - /** - * Insert a certificate into the store. - * The certificate will be stored under the given special name instead of its fingerprint. - * - * If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be - * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate - * will be stored in the store and returned. - * - * This method will not block. Instead, if the store is already write-locked, this method will simply return null - * without any writing. - * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock - * and return the written certificate. - * - * @param specialName special name for the certificate - * @param data input stream containing the new certificate instance - * @param merge callback for merging with an existing certificate instance - * @return merged certificate or null if the store cannot be locked - * - * @throws IOException in case of an IO-error - * @throws BadDataException if the data stream does not contain valid OpenPGP data - * @throws BadNameException if the special name is not known - */ - Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) - throws IOException, BadDataException, BadNameException; - - /** - * Return an {@link Iterator} containing all certificates in the store. - * The iterator will contain both certificates addressed by special names and by fingerprints. - * - * @return certificates - */ - Iterator getCertificates(); - - /** - * Return an {@link Iterator} containing all certificate fingerprints from the store. - * Note that this only includes the fingerprints of certificate primary keys, not those of subkeys. - * - * @return fingerprints - */ - Iterator getFingerprints(); - - /** - * Return the current trust-root key. - * If no trust-root key is present, return null. - * - * @return trust-root key - * - * @throws IOException in case of an IO error - * @throws BadDataException if the key datum contains invalid data - */ - Key getTrustRoot() - throws IOException, BadDataException; - - /** - * Return the current trust-root key, but only iff it changed since the last invocation of this method. - * To compare the key against its last returned result, the given tag is used. - * If the tag of the currently found key matches the given argument, return null. - * - * @param tag tag to compare freshness - * @return changed key or null - * - * @throws IOException in case of an IO error - * @throws BadDataException if the key datum contains invalid data - */ - Key getTrustRootIfChanged(String tag) - throws IOException, BadDataException; - - /** - * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge - * the two instances into one {@link Key}. The result will be stored in the store and returned. - * - * This method will not block. Instead, if the store is already write-locked, this method will simply return null - * without writing anything. - * However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock - * and return the written key. - * - * @param data input stream containing the new trust-root key - * @param keyMerger callback for merging with an existing key instance - * @return merged key - * - * @throws IOException in case of an IO error - * @throws InterruptedException in case the inserting thread gets interrupted - * @throws BadDataException if the data stream does not contain a valid OpenPGP key - */ - Key insertTrustRoot(InputStream data, KeyMaterialMerger keyMerger) - throws IOException, InterruptedException, BadDataException; - - /** - * Insert the given trust-root key into the store. - * If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge - * the two instances into one {@link Key}. The result will be stored in the store and returned. - * - * This method will block until a write-lock on the store can be acquired. If you cannot afford blocking, - * consider using {@link #tryInsertTrustRoot(InputStream, KeyMaterialMerger)} instead. - * - * @param data input stream containing the new trust-root key - * @param keyMerger callback for merging with an existing key instance - * @return merged key - * - * @throws IOException in case of an IO error - * @throws BadDataException if the data stream does not contain a valid OpenPGP key - */ - Key tryInsertTrustRoot(InputStream data, KeyMaterialMerger keyMerger) - throws IOException, BadDataException; -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java deleted file mode 100644 index a8325ee..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateStore.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -public interface CertificateStore extends CertificateDirectory, SubkeyLookup { - -} diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java deleted file mode 100644 index c06ce06..0000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Exceptions defined by the Shared PGP Certificate Directory. - * - * @see Failure Modes - */ -package pgp.certificate_store.exception; diff --git a/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java b/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java deleted file mode 100644 index e766c29..0000000 --- a/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class DummyTest { - @Test - public void test() { - assertTrue(true); - } -} diff --git a/settings.gradle b/settings.gradle index 3920c69..39451e2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,5 @@ rootProject.name = 'cert-d-java' include 'pgp-cert-d-java', - 'pgp-cert-d-java-jdbc-sqlite-lookup', - 'pgp-certificate-store' + 'pgp-cert-d-java-jdbc-sqlite-lookup' From f91c5065fc88b8a0509a9401af59422fbae9544e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 11 Aug 2022 21:50:02 +0200 Subject: [PATCH 27/63] Organize cert-d-java classes in packages --- .../java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java | 2 +- .../main/java/pgp/cert_d/jdbc/sqlite/package-info.java | 2 +- .../main/java/pgp/cert_d/PGPCertificateDirectories.java | 3 +++ .../src/main/java/pgp/cert_d/PGPCertificateDirectory.java | 2 ++ .../java/pgp/cert_d/ReadOnlyPGPCertificateDirectory.java | 2 ++ .../java/pgp/cert_d/WritingPGPCertificateDirectory.java | 2 ++ .../FileBasedCertificateDirectoryBackend.java | 7 ++++++- .../InMemoryCertificateDirectoryBackend.java | 6 +++++- .../src/main/java/pgp/cert_d/backend/package-info.java | 8 ++++++++ .../java/pgp/cert_d/{ => exception}/BadDataException.java | 2 +- .../java/pgp/cert_d/{ => exception}/BadNameException.java | 2 +- .../pgp/cert_d/{ => exception}/NotAStoreException.java | 2 +- .../src/main/java/pgp/cert_d/exception/package-info.java | 8 ++++++++ .../cert_d/{ => subkey_lookup}/InMemorySubkeyLookup.java | 2 +- .../java/pgp/cert_d/{ => subkey_lookup}/SubkeyLookup.java | 2 +- .../main/java/pgp/cert_d/subkey_lookup/package-info.java | 8 ++++++++ .../java/pgp/certificate/KeyMaterialReaderBackend.java | 2 +- .../src/test/java/pgp/cert_d/FilenameResolverTest.java | 2 ++ .../src/test/java/pgp/cert_d/SubkeyLookupTest.java | 2 ++ 19 files changed, 56 insertions(+), 10 deletions(-) rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => backend}/FileBasedCertificateDirectoryBackend.java (98%) rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => backend}/InMemoryCertificateDirectoryBackend.java (94%) create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/backend/package-info.java rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => exception}/BadDataException.java (88%) rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => exception}/BadNameException.java (92%) rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => exception}/NotAStoreException.java (92%) create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => subkey_lookup}/InMemorySubkeyLookup.java (97%) rename pgp-cert-d-java/src/main/java/pgp/cert_d/{ => subkey_lookup}/SubkeyLookup.java (96%) create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/package-info.java diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java index 5e3b58e..cc56b6d 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookup.java @@ -11,7 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import pgp.cert_d.SubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookup; public class DatabaseSubkeyLookup implements SubkeyLookup { diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java index 08114e4..74bf428 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/package-info.java @@ -3,6 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Implementation of a {@link pgp.cert_d.SubkeyLookup} mechanism using an SQLite Database. + * Implementation of a {@link pgp.cert_d.subkey_lookup.SubkeyLookup} mechanism using an SQLite Database. */ package pgp.cert_d.jdbc.sqlite; 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 de185b0..1dfeefb 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 @@ -4,6 +4,9 @@ package pgp.cert_d; +import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; +import pgp.cert_d.backend.InMemoryCertificateDirectoryBackend; +import pgp.cert_d.exception.NotAStoreException; import pgp.certificate.KeyMaterialReaderBackend; import java.io.File; 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 f3a753d..48de056 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 @@ -4,6 +4,8 @@ package pgp.cert_d; +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; import pgp.certificate.Certificate; import pgp.certificate.KeyMaterial; import pgp.certificate.KeyMaterialMerger; 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 03a563b..801179f 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 @@ -4,6 +4,8 @@ package pgp.cert_d; +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; import pgp.certificate.Certificate; import java.io.IOException; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java index 57ce4c8..03f2c51 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java @@ -4,6 +4,8 @@ package pgp.cert_d; +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; import pgp.certificate.Certificate; import pgp.certificate.KeyMaterial; import pgp.certificate.KeyMaterialMerger; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java similarity index 98% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java index 469457f..d802077 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/FileBasedCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/FileBasedCertificateDirectoryBackend.java @@ -2,8 +2,13 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.backend; +import pgp.cert_d.PGPCertificateDirectory; +import pgp.cert_d.SpecialNames; +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; +import pgp.cert_d.exception.NotAStoreException; import pgp.certificate.Certificate; import pgp.certificate.KeyMaterial; import pgp.certificate.KeyMaterialMerger; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java similarity index 94% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java index ff7c401..5fac78c 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemoryCertificateDirectoryBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/InMemoryCertificateDirectoryBackend.java @@ -2,8 +2,12 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.backend; +import pgp.cert_d.PGPCertificateDirectory; +import pgp.cert_d.SpecialNames; +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; import pgp.certificate.Certificate; import pgp.certificate.KeyMaterial; import pgp.certificate.KeyMaterialMerger; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/package-info.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/package-info.java new file mode 100644 index 0000000..bf02c7c --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/backend/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Storage Backends. + */ +package pgp.cert_d.backend; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java similarity index 88% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java index 6dbbcde..7dc8b7d 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BadDataException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.exception; /** * The data was not a valid OpenPGP cert or key in binary format. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java similarity index 92% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java index 4386506..510804e 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BadNameException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.exception; /** * Provided name was neither a valid fingerprint, nor a known special name. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java similarity index 92% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java index 4abd1f0..734de49 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/NotAStoreException.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.exception; /** * The base dir cannot possibly contain a store. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java new file mode 100644 index 0000000..b051b8b --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Exceptions. + */ +package pgp.cert_d.exception; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookup.java similarity index 97% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookup.java index 400b0c1..a42f652 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookup.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.subkey_lookup; import java.util.Collections; import java.util.HashMap; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookup.java similarity index 96% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookup.java index 58101bd..05fe766 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookup.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.subkey_lookup; import java.io.IOException; import java.util.List; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/package-info.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/package-info.java new file mode 100644 index 0000000..eb0a8b5 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Subkey Lookup functionality. + */ +package pgp.cert_d.subkey_lookup; diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java index b1996f9..2ee7972 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java +++ b/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java @@ -4,7 +4,7 @@ package pgp.certificate; -import pgp.cert_d.BadDataException; +import pgp.cert_d.exception.BadDataException; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java index 513bcad..8d94279 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java @@ -6,6 +6,8 @@ package pgp.cert_d; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; +import pgp.cert_d.exception.BadNameException; import java.io.File; import java.io.IOException; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java index 979f190..d05a149 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java @@ -24,6 +24,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import pgp.cert_d.jdbc.sqlite.DatabaseSubkeyLookup; import pgp.cert_d.jdbc.sqlite.SqliteSubkeyLookupDaoImpl; +import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookup; public class SubkeyLookupTest { From a3162f0cf986d40201868def55f814292a139324 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 12 Aug 2022 14:10:09 +0200 Subject: [PATCH 28/63] Reintroduce pgp-certificate-store layer --- pgp-cert-d-java/build.gradle | 2 + .../pgp/cert_d/PGPCertificateDirectories.java | 16 +-- .../pgp/cert_d/PGPCertificateDirectory.java | 35 ++++-- .../cert_d/PGPCertificateStoreAdapter.java | 77 +++++++++++++ .../ReadOnlyPGPCertificateDirectory.java | 6 +- .../WritingPGPCertificateDirectory.java | 10 +- .../FileBasedCertificateDirectoryBackend.java | 14 +-- .../InMemoryCertificateDirectoryBackend.java | 12 +- .../java/pgp/cert_d/FilenameResolverTest.java | 2 +- pgp-certificate-store/README.md | 13 +++ pgp-certificate-store/build.gradle | 36 ++++++ .../PGPCertificateStore.java | 104 ++++++++++++++++++ .../certificate/Certificate.java | 2 +- .../certificate_store}/certificate/Key.java | 2 +- .../certificate/KeyMaterial.java | 5 +- .../certificate/KeyMaterialMerger.java | 2 +- .../certificate/KeyMaterialReaderBackend.java | 4 +- .../certificate/package-info.java | 2 +- .../exception/BadDataException.java | 2 +- .../exception/BadNameException.java | 2 +- .../exception/NotAStoreException.java | 2 +- .../exception/package-info.java | 2 +- .../pgp/certificate_store/package-info.java | 8 ++ .../java/pgp/certificate_store/DummyTest.java | 16 +++ settings.gradle | 3 +- 25 files changed, 330 insertions(+), 49 deletions(-) create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java create mode 100644 pgp-certificate-store/README.md create mode 100644 pgp-certificate-store/build.gradle create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/Certificate.java (87%) rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/Key.java (91%) rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/KeyMaterial.java (88%) rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/KeyMaterialMerger.java (94%) rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/KeyMaterialReaderBackend.java (87%) rename {pgp-cert-d-java/src/main/java/pgp => pgp-certificate-store/src/main/java/pgp/certificate_store}/certificate/package-info.java (80%) rename {pgp-cert-d-java/src/main/java/pgp/cert_d => pgp-certificate-store/src/main/java/pgp/certificate_store}/exception/BadDataException.java (85%) rename {pgp-cert-d-java/src/main/java/pgp/cert_d => pgp-certificate-store/src/main/java/pgp/certificate_store}/exception/BadNameException.java (90%) rename {pgp-cert-d-java/src/main/java/pgp/cert_d => pgp-certificate-store/src/main/java/pgp/certificate_store}/exception/NotAStoreException.java (89%) rename {pgp-cert-d-java/src/main/java/pgp/cert_d => pgp-certificate-store/src/main/java/pgp/certificate_store}/exception/package-info.java (76%) create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java create mode 100644 pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index 9513682..fabb0a8 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -26,6 +26,8 @@ dependencies { // Logging testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + api project(":pgp-certificate-store") + // SQL Subkey table testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") } 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 1dfeefb..e248e0c 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 @@ -6,8 +6,10 @@ package pgp.cert_d; import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; import pgp.cert_d.backend.InMemoryCertificateDirectoryBackend; -import pgp.cert_d.exception.NotAStoreException; -import pgp.certificate.KeyMaterialReaderBackend; +import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookup; +import pgp.certificate_store.certificate.KeyMaterialReaderBackend; +import pgp.certificate_store.exception.NotAStoreException; import java.io.File; @@ -18,18 +20,18 @@ public final class PGPCertificateDirectories { } public static PGPCertificateDirectory inMemoryCertificateDirectory(KeyMaterialReaderBackend keyReader) { - return new PGPCertificateDirectory(new InMemoryCertificateDirectoryBackend(keyReader)); + return new PGPCertificateDirectory(new InMemoryCertificateDirectoryBackend(keyReader), new InMemorySubkeyLookup()); } - public static PGPCertificateDirectory defaultFileBasedCertificateDirectory(KeyMaterialReaderBackend keyReader) + public static PGPCertificateDirectory defaultFileBasedCertificateDirectory(KeyMaterialReaderBackend keyReader, SubkeyLookup subkeyLookup) throws NotAStoreException { - return fileBasedCertificateDirectory(keyReader, BaseDirectoryProvider.getDefaultBaseDir()); + return fileBasedCertificateDirectory(keyReader, BaseDirectoryProvider.getDefaultBaseDir(), subkeyLookup); } public static PGPCertificateDirectory fileBasedCertificateDirectory( - KeyMaterialReaderBackend keyReader, File baseDirectory) + KeyMaterialReaderBackend keyReader, File baseDirectory, SubkeyLookup subkeyLookup) throws NotAStoreException { return new PGPCertificateDirectory( - new FileBasedCertificateDirectoryBackend(baseDirectory, keyReader)); + 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 48de056..c84af4d 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 @@ -4,23 +4,28 @@ package pgp.cert_d; -import pgp.cert_d.exception.BadDataException; -import pgp.cert_d.exception.BadNameException; -import pgp.certificate.Certificate; -import pgp.certificate.KeyMaterial; -import pgp.certificate.KeyMaterialMerger; +import pgp.cert_d.subkey_lookup.SubkeyLookup; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.KeyMaterial; +import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; +import java.util.List; +import java.util.Set; public class PGPCertificateDirectory - implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory { + implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory, SubkeyLookup { private final Backend backend; + private final SubkeyLookup subkeyLookup; - public PGPCertificateDirectory(Backend backend) { + public PGPCertificateDirectory(Backend backend, SubkeyLookup subkeyLookup) { this.backend = backend; + this.subkeyLookup = subkeyLookup; } @Override @@ -83,6 +88,7 @@ public class PGPCertificateDirectory throws IOException, BadDataException, InterruptedException { backend.getLock().lockDirectory(); KeyMaterial inserted = backend.doInsertTrustRoot(data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } @@ -94,6 +100,7 @@ public class PGPCertificateDirectory return null; } KeyMaterial inserted = backend.doInsertTrustRoot(data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } @@ -105,6 +112,7 @@ public class PGPCertificateDirectory throws IOException, BadDataException, InterruptedException { backend.getLock().lockDirectory(); Certificate inserted = backend.doInsert(data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } @@ -116,6 +124,7 @@ public class PGPCertificateDirectory return null; } Certificate inserted = backend.doInsert(data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } @@ -125,6 +134,7 @@ public class PGPCertificateDirectory throws IOException, BadDataException, BadNameException, InterruptedException { backend.getLock().lockDirectory(); Certificate inserted = backend.doInsertWithSpecialName(specialName, data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } @@ -136,10 +146,21 @@ public class PGPCertificateDirectory return null; } Certificate inserted = backend.doInsertWithSpecialName(specialName, data, merge); + subkeyLookup.storeCertificateSubkeyIds(inserted.getFingerprint(), inserted.getSubkeyIds()); backend.getLock().releaseDirectory(); return inserted; } + @Override + public Set getCertificateFingerprintsForSubkeyId(long subkeyId) throws IOException { + return subkeyLookup.getCertificateFingerprintsForSubkeyId(subkeyId); + } + + @Override + public void storeCertificateSubkeyIds(String certificate, List subkeyIds) throws IOException { + subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds); + } + public interface Backend { LockingMechanism getLock(); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java new file mode 100644 index 0000000..5d95e6f --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.certificate_store.PGPCertificateStore; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Adapter class to adapt a {@link PGPCertificateDirectory} to the {@link PGPCertificateStore} interface. + */ +public class PGPCertificateStoreAdapter implements PGPCertificateStore { + + private final PGPCertificateDirectory directory; + + public PGPCertificateStoreAdapter(PGPCertificateDirectory directory) { + this.directory = directory; + } + + @Override + public Certificate getCertificate(String identifier) + throws IOException, BadNameException, BadDataException { + if (SpecialNames.lookupSpecialName(identifier) != null) { + return directory.getBySpecialName(identifier); + } else { + return directory.getByFingerprint(identifier.toLowerCase()); + } + } + + @Override + public Iterator getCertificatesBySubkeyId(long subkeyId) + throws IOException, BadDataException { + Set fingerprints = directory.getCertificateFingerprintsForSubkeyId(subkeyId); + Set certificates = new HashSet<>(); + for (String fingerprint : fingerprints) { + try { + certificates.add(directory.getByFingerprint(fingerprint)); + } catch (BadNameException e) { + throw new RuntimeException(e); + } + } + return certificates.iterator(); + } + + @Override + public Certificate insertCertificate(InputStream data, KeyMaterialMerger merge) + throws IOException, InterruptedException, BadDataException { + Certificate certificate = directory.insert(data, merge); + return certificate; + } + + @Override + public Certificate insertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, InterruptedException, BadDataException, BadNameException { + return directory.insertWithSpecialName(specialName, data, merge); + } + + @Override + public Iterator getCertificates() { + return directory.items(); + } + + @Override + public Iterator getFingerprints() { + return directory.fingerprints(); + } +} 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 801179f..4c9fabc 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 @@ -4,9 +4,9 @@ package pgp.cert_d; -import pgp.cert_d.exception.BadDataException; -import pgp.cert_d.exception.BadNameException; -import pgp.certificate.Certificate; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.util.Iterator; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java index 03f2c51..2857165 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java @@ -4,11 +4,11 @@ package pgp.cert_d; -import pgp.cert_d.exception.BadDataException; -import pgp.cert_d.exception.BadNameException; -import pgp.certificate.Certificate; -import pgp.certificate.KeyMaterial; -import pgp.certificate.KeyMaterialMerger; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.KeyMaterial; +import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.io.InputStream; 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 d802077..5decae2 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 @@ -6,13 +6,13 @@ package pgp.cert_d.backend; import pgp.cert_d.PGPCertificateDirectory; import pgp.cert_d.SpecialNames; -import pgp.cert_d.exception.BadDataException; -import pgp.cert_d.exception.BadNameException; -import pgp.cert_d.exception.NotAStoreException; -import pgp.certificate.Certificate; -import pgp.certificate.KeyMaterial; -import pgp.certificate.KeyMaterialMerger; -import pgp.certificate.KeyMaterialReaderBackend; +import pgp.certificate_store.certificate.Certificate; +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.BufferedInputStream; import java.io.File; 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 5fac78c..caea4a8 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 @@ -6,12 +6,12 @@ package pgp.cert_d.backend; import pgp.cert_d.PGPCertificateDirectory; import pgp.cert_d.SpecialNames; -import pgp.cert_d.exception.BadDataException; -import pgp.cert_d.exception.BadNameException; -import pgp.certificate.Certificate; -import pgp.certificate.KeyMaterial; -import pgp.certificate.KeyMaterialMerger; -import pgp.certificate.KeyMaterialReaderBackend; +import pgp.certificate_store.certificate.Certificate; +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 java.io.IOException; import java.io.InputStream; diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java index 8d94279..ee27bf0 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java @@ -7,7 +7,7 @@ package pgp.cert_d; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend; -import pgp.cert_d.exception.BadNameException; +import pgp.certificate_store.exception.BadNameException; import java.io.File; import java.io.IOException; diff --git a/pgp-certificate-store/README.md b/pgp-certificate-store/README.md new file mode 100644 index 0000000..58f9af9 --- /dev/null +++ b/pgp-certificate-store/README.md @@ -0,0 +1,13 @@ + + +# PGP Certificate Store Definitions + +[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgp-certificate-store/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgp-certificate-store) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgp-certificate-store)](https://search.maven.org/artifact/org.pgpainless/pgp-certificate-store) + +This module contains API definitions for an OpenPGP certificate store. +A certificate store is used to store public key certificates only. diff --git a/pgp-certificate-store/build.gradle b/pgp-certificate-store/build.gradle new file mode 100644 index 0000000..f2d7d7a --- /dev/null +++ b/pgp-certificate-store/build.gradle @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id 'java-library' +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +apply plugin: 'ru.vyarus.animalsniffer' + +dependencies { + // animal sniffer for ensuring Android API compatibility + signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" + + // JUnit for testing + testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + // Logging + api "org.slf4j:slf4j-api:$slf4jVersion" + testImplementation "ch.qos.logback:logback-classic:$logbackVersion" +} + +animalsniffer { + sourceSets = [sourceSets.main] +} + +test { + useJUnitPlatform() +} 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 new file mode 100644 index 0000000..acca57e --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/PGPCertificateStore.java @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.certificate.KeyMaterialMerger; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +/** + * Interface for an OpenPGP certificate (public key) store. + */ +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 + * + * @throws IOException in case of an IO-error + * @throws BadNameException if the identifier is invalid + * @throws BadDataException if the certificate file contains invalid data + */ + Certificate getCertificate(String identifier) + throws IOException, BadNameException, BadDataException; + + /** + * Return an {@link Iterator} over all certificates in the store that contain a subkey with the given + * subkey id. + * @param subkeyId id of the subkey + * @return iterator + * + * @throws IOException in case of an IO error + * @throws BadDataException if any of the certificate files contains invalid data + */ + Iterator getCertificatesBySubkeyId(long subkeyId) + throws IOException, BadDataException; + + /** + * Insert a certificate into the store. + * If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be + * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate + * will be stored in the store and returned. + * + * This method will block until a write-lock on the store can be acquired. + * + * @param data input stream containing the new certificate instance + * @param merge callback for merging with an existing certificate instance + * @return merged certificate + * + * @throws IOException in case of an IO-error + * @throws InterruptedException in case the inserting thread gets interrupted + * @throws BadDataException if the data stream does not contain valid OpenPGP data + */ + Certificate insertCertificate(InputStream data, KeyMaterialMerger merge) + throws IOException, InterruptedException, BadDataException; + + /** + * Insert a certificate into the store. + * The certificate will be stored under the given special name instead of its fingerprint. + * + * If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be + * used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate + * will be stored in the store and returned. + * + * This method will block until a write-lock on the store can be acquired. + * + * @param specialName special name of the certificate + * @param data input stream containing the new certificate instance + * @param merge callback for merging with an existing certificate instance + * @return merged certificate or null if the store cannot be locked + * + * @throws IOException in case of an IO-error + * @throws InterruptedException if the thread is interrupted + * @throws BadDataException if the certificate file does not contain valid OpenPGP data + * @throws BadNameException if the special name is unknown + */ + Certificate insertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge) + throws IOException, InterruptedException, BadDataException, BadNameException; + + /** + * Return an {@link Iterator} containing all certificates in the store. + * The iterator will contain both certificates addressed by special names and by fingerprints. + * + * @return certificates + */ + Iterator getCertificates(); + + /** + * Return an {@link Iterator} containing all certificate fingerprints from the store. + * Note that this only includes the fingerprints of certificate primary keys, not those of subkeys. + * + * @return fingerprints + */ + Iterator getFingerprints(); +} diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java similarity index 87% rename from pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java index 969064d..d2b1d1b 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate; +package pgp.certificate_store.certificate; /** * OpenPGP certificate (public key). diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/Key.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java similarity index 91% rename from pgp-cert-d-java/src/main/java/pgp/certificate/Key.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java index 80c9636..5d6d713 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/Key.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate; +package pgp.certificate_store.certificate; /** * OpenPGP key (secret key). diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java similarity index 88% rename from pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java index 87c309e..5293edf 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterial.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java @@ -2,10 +2,11 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate; +package pgp.certificate_store.certificate; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Set; public interface KeyMaterial { @@ -36,5 +37,5 @@ public interface KeyMaterial { * @return subkeys * @throws IOException in case of an IO error */ - Set getSubkeyIds() throws IOException; + List getSubkeyIds() throws IOException; } diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialMerger.java similarity index 94% rename from pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialMerger.java index bd70bc8..20ad268 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialMerger.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialMerger.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate; +package pgp.certificate_store.certificate; import java.io.IOException; diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java similarity index 87% rename from pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java index 2ee7972..42b2bb4 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/KeyMaterialReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.certificate; +package pgp.certificate_store.certificate; -import pgp.cert_d.exception.BadDataException; +import pgp.certificate_store.exception.BadDataException; import java.io.IOException; import java.io.InputStream; diff --git a/pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/package-info.java similarity index 80% rename from pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/package-info.java index ac5b0f4..5060b18 100644 --- a/pgp-cert-d-java/src/main/java/pgp/certificate/package-info.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/package-info.java @@ -5,4 +5,4 @@ /** * General OpenPGP Certificate Storage related classes. */ -package pgp.certificate; +package pgp.certificate_store.certificate; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java similarity index 85% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java index 7dc8b7d..3bb7019 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadDataException.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadDataException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d.exception; +package pgp.certificate_store.exception; /** * The data was not a valid OpenPGP cert or key in binary format. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java similarity index 90% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java index 510804e..957126e 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/BadNameException.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/BadNameException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d.exception; +package pgp.certificate_store.exception; /** * Provided name was neither a valid fingerprint, nor a known special name. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java similarity index 89% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java index 734de49..a19aa9c 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/NotAStoreException.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/NotAStoreException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d.exception; +package pgp.certificate_store.exception; /** * The base dir cannot possibly contain a store. diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java similarity index 76% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java rename to pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java index b051b8b..302b1d2 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/exception/package-info.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/exception/package-info.java @@ -5,4 +5,4 @@ /** * Exceptions. */ -package pgp.cert_d.exception; +package pgp.certificate_store.exception; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java new file mode 100644 index 0000000..39164d4 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Abstract definitions of an OpenPGP certificate store. + */ +package pgp.certificate_store; diff --git a/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java b/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java new file mode 100644 index 0000000..e766c29 --- /dev/null +++ b/pgp-certificate-store/src/test/java/pgp/certificate_store/DummyTest.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DummyTest { + @Test + public void test() { + assertTrue(true); + } +} diff --git a/settings.gradle b/settings.gradle index 39451e2..ef5cea0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ rootProject.name = 'cert-d-java' -include 'pgp-cert-d-java', +include 'pgp-certificate-store', + 'pgp-cert-d-java', 'pgp-cert-d-java-jdbc-sqlite-lookup' From 991fea25033b7f665631e67a84f257f99cce18c5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 12 Aug 2022 15:08:33 +0200 Subject: [PATCH 29/63] Add SubkeyLookupFactory class --- .../sqlite/DatabaseSubkeyLookupFactory.java | 33 +++++++++++++++++++ .../InMemorySubkeyLookupFactory.java | 14 ++++++++ .../subkey_lookup/SubkeyLookupFactory.java | 18 ++++++++++ 3 files changed, 65 insertions(+) create mode 100644 pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java new file mode 100644 index 0000000..d2cecd3 --- /dev/null +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d.jdbc.sqlite; + +import pgp.cert_d.subkey_lookup.SubkeyLookup; +import pgp.cert_d.subkey_lookup.SubkeyLookupFactory; + +import java.io.File; +import java.io.IOException; +import java.sql.SQLException; + +/** + * Implementation of {@link SubkeyLookupFactory} which creates a SQLite-based {@link DatabaseSubkeyLookup}. + */ +public class DatabaseSubkeyLookupFactory implements SubkeyLookupFactory { + + @Override + public SubkeyLookup createFileBasedInstance(File baseDirectory) { + File databaseFile = new File(baseDirectory, "_pgpainless_subkey_map.db"); + SubkeyLookupDao dao; + try { + if (!databaseFile.exists()) { + databaseFile.createNewFile(); + } + dao = SqliteSubkeyLookupDaoImpl.forDatabaseFile(databaseFile); + } catch (SQLException | IOException e) { + throw new RuntimeException(e); + } + return new DatabaseSubkeyLookup(dao); + } +} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java new file mode 100644 index 0000000..a224c64 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d.subkey_lookup; + +import java.io.File; + +public class InMemorySubkeyLookupFactory implements SubkeyLookupFactory { + @Override + public SubkeyLookup createFileBasedInstance(File baseDirectory) { + return new InMemorySubkeyLookup(); + } +} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java new file mode 100644 index 0000000..442e9ef --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d.subkey_lookup; + +import java.io.File; + +public interface SubkeyLookupFactory { + + /** + * Create a new {@link SubkeyLookup} instance that lives in the given baseDirectory. + * + * @param baseDirectory base directory + * @return subkey lookup + */ + SubkeyLookup createFileBasedInstance(File baseDirectory); +} From 70367e98f022d0cd27a2b384af8945d5dcfb17a6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 12 Aug 2022 15:42:44 +0200 Subject: [PATCH 30/63] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78481a..51b270f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog +## 0.1.2-SNAPSHOT +- `pgp-certificate-store`: + - Rework `Certificate`, `Key` to inherit from `KeyMaterial` + - Rename `CertificateReaderBackend` to `KeyMaterialReaderBackend` + - Rename `CertificateMerger` to `KeyMaterialMerger` + - Rework `PGPCertificateStore` class +- `pgp-cert-d-java`: + - Rework `PGPCertificateDirectory` class by separating out backend logic + - Split interface into `ReadOnlyPGPCertificateDirectory` and `WritingPGPCertificateDirectory` +- `pgp-cert-d-java-jdbc-sqlite-lookup`: + - Add `DatabaseSubkeyLookupFactory` + ## 0.1.1 - Bump `slf4j` to `1.7.36` - Bump `logback` to `1.2.11` From 5e850581c01f5120710c138d07c0b8dc8036df0c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 23 Aug 2022 15:19:01 +0200 Subject: [PATCH 31/63] 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' From d050cb55167843cef94a59be8d13d311f6ec4df0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 23 Aug 2022 15:36:26 +0200 Subject: [PATCH 32/63] Execute tests on both inMemory and fileBased store backends --- .../cert_d/PGPCertificateDirectoryTest.java | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) 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 10fde1a..65cd558 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 @@ -5,18 +5,27 @@ package pgp.cert_d; import org.bouncycastle.util.io.Streams; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; 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 pgp.certificate_store.exception.NotAStoreException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; +import java.util.Set; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -134,11 +143,23 @@ public class PGPCertificateDirectoryTest { "-----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( + private static Stream provideTestSubjects() throws IOException, NotAStoreException { + PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory( new TestKeyMaterialReaderBackend()); + File tempDir = Files.createTempDirectory("pgp-cert-d-test").toFile(); + tempDir.deleteOnExit(); + PGPCertificateDirectory fileBased = PGPCertificateDirectories.fileBasedCertificateDirectory( + new TestKeyMaterialReaderBackend(), + tempDir, + new InMemorySubkeyLookup()); + + return Stream.of(inMemory, fileBased); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) throws IOException, InterruptedException, BadDataException { // Manually lock the dir assertFalse(directory.backend.getLock().isLocked()); directory.backend.getLock().lockDirectory(); @@ -153,19 +174,16 @@ public class PGPCertificateDirectoryTest { assertNotNull(inserted); } - @Test - public void getByInvalidNameFails() { - PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( - new TestKeyMaterialReaderBackend()); - + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void getByInvalidNameFails(PGPCertificateDirectory directory) { assertThrows(BadNameException.class, () -> directory.getBySpecialName("invalid")); } - @Test - public void testInsertAndGetSingleCert() throws BadDataException, IOException, InterruptedException, BadNameException { - PGPCertificateDirectory directory = PGPCertificateDirectories.inMemoryCertificateDirectory( - new TestKeyMaterialReaderBackend()); - + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testInsertAndGetSingleCert(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException, BadNameException { assertNull(directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)); @@ -182,11 +200,10 @@ public class PGPCertificateDirectoryTest { 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()); - + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testInsertAndGetTrustRootAndCert(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException { assertNull(directory.getTrustRoot()); KeyMaterial trustRootMaterial = directory.insertTrustRoot( @@ -203,9 +220,14 @@ public class PGPCertificateDirectoryTest { directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + Set expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP)); + + Set actual = new HashSet<>(); Iterator fingerprints = directory.fingerprints(); - assertEquals(RON_FP, fingerprints.next()); - assertEquals(CEDRIC_FP, fingerprints.next()); + actual.add(fingerprints.next()); + actual.add(fingerprints.next()); assertFalse(fingerprints.hasNext()); + + assertEquals(expected, actual); } } From 27f4598437ad7a26b1ed21df3512e65ffeae94c7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 13:04:28 +0200 Subject: [PATCH 33/63] Add back support for getXIfChanged(Y, tag) --- pgp-cert-d-java/build.gradle | 2 +- .../pgp/cert_d/PGPCertificateDirectory.java | 32 +++++ .../cert_d/PGPCertificateStoreAdapter.java | 10 ++ .../ReadOnlyPGPCertificateDirectory.java | 9 ++ .../FileBasedCertificateDirectoryBackend.java | 74 +++++++--- .../InMemoryCertificateDirectoryBackend.java | 39 +++++- .../cert_d/PGPCertificateDirectoryTest.java | 128 ++++++++++++++++-- .../{ => dummy}/TestKeyMaterialMerger.java | 2 +- .../TestKeyMaterialReaderBackend.java | 70 ++-------- .../PGPCertificateStore.java | 19 ++- .../certificate/Certificate.java | 56 +++++++- .../certificate_store/certificate/Key.java | 57 +++++++- .../certificate/KeyMaterial.java | 22 ++- .../certificate/KeyMaterialReaderBackend.java | 2 +- version.gradle | 2 +- 15 files changed, 423 insertions(+), 101 deletions(-) rename pgp-cert-d-java/src/test/java/pgp/cert_d/{ => dummy}/TestKeyMaterialMerger.java (94%) rename pgp-cert-d-java/src/test/java/pgp/cert_d/{ => dummy}/TestKeyMaterialReaderBackend.java (58%) diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index a4b97b3..35032ca 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'ru.vyarus.animalsniffer' dependencies { // animal sniffer for ensuring Android API compatibility - signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" + signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:8.0.0_r2@signature" // JUnit testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" 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 2a0d8f5..668bd2f 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 @@ -33,6 +33,16 @@ public class PGPCertificateDirectory return backend.readByFingerprint(fingerprint); } + @Override + public Certificate getByFingerprintIfChanged(String fingerprint, long tag) + throws IOException, BadNameException, BadDataException { + if (tag != backend.getTagForFingerprint(fingerprint)) { + return getByFingerprint(fingerprint); + } + return null; + } + + @Override public Certificate getBySpecialName(String specialName) throws BadNameException, BadDataException, IOException { @@ -43,6 +53,15 @@ public class PGPCertificateDirectory return null; } + @Override + public Certificate getBySpecialNameIfChanged(String specialName, long tag) + throws IOException, BadNameException, BadDataException { + if (tag != backend.getTagForSpecialName(specialName)) { + return getBySpecialName(specialName); + } + return null; + } + @Override public Certificate getTrustRootCertificate() throws IOException, BadDataException { @@ -53,6 +72,15 @@ public class PGPCertificateDirectory } } + @Override + public Certificate getTrustRootCertificateIfChanged(long tag) throws IOException, BadDataException { + try { + return getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag); + } catch (BadNameException e) { + throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is an implementation MUST"); + } + } + @Override public Iterator items() { return backend.readItems(); @@ -179,6 +207,10 @@ public class PGPCertificateDirectory Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException; + + Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException; + + Long getTagForSpecialName(String specialName) throws BadNameException, IOException; } public interface LockingMechanism { diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java index 5d95e6f..972d3aa 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/PGPCertificateStoreAdapter.java @@ -37,6 +37,16 @@ public class PGPCertificateStoreAdapter implements PGPCertificateStore { } } + @Override + public Certificate getCertificateIfChanged(String identifier, Long tag) + throws IOException, BadNameException, BadDataException { + if (SpecialNames.lookupSpecialName(identifier) != null) { + return directory.getBySpecialNameIfChanged(identifier, tag); + } else { + return directory.getByFingerprintIfChanged(identifier.toLowerCase(), tag); + } + } + @Override public Iterator getCertificatesBySubkeyId(long subkeyId) throws IOException, BadDataException { 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 4c9fabc..8c5efdc 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 @@ -16,12 +16,21 @@ public interface ReadOnlyPGPCertificateDirectory { Certificate getTrustRootCertificate() throws IOException, BadDataException; + Certificate getTrustRootCertificateIfChanged(long tag) + throws IOException, BadDataException; + Certificate getByFingerprint(String fingerprint) throws IOException, BadNameException, BadDataException; + Certificate getByFingerprintIfChanged(String fingerprint, long tag) + throws IOException, BadNameException, BadDataException; + Certificate getBySpecialName(String specialName) throws IOException, BadNameException, BadDataException; + Certificate getBySpecialNameIfChanged(String specialName, long tag) + throws IOException, BadNameException, BadDataException; + Iterator items(); Iterator fingerprints(); 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 5decae2..e5750e8 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 @@ -7,6 +7,7 @@ package pgp.cert_d.backend; import pgp.cert_d.PGPCertificateDirectory; import pgp.cert_d.SpecialNames; 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; @@ -25,6 +26,9 @@ import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -160,10 +164,12 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec return null; } + long tag = getTagForFingerprint(fingerprint); + FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = reader.read(bufferedIn).asCertificate(); + Certificate certificate = reader.read(bufferedIn, tag).asCertificate(); if (!certificate.getFingerprint().equals(fingerprint)) { // TODO: Figure out more suitable exception throw new BadDataException(); @@ -179,9 +185,11 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec return null; } + long tag = getTagForSpecialName(specialName); + FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - KeyMaterial keyMaterial = reader.read(bufferedIn); + KeyMaterial keyMaterial = reader.read(bufferedIn, tag); return keyMaterial; } @@ -214,7 +222,8 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec @Override Certificate get() throws BadDataException { try { - Certificate certificate = reader.read(new FileInputStream(certFile)).asCertificate(); + long tag = getTag(certFile); + Certificate certificate = reader.read(new FileInputStream(certFile), tag).asCertificate(); if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) { throw new BadDataException(); } @@ -246,7 +255,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec @Override public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException { - KeyMaterial newCertificate = reader.read(data); + KeyMaterial newCertificate = reader.read(data, null); KeyMaterial existingCertificate; File certFile; try { @@ -256,18 +265,22 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec throw new BadDataException(); } - if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + if (existingCertificate != null) { newCertificate = merge.merge(newCertificate, existingCertificate); } - writeToFile(newCertificate.getInputStream(), certFile); - + long tag = writeToFile(newCertificate.getInputStream(), certFile); + if (newCertificate instanceof Key) { + newCertificate = new Key((Key) newCertificate, tag); + } else { + newCertificate = new Certificate((Certificate) newCertificate, tag); + } return newCertificate; } @Override public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException { - KeyMaterial newCertificate = reader.read(data); + KeyMaterial newCertificate = reader.read(data, null); Certificate existingCertificate; File certFile; try { @@ -277,18 +290,17 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec throw new BadDataException(); } - if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + if (existingCertificate != null) { newCertificate = merge.merge(newCertificate, existingCertificate); } - writeToFile(newCertificate.getInputStream(), certFile); - - return newCertificate.asCertificate(); + long tag = writeToFile(newCertificate.getInputStream(), certFile); + return new Certificate(newCertificate.asCertificate(), tag); } @Override public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException { - KeyMaterial newCertificate = reader.read(data); + KeyMaterial newCertificate = reader.read(data, null); KeyMaterial existingCertificate; File certFile; try { @@ -298,16 +310,41 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec throw new BadDataException(); } - if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) { + if (existingCertificate != null) { newCertificate = merge.merge(newCertificate, existingCertificate); } - writeToFile(newCertificate.getInputStream(), certFile); - - return newCertificate.asCertificate(); + long tag = writeToFile(newCertificate.getInputStream(), certFile); + return new Certificate(newCertificate.asCertificate(), tag); } - private void writeToFile(InputStream inputStream, File certFile) + @Override + public Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException { + File file = resolver.getCertFileByFingerprint(fingerprint); + return getTag(file); + } + + @Override + public Long getTagForSpecialName(String specialName) throws BadNameException, IOException { + File file = resolver.getCertFileBySpecialName(specialName); + return getTag(file); + } + + private Long getTag(File file) throws IOException { + if (!file.exists()) { + throw new IllegalArgumentException("File MUST exist."); + } + Path path = file.toPath(); + BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); + + // On UNIX file systems, for example, fileKey() will return the device ID and inode + int fileId = attrs.fileKey().hashCode(); + long lastMod = attrs.lastModifiedTime().toMillis(); + + return lastMod + (11L * fileId); + } + + private long writeToFile(InputStream inputStream, File certFile) throws IOException { certFile.getParentFile().mkdirs(); if (!certFile.exists() && !certFile.createNewFile()) { @@ -324,6 +361,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec inputStream.close(); fileOut.close(); + return getTag(certFile); } public static class FilenameResolver { 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 26c6e2b..60069c8 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 @@ -7,6 +7,7 @@ package pgp.cert_d.backend; import pgp.cert_d.PGPCertificateDirectory; import pgp.cert_d.SpecialNames; 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; @@ -91,7 +92,7 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect @Override public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException { - KeyMaterial update = reader.read(data); + KeyMaterial update = reader.read(data, null); KeyMaterial existing = null; try { existing = readBySpecialName(SpecialNames.TRUST_ROOT); @@ -100,6 +101,11 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect throw new RuntimeException(e); } KeyMaterial merged = merge.merge(update, existing); + if (merged instanceof Key) { + merged = new Key((Key) merged, System.currentTimeMillis()); + } else { + merged = new Certificate((Certificate) merged, System.currentTimeMillis()); + } keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged); return merged; } @@ -108,9 +114,10 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect @Override public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException { - KeyMaterial update = reader.read(data); + KeyMaterial update = reader.read(data, null); Certificate existing = readByFingerprint(update.getFingerprint()); Certificate merged = merge.merge(update, existing).asCertificate(); + merged = new Certificate(merged, System.currentTimeMillis()); certificateFingerprintMap.put(update.getFingerprint(), merged); return merged; } @@ -118,10 +125,36 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect @Override public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException { - KeyMaterial keyMaterial = reader.read(data); + KeyMaterial keyMaterial = reader.read(data, null); KeyMaterial existing = readBySpecialName(specialName); KeyMaterial merged = merge.merge(keyMaterial, existing); + if (merged instanceof Key) { + merged = new Key((Key) merged, System.currentTimeMillis()); + } else { + merged = new Certificate((Certificate) merged, System.currentTimeMillis()); + } keyMaterialSpecialNameMap.put(specialName, merged); return merged.asCertificate(); } + + @Override + public Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException { + Certificate certificate = certificateFingerprintMap.get(fingerprint); + if (certificate == null) { + return null; + } + return certificate.getTag(); + } + + @Override + public Long getTagForSpecialName(String specialName) throws BadNameException, IOException { + if (SpecialNames.lookupSpecialName(specialName) == null) { + throw new BadNameException("Invalid special name " + specialName); + } + KeyMaterial tagged = keyMaterialSpecialNameMap.get(specialName); + if (tagged == null) { + return null; + } + return tagged.getTag(); + } } 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 65cd558..a3803c2 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 @@ -5,12 +5,17 @@ package pgp.cert_d; import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; 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.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.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; @@ -18,6 +23,7 @@ import pgp.certificate_store.exception.NotAStoreException; import java.io.ByteArrayInputStream; 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; @@ -30,6 +36,7 @@ import java.util.stream.Stream; 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.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; @@ -37,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class PGPCertificateDirectoryTest { + @SuppressWarnings("CharsetObjectCanBeUsed") private static final Charset UTF8 = Charset.forName("UTF8"); private static final String HARRY_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + @@ -143,7 +151,10 @@ public class PGPCertificateDirectoryTest { "-----END PGP PUBLIC KEY BLOCK-----\n"; private static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021"; - private static Stream provideTestSubjects() throws IOException, NotAStoreException { + private static final KeyMaterialMerger merger = new TestKeyMaterialMerger(); + + private static Stream provideTestSubjects() + throws IOException, NotAStoreException { PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory( new TestKeyMaterialReaderBackend()); @@ -159,18 +170,19 @@ public class PGPCertificateDirectoryTest { @ParameterizedTest @MethodSource("provideTestSubjects") - public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) throws IOException, InterruptedException, BadDataException { + public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) + throws IOException, InterruptedException, BadDataException { // 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()); + Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); assertNull(inserted); directory.backend.getLock().releaseDirectory(); - inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger()); + inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); assertNotNull(inserted); } @@ -188,7 +200,7 @@ public class PGPCertificateDirectoryTest { ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)); - Certificate certificate = directory.insert(bytesIn, new TestKeyMaterialMerger()); + Certificate certificate = directory.insert(bytesIn, merger); assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match"); Certificate get = directory.getByFingerprint(CEDRIC_FP); @@ -207,7 +219,7 @@ public class PGPCertificateDirectoryTest { assertNull(directory.getTrustRoot()); KeyMaterial trustRootMaterial = directory.insertTrustRoot( - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), new TestKeyMaterialMerger()); + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); assertNotNull(trustRootMaterial); assertTrue(trustRootMaterial instanceof Key); assertEquals(HARRY_FP, trustRootMaterial.getFingerprint()); @@ -217,8 +229,8 @@ public class PGPCertificateDirectoryTest { 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()); + directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), merger); + directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); Set expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP)); @@ -230,4 +242,104 @@ public class PGPCertificateDirectoryTest { assertEquals(expected, actual); } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testGetTrustRootIfChanged(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException { + KeyMaterial trustRootMaterial = directory.insertTrustRoot( + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + + assertNotNull(trustRootMaterial.getTag()); + Long tag = trustRootMaterial.getTag(); + assertNull(directory.getTrustRootCertificateIfChanged(tag)); + assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1)); + + Long oldTag = tag; + // "update" key + trustRootMaterial = directory.insertTrustRoot( + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + tag = trustRootMaterial.getTag(); + + assertNotEquals(oldTag, tag); + assertNotNull(directory.getTrustRootCertificateIfChanged(oldTag)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testGetBySpecialNameIfChanged(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException, BadNameException { + KeyMaterial specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + + assertNotNull(specialName.getTag()); + Long tag = specialName.getTag(); + assertNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag)); + assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag + 1)); + + Long oldTag = tag; + // "update" key + specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, + new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + tag = specialName.getTag(); + + assertNotEquals(oldTag, tag); + assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, oldTag)); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void testGetByFingerprintIfChanged(PGPCertificateDirectory directory) + throws BadDataException, IOException, InterruptedException, BadNameException { + Certificate certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + Long tag = certificate.getTag(); + assertNotNull(tag); + + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1)); + + Long oldTag = tag; + // "update" cert + certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + tag = certificate.getTag(); + + assertNotEquals(oldTag, tag); + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag)); + } + + @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(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + Long tag = certificate.getTag(); + assertNotNull(tag); + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); + + Long oldTag = tag; + + // 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.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)); + } } 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/dummy/TestKeyMaterialMerger.java similarity index 94% rename from pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java rename to pgp-cert-d-java/src/test/java/pgp/cert_d/dummy/TestKeyMaterialMerger.java index 6810f0b..8ca24c3 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialMerger.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/dummy/TestKeyMaterialMerger.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.dummy; import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterialMerger; 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/dummy/TestKeyMaterialReaderBackend.java similarity index 58% rename from pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java rename to pgp-cert-d-java/src/test/java/pgp/cert_d/dummy/TestKeyMaterialReaderBackend.java index d653837..2ef392d 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeyMaterialReaderBackend.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/dummy/TestKeyMaterialReaderBackend.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.cert_d; +package pgp.cert_d.dummy; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; @@ -33,24 +33,22 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend { KeyFingerPrintCalculator fpCalc = new BcKeyFingerprintCalculator(); @Override - public KeyMaterial read(InputStream data) throws IOException, BadDataException { + public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException { ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.pipeAll(data, out); try { - Key key = readKey(new ByteArrayInputStream(out.toByteArray())); - return key; + return readKey(new ByteArrayInputStream(out.toByteArray()), tag); } catch (IOException | PGPException e) { try { - Certificate certificate = readCertificate(new ByteArrayInputStream(out.toByteArray())); - return certificate; + return readCertificate(new ByteArrayInputStream(out.toByteArray()), tag); } catch (IOException e1) { throw new BadDataException(); } } } - private Key readKey(InputStream inputStream) throws IOException, PGPException { + private Key readKey(InputStream inputStream, Long tag) throws IOException, PGPException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); Streams.pipeAll(inputStream, buffer); inputStream.close(); @@ -60,64 +58,21 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend { PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(decoderStream, fpCalc); PGPPublicKeyRing cert = extractCert(secretKeys); ByteArrayInputStream encoded = new ByteArrayInputStream(cert.getEncoded()); - Certificate certificate = readCertificate(encoded); + Certificate certificate = readCertificate(encoded, tag); - 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(); - } - }; + return new Key(buffer.toByteArray(), certificate, tag); } - private Certificate readCertificate(InputStream inputStream) throws IOException { + private Certificate readCertificate(InputStream inputStream, Long tag) 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); - } - }; + String fingerprint = Hex.toHexString(cert.getPublicKey().getFingerprint()).toLowerCase(); + List subKeyIds = getSubkeyIds(cert); + return new Certificate(buffer.toByteArray(), fingerprint, subKeyIds, tag); } private PGPPublicKeyRing extractCert(PGPSecretKeyRing secretKeys) { @@ -126,8 +81,7 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend { while (publicKeyIterator.hasNext()) { publicKeyList.add(publicKeyIterator.next()); } - PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList); - return publicKeyRing; + return new PGPPublicKeyRing(publicKeyList); } private static List getSubkeyIds(PGPKeyRing keyRing) { 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 acca57e..d0915b0 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 @@ -32,6 +32,23 @@ public interface PGPCertificateStore { Certificate getCertificate(String identifier) throws IOException, BadNameException, BadDataException; + /** + * Return the certificate that matches the given identifier, but only if it has been changed. + * Whether it has been changed is determined by calculating the tag in the directory + * (e.g. by looking at the inode and last modification date) and comparing the result with the tag provided by + * the caller. + * + * @param identifier certificate identifier + * @param tag tag by the caller + * @return certificate if it has been changed, null otherwise + * + * @throws IOException in case of an IO-error + * @throws BadNameException if the identifier is invalid + * @throws BadDataException if the certificate file contains invalid data + */ + Certificate getCertificateIfChanged(String identifier, Long tag) + throws IOException, BadNameException, BadDataException; + /** * Return an {@link Iterator} over all certificates in the store that contain a subkey with the given * subkey id. @@ -42,7 +59,7 @@ public interface PGPCertificateStore { * @throws BadDataException if any of the certificate files contains invalid data */ Iterator getCertificatesBySubkeyId(long subkeyId) - throws IOException, BadDataException; + throws IOException, BadDataException; /** * Insert a certificate into the store. diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java index d2b1d1b..175f739 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Certificate.java @@ -4,13 +4,67 @@ package pgp.certificate_store.certificate; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; + /** * OpenPGP certificate (public key). */ -public abstract class Certificate implements KeyMaterial { +public class Certificate implements KeyMaterial { + + private final byte[] bytes; + private final String fingerprint; + private final List subkeyIds; + private final Long tag; + + /** + * Certificate constructor. + * + * @param bytes encoding of the certificate + * @param fingerprint fingerprint (lowercase hex characters) + * @param subkeyIds list of subkey ids + * @param tag tag + */ + public Certificate(byte[] bytes, String fingerprint, List subkeyIds, Long tag) { + this.bytes = bytes; + this.fingerprint = fingerprint; + this.subkeyIds = subkeyIds; + this.tag = tag; + } + + /** + * Copy constructor to assign a new tag to the {@link Certificate}. + * + * @param cert certificate + * @param tag tag + */ + public Certificate(Certificate cert, Long tag) { + this(cert.bytes, cert.fingerprint, cert.subkeyIds, tag); + } + + @Override + public String getFingerprint() { + return fingerprint; + } @Override public Certificate asCertificate() { return this; } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } + + @Override + public Long getTag() { + return tag; + } + + @Override + public List getSubkeyIds() { + return subkeyIds; + } } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java index 5d6d713..a2a4aef 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/Key.java @@ -4,21 +4,74 @@ package pgp.certificate_store.certificate; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; + /** * OpenPGP key (secret key). */ -public abstract class Key implements KeyMaterial { +public class Key implements KeyMaterial { + + private final byte[] bytes; + private final Certificate certificate; + private final Long tag; + + /** + * Key constructor. + * + * @param bytes encoding of the key + * @param certificate associated certificate + * @param tag tag + */ + public Key(byte[] bytes, Certificate certificate, Long tag) { + this.bytes = bytes; + this.certificate = certificate; + this.tag = tag; + } + + /** + * Copy constructor to change the tag of both the {@link Key} and its {@link Certificate}. + * + * @param key key + * @param tag tag + */ + public Key(Key key, Long tag) { + this(key.bytes, new Certificate(key.certificate, tag), tag); + } /** * Return the certificate part of this OpenPGP key. * * @return OpenPGP certificate */ - public abstract Certificate getCertificate(); + public Certificate getCertificate() { + return new Certificate(certificate, getTag()); + } + + @Override + public String getFingerprint() { + return certificate.getFingerprint(); + } @Override public Certificate asCertificate() { return getCertificate(); } + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } + + @Override + public Long getTag() { + return tag; + } + + @Override + public List getSubkeyIds() { + return certificate.getSubkeyIds(); + } + } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java index 5293edf..1438b47 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterial.java @@ -4,7 +4,6 @@ package pgp.certificate_store.certificate; -import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Set; @@ -19,23 +18,34 @@ public interface KeyMaterial { */ String getFingerprint(); + /** + * Return the {@link Certificate} belonging to this key material. + * If this is already a {@link Certificate}, return this. + * If this is a {@link Key}, extract the {@link Certificate} and return it. + * + * @return certificate + */ Certificate asCertificate(); /** * Return an {@link InputStream} of the binary representation of the secret key. * * @return input stream - * @throws IOException in case of an IO error */ - InputStream getInputStream() throws IOException; + InputStream getInputStream(); - String getTag() throws IOException; + /** + * Return the tag belonging to this key material. + * The tag can be used to keep an application cache in sync with what is in the directory. + * + * @return tag + */ + Long getTag(); /** * Return a {@link Set} containing key-ids of subkeys. * * @return subkeys - * @throws IOException in case of an IO error */ - List getSubkeyIds() throws IOException; + List getSubkeyIds(); } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java index 42b2bb4..42b3e2c 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java @@ -20,5 +20,5 @@ public interface KeyMaterialReaderBackend { * @throws IOException in case of an IO error * @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate */ - KeyMaterial read(InputStream data) throws IOException, BadDataException; + KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException; } diff --git a/version.gradle b/version.gradle index 0287d07..1951785 100644 --- a/version.gradle +++ b/version.gradle @@ -6,7 +6,7 @@ allprojects { ext { shortVersion = '0.1.2' isSnapshot = true - minAndroidSdk = 10 + minAndroidSdk = 26 javaSourceCompatibility = 1.8 bouncycastleVersion = '1.71' slf4jVersion = '1.7.36' From f38218963874675f93a474367ded32df51ce9687 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 13:58:18 +0200 Subject: [PATCH 34/63] DatabaseSubkeyLookupFactory: Make database name configurable --- .../jdbc/sqlite/DatabaseSubkeyLookupFactory.java | 12 +++++++++++- .../cert_d/jdbc/sqlite/SqliteSubkeyLookupTest.java | 14 +++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java index d2cecd3..d0a259a 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/DatabaseSubkeyLookupFactory.java @@ -16,9 +16,19 @@ import java.sql.SQLException; */ public class DatabaseSubkeyLookupFactory implements SubkeyLookupFactory { + private String databaseName; + + public DatabaseSubkeyLookupFactory() { + this("_pgpainless_subkey_map.db"); + } + + public DatabaseSubkeyLookupFactory(String databaseName) { + this.databaseName = databaseName; + } + @Override public SubkeyLookup createFileBasedInstance(File baseDirectory) { - File databaseFile = new File(baseDirectory, "_pgpainless_subkey_map.db"); + File databaseFile = new File(baseDirectory, databaseName); SubkeyLookupDao dao; try { if (!databaseFile.exists()) { diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookupTest.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookupTest.java index b71337c..5539d12 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookupTest.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookupTest.java @@ -22,15 +22,15 @@ import org.junit.jupiter.api.Test; public class SqliteSubkeyLookupTest { - private File databaseFile; + private File tempDir; private DatabaseSubkeyLookup lookup; @BeforeEach - public void setupLookup() throws IOException, SQLException { - databaseFile = Files.createTempFile("pgp.cert.d-", "lookup.db").toFile(); - databaseFile.createNewFile(); - databaseFile.deleteOnExit(); - lookup = new DatabaseSubkeyLookup(SqliteSubkeyLookupDaoImpl.forDatabaseFile(databaseFile)); + public void setupLookup() throws IOException { + tempDir = Files.createTempDirectory("pgp.cert.d").toFile(); + tempDir.deleteOnExit(); + lookup = (DatabaseSubkeyLookup) new DatabaseSubkeyLookupFactory() + .createFileBasedInstance(tempDir); } @Test @@ -55,7 +55,7 @@ public class SqliteSubkeyLookupTest { assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getCertificateFingerprintsForSubkeyId(1337)); // do the lookup using a second db instance on the same file - DatabaseSubkeyLookup secondInstance = new DatabaseSubkeyLookup(SqliteSubkeyLookupDaoImpl.forDatabaseFile(databaseFile)); + DatabaseSubkeyLookup secondInstance = (DatabaseSubkeyLookup) new DatabaseSubkeyLookupFactory().createFileBasedInstance(tempDir); assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), secondInstance.getCertificateFingerprintsForSubkeyId(1337)); } From a956aec5fc9a08159f6b2ee3757a63ef75653e92 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 14:09:20 +0200 Subject: [PATCH 35/63] Add documentation to BaseDirectoryProvider --- .../main/java/pgp/cert_d/BaseDirectoryProvider.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java index 0a24084..386a5c7 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java @@ -6,6 +6,16 @@ package pgp.cert_d; import java.io.File; +/** + * Provider class that is responsible for resolving the pgp.cert.d base directory of the system. + * The result can be overwritten by setting the
PGP_CERT_D
environment variable. + * If this variable is not set, the system-specific default directory will be returned. + * + * On Windows systems, this is
%APPDATA%\pgp.cert.d
. + * On Linux systems it is either
$XDG_DATA_HOME/pgp.cert.d
or, if
$XDG_DATA_HOME
is not set, + * it is
$HOME/.local/share/pgp.cert.d
+ * On Mac systems it is
$HOME/Library/Application Support/pgp.cert.d
. + */ public class BaseDirectoryProvider { public static File getDefaultBaseDir() { From 662f96ff7727edee9470e0c5e8e927b607227411 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 14:53:47 +0200 Subject: [PATCH 36/63] Add javadoc for interfaces --- .../pgp/cert_d/PGPCertificateDirectory.java | 105 ++++++++++++++++++ .../ReadOnlyPGPCertificateDirectory.java | 86 ++++++++++++++ .../WritingPGPCertificateDirectory.java | 87 +++++++++++++++ .../certificate/KeyMaterialReaderBackend.java | 1 + 4 files changed, 279 insertions(+) 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 668bd2f..3a3a9d7 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 @@ -17,12 +17,23 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +/** + * Implementation of the Shared PGP Certificate Directory. + * + * @see Shared PGP Certificate Directory Specification + */ public class PGPCertificateDirectory implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory, SubkeyLookup { final Backend backend; final SubkeyLookup subkeyLookup; + /** + * Constructor for a PGP certificate directory. + * + * @param backend storage backend + * @param subkeyLookup subkey lookup mechanism to map subkey-ids to certificates + */ public PGPCertificateDirectory(Backend backend, SubkeyLookup subkeyLookup) { this.backend = backend; this.subkeyLookup = subkeyLookup; @@ -189,30 +200,119 @@ public class PGPCertificateDirectory subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds); } + /** + * Storage backend. + */ public interface Backend { + /** + * Get the locking mechanism to write-lock the backend. + * + * @return lock + */ LockingMechanism getLock(); + /** + * Read a {@link Certificate} by its OpenPGP fingerprint. + * + * @param fingerprint fingerprint + * @return certificate + * + * @throws BadNameException if the fingerprint is malformed + * @throws IOException in case of an IO error + * @throws BadDataException if the certificate contains bad data + */ Certificate readByFingerprint(String fingerprint) throws BadNameException, IOException, BadDataException; + /** + * Read a {@link Certificate} or {@link pgp.certificate_store.certificate.Key} by the given special name. + * + * @param specialName special name + * @return certificate or key + * + * @throws BadNameException if the special name is not known + * @throws IOException in case of an IO error + * @throws BadDataException if the certificate contains bad data + */ KeyMaterial readBySpecialName(String specialName) throws BadNameException, IOException, BadDataException; + /** + * Return an {@link Iterator} of all {@link Certificate Certificates} in the store, except for certificates + * stored under a special name. + * + * @return iterator + */ Iterator readItems(); + /** + * Insert a {@link pgp.certificate_store.certificate.Key} or {@link Certificate} as trust-root. + * + * @param data input stream containing the key material + * @param merge callback to merge the key material with existing key material + * @return merged or inserted key material + * + * @throws BadDataException if the data stream or existing key material contains bad data + * @throws IOException in case of an IO error + */ KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException; + /** + * Insert a {@link Certificate} identified by its fingerprint into the directory. + * + * @param data input stream containing the certificate data + * @param merge callback to merge the certificate with existing key material + * @return merged or inserted certificate + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or existing certificate contains bad data + */ Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException; + /** + * Insert a {@link pgp.certificate_store.certificate.Key} or {@link Certificate} under the given special name. + * + * @param specialName special name to identify the key material with + * @param data data stream containing the key or certificate + * @param merge callback to merge the key/certificate with existing key material + * @return certificate component of the merged or inserted key material + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or existing key material contains bad data + * @throws BadNameException if the special name is not known + */ Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException; + /** + * Calculate the tag of the certificate with the given fingerprint. + * + * @param fingerprint fingerprint + * @return tag + * + * @throws BadNameException if the fingerprint is malformed + * @throws IOException in case of an IO error + * @throws IllegalArgumentException if the certificate does not exist + */ Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException; + /** + * Calculate the tag of the certificate identified by the given special name. + * + * @param specialName special name + * @return tag + * + * @throws BadNameException if the special name is not known + * @throws IOException in case of an IO error + * @throws IllegalArgumentException if the certificate or key does not exist + */ Long getTagForSpecialName(String specialName) throws BadNameException, IOException; } + /** + * Interface for a write-locking mechanism. + */ public interface LockingMechanism { /** @@ -234,6 +334,11 @@ public class PGPCertificateDirectory */ boolean tryLockDirectory() throws IOException; + /** + * Return true if the lock is in locked state. + * + * @return true if locked + */ boolean isLocked(); /** 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 8c5efdc..0b1416c 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 @@ -11,27 +11,113 @@ import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.util.Iterator; +/** + * Interface for a read-only OpenPGP certificate directory. + */ 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 + */ Certificate getTrustRootCertificate() throws IOException, BadDataException; + /** + * Get the trust-root certificate if it has changed. + * This method uses the
tag
to calculate if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
null
is returned. + * Otherwise. the changed certificate is returned. + * + * @param tag tag + * @return changed certificate, or null if the certificate is unchanged or not found. + * + * @throws IOException in case of an IO error + * @throws BadDataException if the certificate contains bad data + */ 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 + * + * @throws IOException in case of an IO error + * @throws BadNameException if the fingerprint is malformed + * @throws BadDataException if the certificate contains bad data + */ Certificate getByFingerprint(String fingerprint) throws IOException, BadNameException, BadDataException; + /** + * Get the certificate identified by the given fingerprint if it has changed. + * This method uses the
tag
to calculate, if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
null
is returned. + * Otherwise, the changed certificate is returned. + * + * @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 + * + * @throws IOException in case of an IO error + * @throws BadNameException if the fingerprint is malformed + * @throws BadDataException if the certificate contains bad data + */ 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 + * + * @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 + */ Certificate getBySpecialName(String specialName) throws IOException, BadNameException, BadDataException; + /** + * Get the certificate identified by the given special name or null, if it has not been changed. + * This method uses the
tag
to calculate, if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
null
is returned. + * Otherwise, the changed certificate is returned. + * + * @param specialName special name + * @param tag tag + * @return certificate or null + * + * @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 + */ Certificate getBySpecialNameIfChanged(String specialName, long tag) throws IOException, BadNameException, BadDataException; + /** + * Get all certificates in the directory, except for certificates which are stored by special name. + * + * @return iterator of certificates + */ Iterator items(); + /** + * Get the fingerprints of all certificates in the directory, except for certificates which are stored by + * special name. + * + * @return iterator of fingerprints + */ Iterator fingerprints(); } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java index 2857165..e5371fc 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/WritingPGPCertificateDirectory.java @@ -13,26 +13,113 @@ import pgp.certificate_store.exception.BadNameException; import java.io.IOException; import java.io.InputStream; +/** + * Interface for a writing OpenPGP certificate directory. + */ public interface WritingPGPCertificateDirectory { + /** + * Return the certificate or key identified by the special name
trust-root
. + * + * @return trust-root key or certificate + * + * @throws IOException in case of an IO error + * @throws BadDataException if the certificate contains bad data + */ KeyMaterial getTrustRoot() throws IOException, BadDataException; + /** + * Insert a key or certificate under the special name
trust-root
. + * This method blocks until the key material has been written. + * + * @param data input stream containing the key or certificate + * @param merge key material merger to merge the key or certificate with existing key material + * @return the merged or inserted key or certificate + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or the existing trust-root key material contains bad data + * @throws InterruptedException if the thread is interrupted + */ KeyMaterial insertTrustRoot(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, InterruptedException; + /** + * Insert a key or certificate under the special name
trust-root
. + * Contrary to {@link #insertTrustRoot(InputStream, KeyMaterialMerger)}, this method does not block. + * Instead, it returns null if the write-lock cannot be obtained. + * + * @param data input stream containing the key or certificate + * @param merge key material merger to merge the key or certificate with existing key material + * @return the merged or inserted key or certificate, or null if the write-lock cannot be obtained + * + * @throws IOException in case of an IO error + * @throws BadDataException if the thread is interrupted + */ KeyMaterial tryInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException; + /** + * Insert a certificate identified by its fingerprint. + * This method blocks until the certificate has been written. + * + * @param data input stream containing the certificate data + * @param merge merge callback to merge the certificate with existing certificate material + * @return the merged or inserted certificate + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or existing certificate contains bad data + * @throws InterruptedException if the thread is interrupted + */ Certificate insert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, InterruptedException; + /** + * Insert a certificate identified by its fingerprint. + * Contrary to {@link #insert(InputStream, KeyMaterialMerger)}, this method does not block. + * Instead, it returns null if the write-lock cannot be obtained. + * + * @param data input stream containing the certificate data + * @param merge merge callback to merge the certificate with existing certificate material + * @return the merged or inserted certificate + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or existing certificate contains bad data + */ Certificate tryInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException; + /** + * Insert a certificate or key under the given special name. + * This method blocks until the certificate/key has been written. + * + * @param specialName special name under which the key material shall be inserted + * @param data input stream containing the key/certificate data + * @param merge callback to merge the key/certificate with existing key material + * @return certificate component of the merged or inserted key material data + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or the existing certificate contains bad data + * @throws BadNameException if the special name is not known + * @throws InterruptedException if the thread is interrupted + */ Certificate insertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException, InterruptedException; + /** + * Insert a certificate or key under the given special name. + * Contrary to {@link #insertWithSpecialName(String, InputStream, KeyMaterialMerger)}, this method does not block. + * Instead, it returns null if the write-lock cannot be obtained. + * + * @param specialName special name under which the key material shall be inserted + * @param data input stream containing the key material + * @param merge callback to merge the key/certificate with existing key material + * @return certificate component of the merged or inserted key material + * + * @throws IOException in case of an IO error + * @throws BadDataException if the data stream or existing key material contains bad data + * @throws BadNameException if the special name is not known + */ Certificate tryInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException; diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java index 42b3e2c..c921d64 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/certificate/KeyMaterialReaderBackend.java @@ -15,6 +15,7 @@ public interface KeyMaterialReaderBackend { * Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}. * * @param data input stream containing the binary representation of the key. + * @param tag tag for the key material. Might be null. * @return key or certificate object * * @throws IOException in case of an IO error From dee2ea88a73db9f8c9eb3a7470b909e6290ff0cc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 15:00:47 +0200 Subject: [PATCH 37/63] Fix animalsniffer signature --- pgp-cert-d-java/build.gradle | 2 +- pgp-certificate-store/build.gradle | 2 +- version.gradle | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pgp-cert-d-java/build.gradle b/pgp-cert-d-java/build.gradle index 35032ca..d59867d 100644 --- a/pgp-cert-d-java/build.gradle +++ b/pgp-cert-d-java/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'ru.vyarus.animalsniffer' dependencies { // animal sniffer for ensuring Android API compatibility - signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:8.0.0_r2@signature" + signature "net.sf.androidscents.signature:android-api-level-${animalsnifferSignatureVersion}@signature" // JUnit testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" diff --git a/pgp-certificate-store/build.gradle b/pgp-certificate-store/build.gradle index f2d7d7a..c994a8b 100644 --- a/pgp-certificate-store/build.gradle +++ b/pgp-certificate-store/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'ru.vyarus.animalsniffer' dependencies { // animal sniffer for ensuring Android API compatibility - signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" + signature "net.sf.androidscents.signature:android-api-level-${animalsnifferSignatureVersion}@signature" // JUnit for testing testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" diff --git a/version.gradle b/version.gradle index 1951785..9223018 100644 --- a/version.gradle +++ b/version.gradle @@ -7,6 +7,7 @@ allprojects { shortVersion = '0.1.2' isSnapshot = true minAndroidSdk = 26 + animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 bouncycastleVersion = '1.71' slf4jVersion = '1.7.36' From f34c6d7735885ccc5dd3afccc426fa5609dd93f0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Aug 2022 23:18:54 +0200 Subject: [PATCH 38/63] Add tests for PGPCertificateStoreAdapter --- pgp-cert-d-java/README.md | 6 +- .../pgp/cert_d/PGPCertificateDirectory.java | 5 + .../InMemoryCertificateDirectoryBackend.java | 16 +- .../cert_d/PGPCertificateDirectoryTest.java | 162 +++--------------- .../PGPCertificateStoreAdapterTest.java | 124 ++++++++++++++ .../src/test/java/pgp/cert_d/TestKeys.java | 130 ++++++++++++++ 6 files changed, 295 insertions(+), 148 deletions(-) create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java create mode 100644 pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeys.java diff --git a/pgp-cert-d-java/README.md b/pgp-cert-d-java/README.md index 58e29d7..49302bb 100644 --- a/pgp-cert-d-java/README.md +++ b/pgp-cert-d-java/README.md @@ -13,9 +13,11 @@ Backend-agnostic implementation of the [Shared PGP Certificate Directory Specifi This module implements the non-OpenPGP parts of the spec, e.g. locating the directory, resolving certificate file paths, locking the directory for writes etc. +This library can be used on Android API level 26 and up. + To get a useful implementation, a backend implementation such as `pgpainless-cert-d` is required, which needs to provide support for reading and merging certificates. -`pgp-cert-d-java` can be used as an implementation of `pgp-certificate-store`. +`pgp-cert-d-java` can be used as an implementation of `pgp-certificate-store` using the `PGPCertificateStoreAdapter` class. -Note: This is a library module. For a command line interface, see [pgpainless-cert-d-cli](https://github.com/pgpainless/cert-d-pgpainless/tree/main/pgpainless-cert-d-cli). \ No newline at end of file +Note: This is a library module. For a command line interface, see [pgpainless-cert-d-cli](https://github.com/pgpainless/cert-d-pgpainless/tree/main/pgpainless-cert-d-cli). 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 3a3a9d7..1d54212 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 @@ -16,6 +16,7 @@ import java.io.InputStream; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; /** * Implementation of the Shared PGP Certificate Directory. @@ -27,6 +28,7 @@ public class PGPCertificateDirectory final Backend backend; final SubkeyLookup subkeyLookup; + private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); /** * Constructor for a PGP certificate directory. @@ -41,6 +43,9 @@ public class PGPCertificateDirectory @Override public Certificate getByFingerprint(String fingerprint) throws BadDataException, BadNameException, IOException { + if (!openPgpV4FingerprintPattern.matcher(fingerprint).matches()) { + throw new BadNameException(); + } return backend.readByFingerprint(fingerprint); } 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 60069c8..b3c3bd6 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 @@ -19,6 +19,7 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirectory.Backend { @@ -60,6 +61,7 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect private final Map keyMaterialSpecialNameMap = new HashMap<>(); private final PGPCertificateDirectory.LockingMechanism lock = new ObjectLockingMechanism(); private final KeyMaterialReaderBackend reader; + private final AtomicLong nonce = new AtomicLong(1); public InMemoryCertificateDirectoryBackend(KeyMaterialReaderBackend reader) { this.reader = reader; @@ -102,9 +104,9 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect } KeyMaterial merged = merge.merge(update, existing); if (merged instanceof Key) { - merged = new Key((Key) merged, System.currentTimeMillis()); + merged = new Key((Key) merged, newTag()); } else { - merged = new Certificate((Certificate) merged, System.currentTimeMillis()); + merged = new Certificate((Certificate) merged, newTag()); } keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged); return merged; @@ -117,7 +119,7 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect KeyMaterial update = reader.read(data, null); Certificate existing = readByFingerprint(update.getFingerprint()); Certificate merged = merge.merge(update, existing).asCertificate(); - merged = new Certificate(merged, System.currentTimeMillis()); + merged = new Certificate(merged, newTag()); certificateFingerprintMap.put(update.getFingerprint(), merged); return merged; } @@ -129,9 +131,9 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect KeyMaterial existing = readBySpecialName(specialName); KeyMaterial merged = merge.merge(keyMaterial, existing); if (merged instanceof Key) { - merged = new Key((Key) merged, System.currentTimeMillis()); + merged = new Key((Key) merged, newTag()); } else { - merged = new Certificate((Certificate) merged, System.currentTimeMillis()); + merged = new Certificate((Certificate) merged, newTag()); } keyMaterialSpecialNameMap.put(specialName, merged); return merged.asCertificate(); @@ -157,4 +159,8 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect } return tagged.getTag(); } + + private Long newTag() { + return System.currentTimeMillis() + nonce.incrementAndGet(); + } } 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 a3803c2..8605f0e 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 @@ -5,8 +5,10 @@ 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; @@ -20,7 +22,6 @@ import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; import pgp.certificate_store.exception.NotAStoreException; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -41,119 +42,16 @@ 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 pgp.cert_d.TestKeys.CEDRIC_FP; +import static pgp.cert_d.TestKeys.HARRY_FP; +import static pgp.cert_d.TestKeys.RON_FP; public class PGPCertificateDirectoryTest { - @SuppressWarnings("CharsetObjectCanBeUsed") - 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"; private static final KeyMaterialMerger merger = new TestKeyMaterialMerger(); - private static Stream provideTestSubjects() + private static Stream provideTestSubjects() throws IOException, NotAStoreException { PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory( new TestKeyMaterialReaderBackend()); @@ -165,7 +63,9 @@ public class PGPCertificateDirectoryTest { tempDir, new InMemorySubkeyLookup()); - return Stream.of(inMemory, fileBased); + return Stream.of( + Arguments.of(Named.of("InMemoryCertificateDirectory", inMemory)), + Arguments.of(Named.of("FileBasedCertificateDirectory", fileBased))); } @ParameterizedTest @@ -178,11 +78,11 @@ public class PGPCertificateDirectoryTest { assertTrue(directory.backend.getLock().isLocked()); assertFalse(directory.backend.getLock().tryLockDirectory()); - Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + Certificate inserted = directory.tryInsert(TestKeys.getCedricCert(), merger); assertNull(inserted); directory.backend.getLock().releaseDirectory(); - inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + inserted = directory.tryInsert(TestKeys.getCedricCert(), merger); assertNotNull(inserted); } @@ -198,15 +98,13 @@ public class PGPCertificateDirectoryTest { throws BadDataException, IOException, InterruptedException, BadNameException { assertNull(directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); - ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)); - - Certificate certificate = directory.insert(bytesIn, merger); + Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); 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); + byte[] expected = TestKeys.CEDRIC_CERT.getBytes(Charset.forName("UTF8")); ByteArrayOutputStream actual = new ByteArrayOutputStream(); Streams.pipeAll(get.getInputStream(), actual); assertArrayEquals(expected, actual.toByteArray(), "InputStream of cert MUST match what we gave in"); @@ -219,7 +117,7 @@ public class PGPCertificateDirectoryTest { assertNull(directory.getTrustRoot()); KeyMaterial trustRootMaterial = directory.insertTrustRoot( - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + TestKeys.getHarryKey(), merger); assertNotNull(trustRootMaterial); assertTrue(trustRootMaterial instanceof Key); assertEquals(HARRY_FP, trustRootMaterial.getFingerprint()); @@ -229,8 +127,8 @@ public class PGPCertificateDirectoryTest { Certificate trustRootCert = directory.getTrustRootCertificate(); assertEquals(HARRY_FP, trustRootCert.getFingerprint()); - directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), merger); - directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + directory.tryInsert(TestKeys.getRonCert(), merger); + directory.insert(TestKeys.getCedricCert(), merger); Set expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP)); @@ -248,7 +146,7 @@ public class PGPCertificateDirectoryTest { public void testGetTrustRootIfChanged(PGPCertificateDirectory directory) throws BadDataException, IOException, InterruptedException { KeyMaterial trustRootMaterial = directory.insertTrustRoot( - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + TestKeys.getHarryKey(), merger); assertNotNull(trustRootMaterial.getTag()); Long tag = trustRootMaterial.getTag(); @@ -258,7 +156,7 @@ public class PGPCertificateDirectoryTest { Long oldTag = tag; // "update" key trustRootMaterial = directory.insertTrustRoot( - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + TestKeys.getHarryKey(), merger); tag = trustRootMaterial.getTag(); assertNotEquals(oldTag, tag); @@ -270,42 +168,24 @@ public class PGPCertificateDirectoryTest { public void testGetBySpecialNameIfChanged(PGPCertificateDirectory directory) throws BadDataException, IOException, InterruptedException, BadNameException { KeyMaterial specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); + TestKeys.getHarryKey(), merger); assertNotNull(specialName.getTag()); Long tag = specialName.getTag(); assertNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag)); assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag + 1)); - - Long oldTag = tag; - // "update" key - specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, - new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger); - tag = specialName.getTag(); - - assertNotEquals(oldTag, tag); - assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, oldTag)); } @ParameterizedTest @MethodSource("provideTestSubjects") public void testGetByFingerprintIfChanged(PGPCertificateDirectory directory) throws BadDataException, IOException, InterruptedException, BadNameException { - Certificate certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); Long tag = certificate.getTag(); assertNotNull(tag); assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1)); - - Long oldTag = tag; - // "update" cert - certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); - tag = certificate.getTag(); - - assertNotEquals(oldTag, tag); - assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); - assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag)); } @Test @@ -320,7 +200,7 @@ public class PGPCertificateDirectoryTest { new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir); // Insert certificate - Certificate certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger); + Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); Long tag = certificate.getTag(); assertNotNull(tag); assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag)); 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 new file mode 100644 index 0000000..56fdac6 --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/PGPCertificateStoreAdapterTest.java @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import pgp.cert_d.backend.InMemoryCertificateDirectoryBackend; +import pgp.cert_d.dummy.TestKeyMaterialMerger; +import pgp.cert_d.dummy.TestKeyMaterialReaderBackend; +import pgp.cert_d.subkey_lookup.InMemorySubkeyLookupFactory; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.exception.BadDataException; +import pgp.certificate_store.exception.BadNameException; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +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; + +public class PGPCertificateStoreAdapterTest { + + private PGPCertificateDirectory directory; + private PGPCertificateStoreAdapter adapter; + + private static final TestKeyMaterialMerger merger = new TestKeyMaterialMerger(); + + @BeforeEach + public void setup() { + directory = new PGPCertificateDirectory( + new InMemoryCertificateDirectoryBackend(new TestKeyMaterialReaderBackend()), + new InMemorySubkeyLookupFactory().createFileBasedInstance(null)); + adapter = new PGPCertificateStoreAdapter(directory); + } + + @Test + public void testBadFPWithInvalidCharsYieldsBadNameException() { + assertThrows(BadNameException.class, () -> adapter.getCertificate("XYZ78fd17f207fdf62f7976c4e9d98917ad84522")); + } + + @Test + public void testBadFPWithTooFewCharsYieldsBadNameException() { + assertThrows(BadNameException.class, () -> adapter.getCertificate("23578fd17f207fdf62f7976c4e9d98917ad")); + } + + @Test + public void testInsertGetCertificate() + throws BadDataException, IOException, InterruptedException, BadNameException { + assertNull(adapter.getCertificate(TestKeys.CEDRIC_FP)); + assertFalse(adapter.getCertificates().hasNext()); + + Certificate certificate = adapter.insertCertificate(TestKeys.getCedricCert(), merger); + assertNotNull(certificate); + assertEquals(TestKeys.CEDRIC_FP, certificate.getFingerprint()); + + certificate = adapter.getCertificate(TestKeys.CEDRIC_FP.toUpperCase()); + assertEquals(TestKeys.CEDRIC_FP, certificate.getFingerprint(), "We can also fetch with uppercase fps"); + + Iterator fingerprints = adapter.getFingerprints(); + assertEquals(TestKeys.CEDRIC_FP, fingerprints.next()); + assertFalse(fingerprints.hasNext()); + } + + @Test + public void testInsertGetTrustRoot() + throws BadDataException, BadNameException, IOException, InterruptedException { + assertNull(adapter.getCertificate(SpecialNames.TRUST_ROOT)); + + Certificate certificate = adapter.insertCertificateBySpecialName( + SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); + assertNotNull(certificate); + assertEquals(TestKeys.HARRY_FP, certificate.getFingerprint()); + + assertFalse(adapter.getCertificates().hasNext(), "Special-named certs are not returned by getCertificates()"); + assertFalse(adapter.getFingerprints().hasNext()); + } + + @Test + public void testGetCertificateIfChanged() + throws BadDataException, IOException, InterruptedException, BadNameException { + Certificate certificate = adapter.insertCertificate(TestKeys.getRonCert(), merger); + Long tag = certificate.getTag(); + + assertNull(adapter.getCertificateIfChanged(TestKeys.RON_FP, tag), "Cert has not changed, tag is still valid"); + assertNotNull(adapter.getCertificateIfChanged(TestKeys.RON_FP, tag + 1)); + } + + @Test + public void testGetTrustRootIfChanged() + throws BadDataException, BadNameException, IOException, InterruptedException { + Certificate certificate = adapter.insertCertificateBySpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); + Long tag = certificate.getTag(); + + assertNull(adapter.getCertificateIfChanged(SpecialNames.TRUST_ROOT, tag)); + assertNotNull(adapter.getCertificateIfChanged(SpecialNames.TRUST_ROOT, tag * 2)); + } + + @Test + public void testGetCertificateBySubkeyId() + throws BadDataException, IOException, InterruptedException { + // Insert some certs + adapter.insertCertificate(TestKeys.getCedricCert(), merger); + adapter.insertCertificate(TestKeys.getHarryKey(), merger); + // Now insert Ron + Certificate certificate = adapter.insertCertificate(TestKeys.getRonCert(), merger); + List subkeyIds = certificate.getSubkeyIds(); + + assertFalse(adapter.getCertificatesBySubkeyId(0).hasNext()); + + for (Long subkeyId : subkeyIds) { + Iterator certsWithSubkey = adapter.getCertificatesBySubkeyId(subkeyId); + Certificate certWithSubkey = certsWithSubkey.next(); + assertFalse(certsWithSubkey.hasNext()); + + assertEquals(TestKeys.RON_FP, certWithSubkey.getFingerprint()); + } + } +} diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeys.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeys.java new file mode 100644 index 0000000..fd2d8f1 --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/TestKeys.java @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; + +public class TestKeys { + @SuppressWarnings("CharsetObjectCanBeUsed") + private static final Charset UTF8 = Charset.forName("UTF8"); + + public 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"; + public static final String HARRY_FP = "23578fd17f207fdf62f7976c4e9d98917ad84522"; + + public 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"; + public static final String RON_FP = "b798af186bfe4c19902d49505647f00137ef4c41"; + + public 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"; + public static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021"; + + public static InputStream getHarryKey() { + return new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)); + } + + public static InputStream getRonCert() { + return new ByteArrayInputStream(RON_CERT.getBytes(UTF8)); + } + + public static InputStream getCedricCert() { + return new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)); + } +} From 4ce9f468466080d54c21e40c84d1b45f96d68920 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:26:12 +0200 Subject: [PATCH 39/63] Increase coverage of PGPCertificateDirectory --- .../InMemoryCertificateDirectoryBackend.java | 4 +++ .../cert_d/PGPCertificateDirectoryTest.java | 36 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) 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 b3c3bd6..90797af 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 @@ -21,6 +21,10 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +/** + * Implementation of the {@link PGPCertificateDirectory.Backend} which stores key material in-memory. + * It uses object locking with {@link #wait()} and {@link #notify()} to synchronize write-access. + */ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirectory.Backend { protected static class ObjectLockingMechanism implements PGPCertificateDirectory.LockingMechanism { 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 8605f0e..4ffb865 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 @@ -70,7 +70,7 @@ public class PGPCertificateDirectoryTest { @ParameterizedTest @MethodSource("provideTestSubjects") - public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) + public void lockDirectoryAndTryInsertWillFail(PGPCertificateDirectory directory) throws IOException, InterruptedException, BadDataException { // Manually lock the dir assertFalse(directory.backend.getLock().isLocked()); @@ -86,6 +86,40 @@ public class PGPCertificateDirectoryTest { assertNotNull(inserted); } + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void lockDirectoryAndTryInsertTrustRootWillFail(PGPCertificateDirectory directory) + throws IOException, InterruptedException, BadDataException { + // Manually lock the dir + assertFalse(directory.backend.getLock().isLocked()); + directory.backend.getLock().lockDirectory(); + assertTrue(directory.backend.getLock().isLocked()); + + KeyMaterial inserted = directory.tryInsertTrustRoot(TestKeys.getHarryKey(), merger); + assertNull(inserted); + + directory.backend.getLock().releaseDirectory(); + inserted = directory.tryInsertTrustRoot(TestKeys.getHarryKey(), merger); + assertNotNull(inserted); + } + + @ParameterizedTest + @MethodSource("provideTestSubjects") + public void lockDirectoryAndTryInsertWithSpecialNameWillFail(PGPCertificateDirectory directory) + throws IOException, InterruptedException, BadDataException, BadNameException { + // Manually lock the dir + assertFalse(directory.backend.getLock().isLocked()); + directory.backend.getLock().lockDirectory(); + assertTrue(directory.backend.getLock().isLocked()); + + Certificate inserted = directory.tryInsertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); + assertNull(inserted); + + directory.backend.getLock().releaseDirectory(); + inserted = directory.tryInsertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); + assertNotNull(inserted); + } + @ParameterizedTest @MethodSource("provideTestSubjects") public void getByInvalidNameFails(PGPCertificateDirectory directory) { From 2758c3efb87a5d0fcf385f818a3cc7286e491454 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:40:22 +0200 Subject: [PATCH 40/63] Add javadoc --- .../backend/FileBasedCertificateDirectoryBackend.java | 11 +++++++++++ .../subkey_lookup/InMemorySubkeyLookupFactory.java | 3 +++ .../pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java | 3 +++ 3 files changed, 17 insertions(+) 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 e5750e8..def57e6 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 @@ -35,12 +35,20 @@ import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; +/** + * Implementation of {@link PGPCertificateDirectory.Backend} which stores certificates in a directory structure. + * + * @see Shared PGP Certificate Directory + */ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirectory.Backend { private abstract static class Lazy { abstract E get() throws BadDataException; } + /** + * Locking mechanism which uses a lock file to synchronize write-access to the store. + */ private static class FileLockingMechanism implements PGPCertificateDirectory.LockingMechanism { private final File lockFile; @@ -364,6 +372,9 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec return getTag(certFile); } + /** + * Class to resolve file names from certificate fingerprints / special names. + */ public static class FilenameResolver { private final File baseDirectory; diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java index a224c64..98e006c 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/InMemorySubkeyLookupFactory.java @@ -6,6 +6,9 @@ package pgp.cert_d.subkey_lookup; import java.io.File; +/** + * Factory class to instantiate {@link InMemorySubkeyLookup} objects. + */ public class InMemorySubkeyLookupFactory implements SubkeyLookupFactory { @Override public SubkeyLookup createFileBasedInstance(File baseDirectory) { diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java index 442e9ef..a958b84 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/subkey_lookup/SubkeyLookupFactory.java @@ -6,6 +6,9 @@ package pgp.cert_d.subkey_lookup; import java.io.File; +/** + * Factory class to instantiate different {@link SubkeyLookup} implementations. + */ public interface SubkeyLookupFactory { /** From 3ec7b082be76aaedb91e4a99915a00cd0c50231c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:41:05 +0200 Subject: [PATCH 41/63] Prevent NPEs in file walker --- .../backend/FileBasedCertificateDirectoryBackend.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 def57e6..1ffc550 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 @@ -217,6 +217,10 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec } }); + if (subdirectories == null) { + subdirectories = new File[0]; + } + for (File subdirectory : subdirectories) { File[] files = subdirectory.listFiles(new FileFilter() { @Override @@ -225,6 +229,10 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec } }); + if (files == null) { + files = new File[0]; + } + for (File certFile : files) { certificateQueue.add(new Lazy() { @Override From da39d41df329c5f1d5022903d3a7b7a0bd35d962 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:41:41 +0200 Subject: [PATCH 42/63] Remove unused method --- .../FileBasedCertificateDirectoryBackend.java | 18 ------------------ 1 file changed, 18 deletions(-) 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 1ffc550..4d37b0d 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 @@ -434,24 +434,6 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec return new File(getBaseDirectory(), specialName); } - /** - * Calculate the file location for the key addressed using the given special name. - * For known special names, see {@link SpecialNames}. - * - * @param specialName special name (e.g. "trust-root") - * @return absolute key file location - * - * @throws BadNameException in case the given special name is not known - */ - public File getKeyFileBySpecialName(String specialName) - throws BadNameException { - if (!isSpecialName(specialName)) { - throw new BadNameException(String.format("%s is not a known special name", specialName)); - } - - return new File(getBaseDirectory(), specialName + ".key"); - } - private boolean isFingerprint(String fingerprint) { return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); } From 142d1f6b16779b0ccb48e1d5d5d64723a8e5a701 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:41:52 +0200 Subject: [PATCH 43/63] Add support for v5 fingerprints --- .../cert_d/backend/FileBasedCertificateDirectoryBackend.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 4d37b0d..89e52ba 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 @@ -386,7 +386,8 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec public static class FilenameResolver { private final File baseDirectory; - private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); + // matches v4 and v5 fingerprints (v4 = 40 hex chars, v5 = 64 hex chars) + private final Pattern openPgpFingerprint = Pattern.compile("^[a-f0-9]{40}([a-f0-9]{24})?$"); public FilenameResolver(File baseDirectory) { this.baseDirectory = baseDirectory; @@ -435,7 +436,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec } private boolean isFingerprint(String fingerprint) { - return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); + return openPgpFingerprint.matcher(fingerprint).matches(); } private boolean isSpecialName(String specialName) { From 682b3d58fa47cad5260b1cba39c6205b82bfd46a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 11:55:15 +0200 Subject: [PATCH 44/63] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b270f..c5c96e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,11 @@ SPDX-License-Identifier: CC0-1.0 - Rename `CertificateMerger` to `KeyMaterialMerger` - Rework `PGPCertificateStore` class - `pgp-cert-d-java`: + - Increase minimum Android API level to 26 + - Add `PGPCertificateDirectories` factory class - Rework `PGPCertificateDirectory` class by separating out backend logic - Split interface into `ReadOnlyPGPCertificateDirectory` and `WritingPGPCertificateDirectory` + - `FileBasedCertificateDirectoryBackend`: Calculate tag based on file attributes (inode) - `pgp-cert-d-java-jdbc-sqlite-lookup`: - Add `DatabaseSubkeyLookupFactory` From 65a283c80b8c23b830aa0106f1d6d1c67593001f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 12:00:14 +0200 Subject: [PATCH 45/63] Cert-D-Java 0.2.0 --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c96e0..ad2475e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: CC0-1.0 # Cert-D-Java Changelog -## 0.1.2-SNAPSHOT +## 0.2.0 - `pgp-certificate-store`: - Rework `Certificate`, `Key` to inherit from `KeyMaterial` - Rename `CertificateReaderBackend` to `KeyMaterialReaderBackend` diff --git a/version.gradle b/version.gradle index 9223018..8e3b938 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '0.1.2' - isSnapshot = true + shortVersion = '0.2.0' + isSnapshot = false minAndroidSdk = 26 animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" javaSourceCompatibility = 1.8 From eab31b8c1242196d13b228c3a0f504c2ede42e5c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 25 Aug 2022 12:02:01 +0200 Subject: [PATCH 46/63] 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 47/63] 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 48/63] 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 49/63] 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 50/63] 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 51/63] 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 52/63] 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 53/63] 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 54/63] 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 55/63] 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 56/63] 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 57/63] 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 58/63] 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 59/63] 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 60/63] 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 61/63] 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 62/63] 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 63/63] 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