diff --git a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java b/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java index 41d1b9cc..2fa7b1a9 100644 --- a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java +++ b/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java @@ -4,12 +4,11 @@ package pgp.cert_d.cli; -import org.pgpainless.certificate_store.CertificateCertificateReader; +import org.pgpainless.certificate_store.CertificateReader; import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter; import pgp.cert_d.SharedPGPCertificateDirectoryImpl; import pgp.cert_d.cli.commands.Get; -import pgp.cert_d.cli.commands.Insert; -import pgp.cert_d.cli.commands.PrintDirectory; +import pgp.cert_d.cli.commands.Import; import pgp.cert_d.exception.NotAStoreException; import pgp.certificate_store.CertificateStore; import picocli.CommandLine; @@ -18,8 +17,7 @@ import java.io.File; @CommandLine.Command( subcommands = { - Insert.class, - PrintDirectory.class, + Import.class, Get.class, } ) @@ -29,7 +27,6 @@ public class PGPCertDCli { File baseDirectory; private static CertificateStore certificateStore; - private static String baseDir; private int executionStrategy(CommandLine.ParseResult parseResult) { try { @@ -45,12 +42,11 @@ public class PGPCertDCli { if (baseDirectory != null) { certificateDirectory = new SharedPGPCertificateDirectoryImpl( baseDirectory, - new CertificateCertificateReader()); + new CertificateReader()); } else { certificateDirectory = new SharedPGPCertificateDirectoryImpl( - new CertificateCertificateReader()); + new CertificateReader()); } - baseDir = certificateDirectory.getBaseDirectory().getAbsolutePath(); certificateStore = new SharedPGPCertificateDirectoryAdapter(certificateDirectory); } @@ -64,8 +60,4 @@ public class PGPCertDCli { public static CertificateStore getCertificateDirectory() { return certificateStore; } - - public static String getBaseDir() { - return baseDir; - } } diff --git a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Insert.java b/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java similarity index 88% rename from pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Insert.java rename to pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java index 54b6f32f..a0f08f58 100644 --- a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Insert.java +++ b/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java @@ -13,11 +13,11 @@ import pgp.certificate_store.Certificate; import pgp.certificate_store.MergeCallback; import picocli.CommandLine; -@CommandLine.Command(name = "insert", - description = "Insert or update a certificate") -public class Insert implements Runnable { +@CommandLine.Command(name = "import", + description = "Import or update a certificate") +public class Import implements Runnable { - private static final Logger LOGGER = LoggerFactory.getLogger(Insert.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Import.class); @Override public void run() { diff --git a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/PrintDirectory.java b/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/PrintDirectory.java deleted file mode 100644 index c5e82e7f..00000000 --- a/pgp-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/PrintDirectory.java +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d.cli.commands; - -import pgp.cert_d.cli.PGPCertDCli; -import picocli.CommandLine; - -@CommandLine.Command( - name = "print-directory", - description = "Print the location of the certificate directory" -) -public class PrintDirectory implements Runnable { - - @Override - public void run() { - // CHECKSTYLE:OFF - System.out.println(PGPCertDCli.getBaseDir()); - // CHECKSTYLE:ON - } -} diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/OSUtil.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java similarity index 97% rename from pgp-cert-d-java/src/main/java/pgp/cert_d/OSUtil.java rename to pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java index 1accf635..23d4d935 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/OSUtil.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/BaseDirectoryProvider.java @@ -6,7 +6,7 @@ package pgp.cert_d; import java.io.File; -public class OSUtil { +public class BaseDirectoryProvider { public static File getDefaultBaseDir() { // Check for environment variable 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 new file mode 100644 index 00000000..5fab72d0 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/CachingSharedPGPCertificateDirectoryWrapper.java @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.cert_d.exception.BadDataException; +import pgp.cert_d.exception.BadNameException; +import pgp.certificate_store.Certificate; +import pgp.certificate_store.MergeCallback; + +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 tagMap = new HashMap<>(); + private static final Map certificateMap = 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 { + tagMap.put(identifier, certificate.getTag()); + } catch (IOException e) { + tagMap.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 tagChanged(String identifier, String tag) { + String tack = tagMap.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(); + tagMap.clear(); + } + + @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 { + 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 (tagChanged(fingerprint, tag)) { + return getByFingerprint(fingerprint); + } + return null; + } + + @Override + public Certificate getBySpecialNameIfChanged(String specialName, String tag) + throws IOException, BadNameException { + if (tagChanged(specialName, tag)) { + return getBySpecialName(specialName); + } + return null; + } + + @Override + public Certificate insert(InputStream data, MergeCallback merge) + throws IOException, BadDataException, InterruptedException { + Certificate certificate = underlyingCertificateDirectory.insert(data, merge); + remember(certificate.getFingerprint(), certificate); + return certificate; + } + + @Override + public Certificate tryInsert(InputStream data, MergeCallback merge) + throws IOException, BadDataException { + Certificate certificate = underlyingCertificateDirectory.tryInsert(data, merge); + if (certificate != null) { + remember(certificate.getFingerprint(), certificate); + } + return certificate; + } + + @Override + public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback 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, MergeCallback 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/FileLockingMechanism.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/FileLockingMechanism.java index 785e5f23..2d87c047 100644 --- 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 @@ -21,6 +21,10 @@ public class FileLockingMechanism implements LockingMechanism { 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) { 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 new file mode 100644 index 00000000..b1b41f02 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/FilenameResolver.java @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import pgp.cert_d.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 + */ + 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; + } + + public File getCertFileBySpecialName(String specialName) throws BadNameException { + if (!isSpecialName(specialName)) { + throw new BadNameException(); + } + + return new File(getBaseDirectory(), specialName); + } + + 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/SharedPGPCertificateDirectory.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SharedPGPCertificateDirectory.java index da2f896e..9bcf32b0 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 @@ -15,21 +15,29 @@ import pgp.certificate_store.MergeCallback; public interface SharedPGPCertificateDirectory { - Certificate get(String fingerprint) throws IOException, BadNameException; + Certificate getByFingerprint(String fingerprint) + throws IOException, BadNameException, BadDataException; - Certificate get(SpecialName specialName) throws IOException, BadNameException; + Certificate getBySpecialName(String specialName) + throws IOException, BadNameException; - Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException; + Certificate getByFingerprintIfChanged(String fingerprint, String tag) + throws IOException, BadNameException, BadDataException; - Certificate getIfChanged(SpecialName specialName, String tag) throws IOException, BadNameException; + Certificate getBySpecialNameIfChanged(String specialName, String tag) + throws IOException, BadNameException; - Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException; + Certificate insert(InputStream data, MergeCallback merge) + throws IOException, BadDataException, InterruptedException; - Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException; + Certificate tryInsert(InputStream data, MergeCallback merge) + throws IOException, BadDataException; - Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException, InterruptedException; + Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + throws IOException, BadDataException, BadNameException, InterruptedException; - Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException; + Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback 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 a0c9b10e..147f7436 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 @@ -14,7 +14,6 @@ import java.io.InputStream; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.SynchronousQueue; -import java.util.regex.Pattern; import pgp.cert_d.exception.BadDataException; import pgp.cert_d.exception.BadNameException; @@ -25,77 +24,70 @@ import pgp.certificate_store.CertificateReaderBackend; public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory { - private final File baseDirectory; - private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$"); - + private final FilenameResolver resolver; private final LockingMechanism writeLock; private final CertificateReaderBackend certificateReaderBackend; public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend) throws NotAStoreException { - this(OSUtil.getDefaultBaseDir(), certificateReaderBackend); + this( + BaseDirectoryProvider.getDefaultBaseDir(), + certificateReaderBackend); } public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend) throws NotAStoreException { + this( + certificateReaderBackend, + new FilenameResolver(baseDirectory), + FileLockingMechanism.defaultDirectoryFileLock(baseDirectory)); + } + + public SharedPGPCertificateDirectoryImpl( + CertificateReaderBackend certificateReaderBackend, + FilenameResolver filenameResolver, + LockingMechanism writeLock) + throws NotAStoreException { this.certificateReaderBackend = certificateReaderBackend; - this.baseDirectory = baseDirectory; + this.resolver = filenameResolver; + this.writeLock = writeLock; + + File baseDirectory = resolver.getBaseDirectory(); if (!baseDirectory.exists()) { if (!baseDirectory.mkdirs()) { - throw new NotAStoreException("Cannot create base directory '" + getBaseDirectory().getAbsolutePath() + "'"); + throw new NotAStoreException("Cannot create base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "'"); } } else { if (baseDirectory.isFile()) { - throw new NotAStoreException("Base directory '" + getBaseDirectory().getAbsolutePath() + "' appears to be a file."); + throw new NotAStoreException("Base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "' appears to be a file."); } } - writeLock = new FileLockingMechanism(new File(getBaseDirectory(), "writelock")); - } - - public File getBaseDirectory() { - return baseDirectory; - } - - private File getCertFile(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; - } - - private File getCertFile(SpecialName specialName) { - return new File(getBaseDirectory(), specialName.getValue()); - } - - private boolean isFingerprint(String fingerprint) { - return openPgpV4FingerprintPattern.matcher(fingerprint).matches(); } @Override - public Certificate get(String fingerprint) throws IOException, BadNameException { - File certFile = getCertFile(fingerprint); + 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 = certificateReaderBackend.readCertificate(bufferedIn); if (!certificate.getFingerprint().equals(fingerprint)) { // TODO: Figure out more suitable exception - throw new BadNameException(); + throw new BadDataException(); } return certificate; } @Override - public Certificate get(SpecialName specialName) throws IOException { - File certFile = getCertFile(specialName); + public Certificate getBySpecialName(String specialName) + throws IOException, BadNameException { + File certFile = resolver.getCertFileBySpecialName(specialName); if (!certFile.exists()) { return null; } @@ -108,8 +100,9 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException { - Certificate certificate = get(fingerprint); + public Certificate getByFingerprintIfChanged(String fingerprint, String tag) + throws IOException, BadNameException, BadDataException { + Certificate certificate = getByFingerprint(fingerprint); if (certificate.getTag().equals(tag)) { return null; } @@ -117,8 +110,9 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate getIfChanged(SpecialName specialName, String tag) throws IOException { - Certificate certificate = get(specialName); + public Certificate getBySpecialNameIfChanged(String specialName, String tag) + throws IOException, BadNameException { + Certificate certificate = getBySpecialName(specialName); if (certificate.getTag().equals(tag)) { return null; } @@ -126,7 +120,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException { + public Certificate insert(InputStream data, MergeCallback merge) + throws IOException, BadDataException, InterruptedException { writeLock.lockDirectory(); Certificate certificate = _insert(data, merge); @@ -136,7 +131,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException { + public Certificate tryInsert(InputStream data, MergeCallback merge) + throws IOException, BadDataException { if (!writeLock.tryLockDirectory()) { return null; } @@ -147,13 +143,14 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } - private Certificate _insert(InputStream data, MergeCallback merge) throws IOException, BadDataException { + private Certificate _insert(InputStream data, MergeCallback merge) + throws IOException, BadDataException { Certificate newCertificate = certificateReaderBackend.readCertificate(data); Certificate existingCertificate; File certFile; try { - existingCertificate = get(newCertificate.getFingerprint()); - certFile = getCertFile(newCertificate.getFingerprint()); + existingCertificate = getByFingerprint(newCertificate.getFingerprint()); + certFile = resolver.getCertFileByFingerprint(newCertificate.getFingerprint()); } catch (BadNameException e) { throw new BadDataException(); } @@ -167,7 +164,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return newCertificate; } - private void writeCertificate(Certificate certificate, File certFile) throws IOException { + private void writeCertificate(Certificate certificate, File certFile) + throws IOException { certFile.getParentFile().mkdirs(); if (!certFile.exists() && !certFile.createNewFile()) { throw new IOException("Could not create cert file " + certFile.getAbsolutePath()); @@ -187,7 +185,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException, InterruptedException { + public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + throws IOException, BadNameException, BadDataException, InterruptedException { writeLock.lockDirectory(); Certificate certificate = _insertSpecial(specialName, data, merge); @@ -197,7 +196,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi } @Override - public Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException { + public Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge) + throws IOException, BadNameException, BadDataException { if (!writeLock.tryLockDirectory()) { return null; } @@ -208,10 +208,11 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi return certificate; } - private Certificate _insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException { + private Certificate _insertSpecial(String specialName, InputStream data, MergeCallback merge) + throws IOException, BadNameException, BadDataException { Certificate newCertificate = certificateReaderBackend.readCertificate(data); - Certificate existingCertificate = get(specialName); - File certFile = getCertFile(specialName); + Certificate existingCertificate = getBySpecialName(specialName); + File certFile = resolver.getCertFileBySpecialName(specialName); if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) { newCertificate = merge.merge(newCertificate, existingCertificate); @@ -230,24 +231,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi // Constructor... wtf. { - for (SpecialName specialName : SpecialName.values()) { - File certFile = getCertFile(specialName); - if (certFile.exists()) { - certificateQueue.add( - new Lazy() { - @Override - Certificate get() { - try { - return certificateReaderBackend.readCertificate(new FileInputStream(certFile)); - } catch (IOException e) { - throw new AssertionError("File got deleted."); - } - } - }); - } - } - - File[] subdirectories = baseDirectory.listFiles(new FileFilter() { + File[] subdirectories = resolver.getBaseDirectory().listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.isDirectory() && file.getName().matches("^[a-f0-9]{2}$"); diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialName.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialName.java deleted file mode 100644 index 874d8996..00000000 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialName.java +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.cert_d; - -import java.util.HashMap; -import java.util.Map; - -/** - * Enum of known special names. - */ -public enum SpecialName { - /** - * Certificate acting as trust root. - * This certificate is used to delegate other trustworthy certificates and to bind pet names to certificates. - */ - TRUST_ROOT("trust-root"), - ; - - static Map MAP = new HashMap<>(); - - static { - for (SpecialName specialName : values()) { - MAP.put(specialName.getValue(), specialName); - } - } - - final String value; - - SpecialName(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public static SpecialName fromString(String value) { - return MAP.get(value); - } -} 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 new file mode 100644 index 00000000..83721f17 --- /dev/null +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import java.util.HashMap; +import java.util.Map; + +public class SpecialNames { + + private static final Map SPECIAL_NAMES = new HashMap<>(); + + static { + SPECIAL_NAMES.put("TRUST-ROOT", "trust-root"); + SPECIAL_NAMES.put("trust-root", "trust-root"); + } + + public static String lookupSpecialName(String specialName) { + return SPECIAL_NAMES.get(specialName); + } +} 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 new file mode 100644 index 00000000..8f00c47a --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/FilenameResolverTest.java @@ -0,0 +1,66 @@ +// 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.exception.BadNameException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class FilenameResolverTest { + + private File baseDir; + private FilenameResolver resolver; + + @BeforeEach + public void setup() throws IOException { + baseDir = Files.createTempDirectory("filenameresolver").toFile(); + baseDir.deleteOnExit(); + resolver = new FilenameResolver(baseDir); + } + + @Test + public void testGetFileForFingerprint1() throws BadNameException { + String fingerprint = "d1a66e1a23b182c9980f788cfbfcc82a015e7330"; + + File subDir = new File(baseDir, "d1"); + File expected = new File(subDir, "a66e1a23b182c9980f788cfbfcc82a015e7330"); + + assertEquals(resolver.getCertFileByFingerprint(fingerprint).getAbsolutePath(), expected.getAbsolutePath()); + } + + @Test + public void testGetFileForFingerprint2() throws BadNameException { + String fingerprint = "eb85bb5fa33a75e15e944e63f231550c4f47e38e"; + + File subDir = new File(baseDir, "eb"); + File expected = new File(subDir, "85bb5fa33a75e15e944e63f231550c4f47e38e"); + + assertEquals(resolver.getCertFileByFingerprint(fingerprint).getAbsolutePath(), expected.getAbsolutePath()); + } + + @Test + public void testGetFileForInvalidNonHexFingerprint() { + String invalidFingerprint = "thisisnothexadecimalthisisnothexadecimal"; + assertThrows(BadNameException.class, () -> resolver.getCertFileByFingerprint(invalidFingerprint)); + } + + @Test + public void testGetFileForInvalidWrongLengthFingerprint() { + String invalidFingerprint = "d1a66e1a23b182c9980f788cfbfcc82a015e73301234"; + assertThrows(BadNameException.class, () -> resolver.getCertFileByFingerprint(invalidFingerprint)); + } + + @Test + public void testGetFileForNullFingerprint() { + assertThrows(NullPointerException.class, () -> resolver.getCertFileByFingerprint(null)); + } +} diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/SpecialNamesTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/SpecialNamesTest.java new file mode 100644 index 00000000..83a2d5dc --- /dev/null +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/SpecialNamesTest.java @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.cert_d; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class SpecialNamesTest { + + @Test + public void bothTrustRootNotationsAreRecognized() { + assertEquals("trust-root", SpecialNames.lookupSpecialName("trust-root")); + assertEquals("trust-root", SpecialNames.lookupSpecialName("TRUST-ROOT")); + } + + @Test + public void testInvalidSpecialNameReturnsNull() { + assertNull(SpecialNames.lookupSpecialName("invalid")); + assertNull(SpecialNames.lookupSpecialName("trust root")); + assertNull(SpecialNames.lookupSpecialName("writelock")); + } +} 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 b88d5827..a7cee536 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 @@ -4,6 +4,8 @@ package pgp.certificate_store; +import java.io.IOException; + /** * Merge a given certificate (update) with an existing certificate. */ @@ -18,6 +20,6 @@ public interface MergeCallback { * @param existing optional already existing copy of the certificate * @return merged certificate */ - Certificate merge(Certificate data, Certificate existing); + Certificate merge(Certificate data, Certificate existing) throws IOException; } diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/SharedPGPCertificateDirectoryAdapter.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/SharedPGPCertificateDirectoryAdapter.java index 686f12b4..4e7ee7c4 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/SharedPGPCertificateDirectoryAdapter.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/SharedPGPCertificateDirectoryAdapter.java @@ -9,7 +9,7 @@ import java.io.InputStream; import java.util.Iterator; import pgp.cert_d.SharedPGPCertificateDirectory; -import pgp.cert_d.SpecialName; +import pgp.cert_d.SpecialNames; import pgp.cert_d.exception.BadDataException; import pgp.cert_d.exception.BadNameException; import pgp.certificate_store.Certificate; @@ -37,38 +37,42 @@ public class SharedPGPCertificateDirectoryAdapter @Override public Certificate getCertificate(String identifier) throws IOException { - SpecialName specialName = SpecialName.fromString(identifier); + String specialName = SpecialNames.lookupSpecialName(identifier); if (specialName != null) { try { - return directory.get(specialName); + return directory.getBySpecialName(specialName); } catch (BadNameException e) { throw new IllegalArgumentException("Unknown special name " + identifier, e); } } try { - return directory.get(identifier); + return directory.getByFingerprint(identifier); } catch (BadNameException e) { - throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e); + throw new IllegalArgumentException("Invalid fingerprint " + identifier, e); + } catch (BadDataException e) { + throw new IOException("Bad data.", e); } } @Override public Certificate getCertificateIfChanged(String identifier, String tag) throws IOException { - SpecialName specialName = SpecialName.fromString(identifier); + String specialName = SpecialNames.lookupSpecialName(identifier); if (specialName != null) { try { - return directory.getIfChanged(specialName, tag); + return directory.getBySpecialNameIfChanged(specialName, tag); } catch (BadNameException e) { throw new IllegalArgumentException("Unknown special name " + identifier, e); } } try { - return directory.getIfChanged(identifier, tag); + return directory.getByFingerprintIfChanged(identifier, tag); } catch (BadNameException e) { - throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e); + throw new IllegalArgumentException("Invalid fingerprint " + identifier, e); + } catch (BadDataException e) { + throw new IOException("Bad data.", e); } } @@ -96,12 +100,12 @@ public class SharedPGPCertificateDirectoryAdapter public Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException, InterruptedException { try { - SpecialName specialNameEnum = SpecialName.fromString(specialName); - if (specialNameEnum == null) { + String specialNameValidated = SpecialNames.lookupSpecialName(specialName); + if (specialNameValidated == null) { throw new IllegalArgumentException("Unknown special name " + specialName); } - return directory.insertSpecial(specialNameEnum, data, merge); + return directory.insertWithSpecialName(specialNameValidated, data, merge); } catch (BadNameException e) { throw new IllegalArgumentException("Unknown special name " + specialName); } catch (BadDataException e) { @@ -113,12 +117,12 @@ public class SharedPGPCertificateDirectoryAdapter public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge) throws IOException { try { - SpecialName specialNameEnum = SpecialName.fromString(specialName); - if (specialNameEnum == null) { + String specialNameValidated = SpecialNames.lookupSpecialName(specialName); + if (specialNameValidated == null) { throw new IllegalArgumentException("Unknown special name " + specialName); } - return directory.tryInsertSpecial(specialNameEnum, data, merge); + return directory.tryInsertWithSpecialName(specialNameValidated, data, merge); } catch (BadNameException e) { throw new IllegalArgumentException("Unknown special name " + specialName); } catch (BadDataException e) { 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 2e5006a9..7494757d 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.CertificateCertificateReader; +import org.pgpainless.certificate_store.CertificateReader; import org.pgpainless.key.OpenPgpFingerprint; import pgp.cert_d.FileLockingMechanism; import pgp.cert_d.LockingMechanism; @@ -37,8 +37,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; public class SharedPGPCertificateDirectoryTest { - Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class); - SharedPGPCertificateDirectory directory; + private static final Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class); + private File tempDir; + private SharedPGPCertificateDirectory directory; private static MergeCallback dummyMerge = new MergeCallback() { @Override @@ -49,29 +50,29 @@ public class SharedPGPCertificateDirectoryTest { @BeforeEach public void beforeEach() throws IOException, NotAStoreException { - File tempDir = Files.createTempDirectory("pgp.cert.d-").toFile(); + tempDir = Files.createTempDirectory("pgp.cert.d-").toFile(); tempDir.deleteOnExit(); - directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateCertificateReader()); + directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateReader()); } @Test public void simpleInsertGet() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException, BadNameException { - logger.info(() -> "simpleInsertGet: " + ((SharedPGPCertificateDirectoryImpl) directory).getBaseDirectory().getAbsolutePath()); + logger.info(() -> "simpleInsertGet: " + tempDir.getAbsolutePath()); PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null); PGPPublicKeyRing cert = PGPainless.extractCertificate(key); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); // standard case: get() is null - assertNull(directory.get(fingerprint.toString().toLowerCase())); + assertNull(directory.getByFingerprint(fingerprint.toString().toLowerCase())); // insert and check returned certs fingerprint Certificate certificate = directory.insert(certIn, dummyMerge); assertEquals(fingerprint.toString().toLowerCase(), certificate.getFingerprint()); // getIfChanged - assertNull(directory.getIfChanged(certificate.getFingerprint(), certificate.getTag())); - assertNotNull(directory.getIfChanged(certificate.getFingerprint(), "invalidTag")); + assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), certificate.getTag())); + assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), "invalidTag")); // tryInsert certIn = new ByteArrayInputStream(cert.getEncoded()); @@ -80,13 +81,12 @@ public class SharedPGPCertificateDirectoryTest { @Test public void tryInsertFailsWithLockedStore() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException { - SharedPGPCertificateDirectoryImpl fileDirectory = (SharedPGPCertificateDirectoryImpl) directory; - logger.info(() -> "tryInsertFailsWithLockedStore: " + fileDirectory.getBaseDirectory().getAbsolutePath()); + logger.info(() -> "tryInsertFailsWithLockedStore: " + tempDir.getAbsolutePath()); PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null); PGPPublicKeyRing cert = PGPainless.extractCertificate(key); ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); - File lockFile = new File(fileDirectory.getBaseDirectory(), "writelock"); + File lockFile = new File(tempDir, "writelock"); LockingMechanism lock = new FileLockingMechanism(lockFile); lock.lockDirectory(); diff --git a/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java b/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateReader.java similarity index 95% rename from pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java rename to pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateReader.java index f17ef896..e3c4ed45 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateCertificateReader.java +++ b/pgpainless-core/src/main/java/org/pgpainless/certificate_store/CertificateReader.java @@ -17,7 +17,7 @@ import org.pgpainless.key.OpenPgpFingerprint; import pgp.certificate_store.Certificate; import pgp.certificate_store.CertificateReaderBackend; -public class CertificateCertificateReader implements CertificateReaderBackend { +public class CertificateReader implements CertificateReaderBackend { @Override public Certificate readCertificate(InputStream inputStream) throws IOException {