From 8fb677676fefe5e4e19c73292953fa591bc2a6a8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 31 Jan 2022 16:51:27 +0100 Subject: [PATCH] Add SharedPGPCertificateDirectoryAdapter class --- pgp-cert-d-java/README.md | 8 ++ .../SharedPGPCertificateDirectoryAdapter.java | 136 ++++++++++++++++++ .../SharedPGPCertificateDirectoryImpl.java | 24 ++-- pgp-certificate-store/README.md | 10 ++ .../CertificateReaderBackend.java | 26 ++++ .../certificate_store/CertificateStore.java | 41 ++++-- .../pgp/certificate_store/ParserBackend.java | 14 -- pgpainless-cert-d/README.md | 7 + .../SharedPGPCertificateDirectoryTest.java | 4 +- ...java => CertificateCertificateReader.java} | 4 +- 10 files changed, 236 insertions(+), 38 deletions(-) create mode 100644 pgp-cert-d-java/README.md create mode 100644 pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryAdapter.java create mode 100644 pgp-certificate-store/README.md create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java delete mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/ParserBackend.java create mode 100644 pgpainless-cert-d/README.md rename pgpainless-core/src/main/java/org/pgpainless/certificate_store/{CertificateParser.java => CertificateCertificateReader.java} (92%) diff --git a/pgp-cert-d-java/README.md b/pgp-cert-d-java/README.md new file mode 100644 index 00000000..945eecc0 --- /dev/null +++ b/pgp-cert-d-java/README.md @@ -0,0 +1,8 @@ + + +# Shared PGP Certificate Directory for Java + diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryAdapter.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryAdapter.java new file mode 100644 index 00000000..b659f366 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryAdapter.java @@ -0,0 +1,136 @@ +// 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.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; +import pgp.certificate_store.Certificate; +import pgp.certificate_store.CertificateStore; +import pgp.certificate_store.MergeCallback; + +/** + * Adapter class used to adapt the {@link SharedPGPCertificateDirectory} for use with + * {@link CertificateStore}. + */ +public class SharedPGPCertificateDirectoryAdapter + implements CertificateStore { + + private final SharedPGPCertificateDirectory directory; + + /** + * Create an adapter to use {@link SharedPGPCertificateDirectory} objects as {@link CertificateStore CertificateStores}. + * + * @param directory directory instance + */ + public SharedPGPCertificateDirectoryAdapter(SharedPGPCertificateDirectory directory) { + this.directory = directory; + } + + @Override + public Certificate getCertificate(String identifier) + throws IOException { + SpecialName specialName = SpecialName.fromString(identifier); + if (specialName != null) { + try { + return directory.get(specialName); + } catch (BadNameException e) { + throw new IllegalArgumentException("Unknown special name " + identifier, e); + } + } + + try { + return directory.get(identifier); + } catch (BadNameException e) { + throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e); + } + } + + @Override + public Certificate getCertificateIfChanged(String identifier, String tag) + throws IOException { + SpecialName specialName = SpecialName.fromString(identifier); + if (specialName != null) { + try { + return directory.getIfChanged(specialName, tag); + } catch (BadNameException e) { + throw new IllegalArgumentException("Unknown special name " + identifier, e); + } + } + + try { + return directory.getIfChanged(identifier, tag); + } catch (BadNameException e) { + throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e); + } + } + + @Override + public Certificate insertCertificate(InputStream data, MergeCallback merge) + throws IOException, InterruptedException { + try { + return directory.insert(data, merge); + } catch (BadDataException e) { + throw new IOException("Cannot insert certificate due to bad data", e); + } + } + + @Override + public Certificate tryInsertCertificate(InputStream data, MergeCallback merge) + throws IOException { + try { + return directory.tryInsert(data, merge); + } catch (BadDataException e) { + throw new IOException("Cannot insert certificate due to bad data", e); + } + } + + @Override + public Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) + throws IOException, InterruptedException { + try { + SpecialName specialNameEnum = SpecialName.fromString(specialName); + if (specialNameEnum == null) { + throw new IllegalArgumentException("Unknown special name " + specialName); + } + + return directory.insertSpecial(specialNameEnum, data, merge); + } catch (BadNameException e) { + throw new IllegalArgumentException("Unknown special name " + specialName); + } catch (BadDataException e) { + throw new IOException("Cannot insert certificate due to bad data", e); + } + } + + @Override + public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) + throws IOException { + try { + SpecialName specialNameEnum = SpecialName.fromString(specialName); + if (specialNameEnum == null) { + throw new IllegalArgumentException("Unknown special name " + specialName); + } + + return directory.tryInsertSpecial(specialNameEnum, data, merge); + } catch (BadNameException e) { + throw new IllegalArgumentException("Unknown special name " + specialName); + } catch (BadDataException e) { + throw new IOException("Cannot insert certificate due to bad data", e); + } + } + + @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/SharedPGPCertificateDirectoryImpl.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectoryImpl.java index a75d2b50..a0c9b10e 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.cert_d.exception.BadNameException; import pgp.cert_d.exception.NotAStoreException; import pgp.certificate_store.Certificate; import pgp.certificate_store.MergeCallback; -import pgp.certificate_store.ParserBackend; +import pgp.certificate_store.CertificateReaderBackend; public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory { @@ -29,16 +29,16 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); private final LockingMechanism writeLock; - private final ParserBackend parserBackend; + private final CertificateReaderBackend certificateReaderBackend; - public SharedPGPCertificateDirectoryImpl(ParserBackend parserBackend) + public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend) throws NotAStoreException { - this(OSUtil.getDefaultBaseDir(), parserBackend); + this(OSUtil.getDefaultBaseDir(), certificateReaderBackend); } - public SharedPGPCertificateDirectoryImpl(File baseDirectory, ParserBackend parserBackend) + public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend) throws NotAStoreException { - this.parserBackend = parserBackend; + this.certificateReaderBackend = certificateReaderBackend; this.baseDirectory = baseDirectory; if (!baseDirectory.exists()) { if (!baseDirectory.mkdirs()) { @@ -83,7 +83,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = parserBackend.readCertificate(bufferedIn); + Certificate certificate = certificateReaderBackend.readCertificate(bufferedIn); if (!certificate.getFingerprint().equals(fingerprint)) { // TODO: Figure out more suitable exception @@ -102,7 +102,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi FileInputStream fileIn = new FileInputStream(certFile); BufferedInputStream bufferedIn = new BufferedInputStream(fileIn); - Certificate certificate = parserBackend.readCertificate(bufferedIn); + Certificate certificate = certificateReaderBackend.readCertificate(bufferedIn); return certificate; } @@ -148,7 +148,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } private Certificate _insert(InputStream data, MergeCallback merge) throws IOException, BadDataException { - Certificate newCertificate = parserBackend.readCertificate(data); + Certificate newCertificate = certificateReaderBackend.readCertificate(data); Certificate existingCertificate; File certFile; try { @@ -209,7 +209,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } private Certificate _insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException { - Certificate newCertificate = parserBackend.readCertificate(data); + Certificate newCertificate = certificateReaderBackend.readCertificate(data); Certificate existingCertificate = get(specialName); File certFile = getCertFile(specialName); @@ -238,7 +238,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi @Override Certificate get() { try { - return parserBackend.readCertificate(new FileInputStream(certFile)); + return certificateReaderBackend.readCertificate(new FileInputStream(certFile)); } catch (IOException e) { throw new AssertionError("File got deleted."); } @@ -267,7 +267,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi @Override Certificate get() throws BadDataException { try { - Certificate certificate = parserBackend.readCertificate(new FileInputStream(certFile)); + Certificate certificate = certificateReaderBackend.readCertificate(new FileInputStream(certFile)); if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) { throw new BadDataException(); } diff --git a/pgp-certificate-store/README.md b/pgp-certificate-store/README.md new file mode 100644 index 00000000..bdbecdc4 --- /dev/null +++ b/pgp-certificate-store/README.md @@ -0,0 +1,10 @@ + + +# PGP Certificate Store Definitions + +This module contains API definitions for a certificate store for PGPainless. +A certificate store is used to store public key certificates only. \ No newline at end of file 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 new file mode 100644 index 00000000..c16b1116 --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateReaderBackend.java @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.certificate_store; + +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 + */ + Certificate readCertificate(InputStream inputStream) throws IOException; + +} 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 6334d3f7..95fa3497 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 @@ -8,21 +8,46 @@ 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 CertificateStore { - Certificate get(String identifier) throws IOException; + /** + * 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 + */ + Certificate getCertificate(String identifier) throws IOException; - Certificate getIfChanged(String identifier, String tag) throws IOException; + /** + * + * @param identifier + * @param tag + * @return + * @throws IOException + */ + Certificate getCertificateIfChanged(String identifier, String tag) throws IOException; - Certificate insert(InputStream data, MergeCallback merge) throws IOException; + Certificate insertCertificate(InputStream data, MergeCallback merge) throws IOException, InterruptedException; - Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException; + Certificate tryInsertCertificate(InputStream data, MergeCallback merge) throws IOException; - Certificate insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException; + Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException, InterruptedException; - Certificate tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException; + Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException; - Iterator items(); + Iterator getCertificates(); - Iterator fingerprints(); + Iterator getFingerprints(); } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/ParserBackend.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/ParserBackend.java deleted file mode 100644 index 7bbc0f2b..00000000 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/ParserBackend.java +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.certificate_store; - -import java.io.IOException; -import java.io.InputStream; - -public interface ParserBackend { - - Certificate readCertificate(InputStream inputStream) throws IOException; - -} diff --git a/pgpainless-cert-d/README.md b/pgpainless-cert-d/README.md new file mode 100644 index 00000000..0cc5f15c --- /dev/null +++ b/pgpainless-cert-d/README.md @@ -0,0 +1,7 @@ + + +# Certificate Stores + PGPainless diff --git a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java index 32fda258..2e5006a9 100644 --- a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java +++ b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.pgpainless.PGPainless; -import org.pgpainless.certificate_store.CertificateParser; +import org.pgpainless.certificate_store.CertificateCertificateReader; import org.pgpainless.key.OpenPgpFingerprint; import pgp.cert_d.FileLockingMechanism; import pgp.cert_d.LockingMechanism; @@ -51,7 +51,7 @@ public class SharedPGPCertificateDirectoryTest { public void beforeEach() throws IOException, NotAStoreException { File tempDir = Files.createTempDirectory("pgp.cert.d-").toFile(); tempDir.deleteOnExit(); - directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateParser()); + directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateCertificateReader()); } @Test diff --git a/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateParser.java b/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java similarity index 92% rename from pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateParser.java rename to pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java index 4b1b3de2..f17ef896 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateParser.java +++ b/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java @@ -15,9 +15,9 @@ import org.bouncycastle.util.encoders.Base64; import org.pgpainless.PGPainless; import org.pgpainless.key.OpenPgpFingerprint; import pgp.certificate_store.Certificate; -import pgp.certificate_store.ParserBackend; +import pgp.certificate_store.CertificateReaderBackend; -public class CertificateParser implements ParserBackend { +public class CertificateCertificateReader implements CertificateReaderBackend { @Override public Certificate readCertificate(InputStream inputStream) throws IOException {