From 8a7bbdbf03f3e3d5efde29cfd1e1f353094e4760 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 16 Feb 2022 15:14:18 +0100 Subject: [PATCH] Integrate SubkeyLookup with CertificateStore --- .../pgp/certificate_store/Certificate.java | 3 + .../CertificateDirectory.java | 147 ++++++++++++++++++ .../certificate_store/CertificateStore.java | 140 +---------------- pgpainless-cert-d-cli/build.gradle | 1 + .../main/java/pgp/cert_d/cli/PGPCertDCli.java | 34 ++-- .../certificate_store/CertificateFactory.java | 14 ++ .../SharedPGPCertificateDirectoryAdapter.java | 39 ++++- ...redPGPCertificateDirectoryAdapterTest.java | 22 ++- 8 files changed, 240 insertions(+), 160 deletions(-) create mode 100644 pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java 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 b2d5015a..b7aca125 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 @@ -6,6 +6,7 @@ package pgp.certificate_store; import java.io.IOException; import java.io.InputStream; +import java.util.Set; public abstract class Certificate { /** @@ -30,4 +31,6 @@ public abstract class Certificate { * @return tag */ public abstract String getTag() throws IOException; + + 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 new file mode 100644 index 00000000..29f5998b --- /dev/null +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/CertificateDirectory.java @@ -0,0 +1,147 @@ +// 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 + */ + 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 + */ + 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 MergeCallback} 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. + * + * @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 + */ + Certificate insertCertificate(InputStream data, MergeCallback 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 + * 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 + */ + Certificate tryInsertCertificate(InputStream data, MergeCallback 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 + * 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. + * + * @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 + */ + Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback 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 + * 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 + */ + Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback 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(); +} 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 1e417e25..a8325ee2 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,144 +4,6 @@ package pgp.certificate_store; -import pgp.certificate_store.exception.BadDataException; -import pgp.certificate_store.exception.BadNameException; +public interface CertificateStore extends CertificateDirectory, SubkeyLookup { -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 { - - /** - * 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, 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 - */ - 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 MergeCallback} 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. - * - * @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 - */ - Certificate insertCertificate(InputStream data, MergeCallback 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 - * 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 - */ - Certificate tryInsertCertificate(InputStream data, MergeCallback 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 - * 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. - * - * @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 - */ - Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback 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 - * 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 - */ - Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback 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(); } diff --git a/pgpainless-cert-d-cli/build.gradle b/pgpainless-cert-d-cli/build.gradle index bc89b5f1..1df0ff16 100644 --- a/pgpainless-cert-d-cli/build.gradle +++ b/pgpainless-cert-d-cli/build.gradle @@ -20,6 +20,7 @@ dependencies { testImplementation "ch.qos.logback:logback-classic:$logbackVersion" implementation project(":pgpainless-cert-d") + implementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") // picocli for cli implementation "info.picocli:picocli:4.6.2" diff --git a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java index 19d873e7..8966c2e6 100644 --- a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java +++ b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/PGPCertDCli.java @@ -6,14 +6,18 @@ package pgp.cert_d.cli; import org.pgpainless.certificate_store.CertificateReader; import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter; +import pgp.cert_d.BaseDirectoryProvider; import pgp.cert_d.SharedPGPCertificateDirectoryImpl; import pgp.cert_d.cli.commands.Get; import pgp.cert_d.cli.commands.Import; +import pgp.cert_d.jdbc.sqlite.SqliteSubkeyLookup; +import pgp.certificate_store.SubkeyLookup; import pgp.certificate_store.exception.NotAStoreException; -import pgp.certificate_store.CertificateStore; +import pgp.certificate_store.CertificateDirectory; import picocli.CommandLine; import java.io.File; +import java.sql.SQLException; @CommandLine.Command( subcommands = { @@ -26,28 +30,30 @@ public class PGPCertDCli { @CommandLine.Option(names = "--base-directory", paramLabel = "DIRECTORY", description = "Overwrite the default certificate directory") File baseDirectory; - private static CertificateStore certificateStore; + private static CertificateDirectory certificateDirectory; private int executionStrategy(CommandLine.ParseResult parseResult) { try { initStore(); - } catch (NotAStoreException e) { + } catch (NotAStoreException | SQLException e) { return -1; } return new CommandLine.RunLast().execute(parseResult); } - private void initStore() throws NotAStoreException { + private void initStore() throws NotAStoreException, SQLException { SharedPGPCertificateDirectoryImpl certificateDirectory; - if (baseDirectory != null) { - certificateDirectory = new SharedPGPCertificateDirectoryImpl( - baseDirectory, - new CertificateReader()); - } else { - certificateDirectory = new SharedPGPCertificateDirectoryImpl( - new CertificateReader()); + SubkeyLookup subkeyLookup; + if (baseDirectory == null) { + baseDirectory = BaseDirectoryProvider.getDefaultBaseDir(); } - certificateStore = new SharedPGPCertificateDirectoryAdapter(certificateDirectory); + + certificateDirectory = new SharedPGPCertificateDirectoryImpl( + baseDirectory, + new CertificateReader()); + subkeyLookup = SqliteSubkeyLookup.forDatabaseFile(new File(baseDirectory, "_pgpainless_subkey_map.db")); + + PGPCertDCli.certificateDirectory = new SharedPGPCertificateDirectoryAdapter(certificateDirectory, subkeyLookup); } public static void main(String[] args) { @@ -57,7 +63,7 @@ public class PGPCertDCli { .execute(args); } - public static CertificateStore getCertificateDirectory() { - return certificateStore; + public static CertificateDirectory getCertificateDirectory() { + return certificateDirectory; } } diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java index 0ef57930..e4b40c11 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java @@ -4,6 +4,7 @@ package org.pgpainless.certificate_store; +import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.util.encoders.Base64; import org.pgpainless.key.OpenPgpFingerprint; @@ -14,6 +15,9 @@ import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; public class CertificateFactory { @@ -40,6 +44,16 @@ public class CertificateFactory { digest.update(publicKeyRing.getEncoded()); return Base64.toBase64String(digest.digest()); } + + @Override + public Set getSubkeyIds() throws IOException { + Set keyIds = new HashSet<>(); + Iterator keys = publicKeyRing.getPublicKeys(); + while (keys.hasNext()) { + keyIds.add(keys.next().getKeyID()); + } + return keyIds; + } }; } } 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 66d94f5e..bef57a78 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 @@ -7,31 +7,36 @@ package org.pgpainless.certificate_store; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; +import java.util.Set; import pgp.cert_d.SharedPGPCertificateDirectory; import pgp.cert_d.SpecialNames; import pgp.certificate_store.Certificate; +import pgp.certificate_store.CertificateDirectory; import pgp.certificate_store.CertificateStore; import pgp.certificate_store.MergeCallback; +import pgp.certificate_store.SubkeyLookup; import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadNameException; /** * Adapter class used to adapt the {@link SharedPGPCertificateDirectory} for use with - * {@link CertificateStore}. + * {@link CertificateDirectory}. */ public class SharedPGPCertificateDirectoryAdapter implements CertificateStore { private final SharedPGPCertificateDirectory directory; + private final SubkeyLookup subkeyLookup; /** - * Create an adapter to use {@link SharedPGPCertificateDirectory} objects as {@link CertificateStore CertificateStores}. + * Create an adapter to use {@link SharedPGPCertificateDirectory} objects as {@link CertificateDirectory CertificateStores}. * * @param directory directory instance */ - public SharedPGPCertificateDirectoryAdapter(SharedPGPCertificateDirectory directory) { + public SharedPGPCertificateDirectoryAdapter(SharedPGPCertificateDirectory directory, SubkeyLookup subkeyLookup) { this.directory = directory; + this.subkeyLookup = subkeyLookup; } @Override @@ -61,13 +66,17 @@ public class SharedPGPCertificateDirectoryAdapter @Override public Certificate insertCertificate(InputStream data, MergeCallback merge) throws IOException, InterruptedException, BadDataException { - return directory.insert(data, merge); + Certificate certificate = directory.insert(data, merge); + storeIdentifierForSubkeys(certificate); + return certificate; } @Override public Certificate tryInsertCertificate(InputStream data, MergeCallback merge) throws IOException, BadDataException { - return directory.tryInsert(data, merge); + Certificate certificate = directory.tryInsert(data, merge); + storeIdentifierForSubkeys(certificate); + return certificate; } @Override @@ -91,4 +100,24 @@ public class SharedPGPCertificateDirectoryAdapter public Iterator getFingerprints() { return directory.fingerprints(); } + + private void storeIdentifierForSubkeys(Certificate certificate) throws IOException { + if (certificate == null) { + return; + } + String fingerprint = certificate.getFingerprint(); + for (Long subkeyId : certificate.getSubkeyIds()) { + storeIdentifierForSubkeyId(subkeyId, fingerprint); + } + } + + @Override + public Set getIdentifiersForSubkeyId(long subkeyId) throws IOException { + return subkeyLookup.getIdentifiersForSubkeyId(subkeyId); + } + + @Override + public void storeIdentifierForSubkeyId(long subkeyId, String identifier) throws IOException { + subkeyLookup.storeIdentifierForSubkeyId(subkeyId, identifier); + } } diff --git a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryAdapterTest.java b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryAdapterTest.java index 905bcde4..2a21efd1 100644 --- a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryAdapterTest.java +++ b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryAdapterTest.java @@ -16,6 +16,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -26,17 +28,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pgpainless.certificate_store.CertificateReader; import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter; +import pgp.cert_d.InMemorySubkeyLookup; import pgp.cert_d.SharedPGPCertificateDirectoryImpl; +import pgp.certificate_store.CertificateStore; 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.CertificateStore; public class SharedPGPCertificateDirectoryAdapterTest { private static final String testCertificate = "98330462069cc616092b06010401da470f010107400db5906b09f701ab1f7f96087eedab6ba44c02fcbd2470137cfeacac5a2d032db405416c696365888f0413160a0041050262069cc609906f054e826378552516a104505b134a7e62f0f154ec3d036f054e8263785525029e01029b01059602030100048b09080705950a09080b0299010000a12600fd117925c0f2192ef5b2a44e3d3038e2a7ce5ba0343fc2dfb661a3a46d1276fb380100bf2872e7e36b63f61ae3556464c4a04344e7d36e0d7313e623effb0290ce0b0fb8380462069cc6120a2b06010401975501050101074034ffd523242385fe92034a5e326a82f4edff614516cc1028ca91fb653557f25b0301080788750418160a001d050262069cc6029e01029b0c059602030100048b09080705950a09080b000a09106f054e8263785525391400ff4eb85df8ddfc15e94c9cf28bc0aa9d0426b571ca64c5421be5889d5410d8632f00fd1ac5e9aed683e711282489d8980222d2ceff15c5ce0499fcb36716d850749406b8330462069cc616092b06010401da470f0101074058f296fb7ce456039856144db677f14018963a8bfd281c84aaeebe7e14df8f1c88d50418160a007d050262069cc6029e01029b02059602030100048b09080705950a09080b5f200419160a0006050262069cc6000a09108119c86e0a4c6dc73a7600ff5e25427da84d824cc3f8890bc6bd037f423f610006e1249b1aad3d7f70ac47a100fc08e67a6a945c1feec301df9dc27e7ea4e61d107d0720e814eea1dc4f1da20a08000a09106f054e8263785525359700ff4ce78cf267c261468322de906118d4f003ceefa72fa3b86119e26f99be3727fc00fe3895207c4aac814549f0189d2f494f5b1fcee7f6da344e63a0c32743b216b406"; private static final String testCertFingerprint = "505b134a7e62f0f154ec3d036f054e8263785525"; + private static final long testCertificateSubkey1 = 7999886635015099685L; + private static final long testCertificateSubkey2 = -5375724347241457298L; + private static final long testCertificateSubkey3 = -9144057193454342713L; private SharedPGPCertificateDirectoryAdapter adapter; private CertificateStore store; @@ -44,7 +50,8 @@ public class SharedPGPCertificateDirectoryAdapterTest { @BeforeEach public void setupInstance() throws IOException, NotAStoreException { adapter = new SharedPGPCertificateDirectoryAdapter( - new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader())); + new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()), + new InMemorySubkeyLookup()); store = adapter; } @@ -73,6 +80,12 @@ public class SharedPGPCertificateDirectoryAdapterTest { Certificate certificate = store.insertCertificate(byteIn, (data, existing) -> data); assertEquals(fingerprint, certificate.getFingerprint()); + Set expectedSubkeys = new HashSet<>(Arrays.asList(testCertificateSubkey1, testCertificateSubkey2, testCertificateSubkey3)); + Set subkeys = certificate.getSubkeyIds(); + assertEquals(expectedSubkeys, subkeys); + for (long subkey : subkeys) { + assertEquals(Collections.singleton(fingerprint), store.getIdentifiersForSubkeyId(subkey)); + } Certificate retrieved = store.getCertificate(fingerprint); assertNotNull(retrieved); @@ -92,6 +105,11 @@ public class SharedPGPCertificateDirectoryAdapterTest { Certificate certificate = store.tryInsertCertificate(byteIn, (data, existing) -> data); assertEquals(fingerprint, certificate.getFingerprint()); + Set subkeys = certificate.getSubkeyIds(); + assertEquals(3, subkeys.size()); + for (long subkey : subkeys) { + assertEquals(Collections.singleton(fingerprint), store.getIdentifiersForSubkeyId(subkey)); + } Certificate retrieved = store.getCertificate(fingerprint); assertNotNull(retrieved);