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)); } 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/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_Denvironment 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.dor, if
$XDG_DATA_HOMEis 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() { 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..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; @@ -33,6 +44,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 +64,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 +83,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
trust-root. + * If no such certificate is found,
nullis 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
tagto calculate if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
nullis 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
tagto calculate, if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
nullis 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,
nullis 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
tagto calculate, if the certificate might have changed. + * If the computed tag equals the given tag, the certificate has not changed, so
nullis 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
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-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