diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/Entry.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/Entry.java index 439d83df..caff4bf8 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/Entry.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/Entry.java @@ -4,27 +4,44 @@ package pgp.cert_d.jdbc.sqlite; +/** + * Subkey-ID database entry. + */ public class Entry { private final int id; - private final String identifier; + private final String certificate; private final long subkeyId; - public Entry(int id, long subkeyId, String identifier) { + public Entry(int id, long subkeyId, String certificate) { this.id = id; this.subkeyId = subkeyId; - this.identifier = identifier; + this.certificate = certificate; } + /** + * Get the internal ID of this entry in the database. + * + * @return internal id + */ public int getId() { return id; } + /** + * Return the key-ID of the subkey. + * + * @return subkey id + */ public long getSubkeyId() { return subkeyId; } - public String getIdentifier() { - return identifier; + /** + * Return the fingerprint of the certificate the subkey belongs to. + * @return fingerprint + */ + public String getCertificate() { + return certificate; } } diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookup.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookup.java index 33b6bfbb..df7761b1 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookup.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/main/java/pgp/cert_d/jdbc/sqlite/SqliteSubkeyLookup.java @@ -28,16 +28,18 @@ public class SqliteSubkeyLookup implements SubkeyLookup { private static final String CREATE_TABLE_STMT = "" + "CREATE TABLE IF NOT EXISTS subkey_lookup (\n" + - " id integer PRIMARY KEY,\n" + - " identifier text NOT NULL,\n" + - " subkey_id integer NOT NULL,\n" + - " UNIQUE(identifier, subkey_id)\n" + + " id integer PRIMARY KEY,\n" + // id (internal to the database) + " certificate text NOT NULL,\n" + // certificate fingerprint + " subkey_id integer NOT NULL,\n" + // subkey id + " UNIQUE(certificate, subkey_id)\n" + ")"; private static final String INSERT_STMT = "" + - "INSERT INTO subkey_lookup(identifier, subkey_id) VALUES (?,?)"; + "INSERT INTO subkey_lookup(certificate, subkey_id) " + + "VALUES (?,?)"; private static final String QUERY_STMT = "" + - "SELECT * FROM subkey_lookup WHERE subkey_id=?"; + "SELECT * FROM subkey_lookup " + + "WHERE subkey_id=?"; public SqliteSubkeyLookup(String databaseURL) throws SQLException { this.databaseUrl = databaseURL; @@ -54,21 +56,27 @@ public class SqliteSubkeyLookup implements SubkeyLookup { return new SqliteSubkeyLookup("jdbc:sqlite:" + databaseFile.getAbsolutePath()); } - public void insert(long subkeyId, String identifier, boolean ignoreDuplicates) throws SQLException { + public void insertValues(String certificate, List subkeyIds) throws SQLException { try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(INSERT_STMT)) { - statement.setString(1, identifier); - statement.setLong(2, subkeyId); - statement.executeUpdate(); - } catch (SQLiteException e) { - // throw any exception, except: - // ignore unique constraint-related exceptions if we ignoreDuplicates - if (!ignoreDuplicates || e.getResultCode().code != SQLiteErrorCode.SQLITE_CONSTRAINT_UNIQUE.code) { - throw e; + for (long subkeyId : subkeyIds) { + try { + statement.setString(1, certificate); + statement.setLong(2, subkeyId); + statement.executeUpdate(); + } catch (SQLiteException e) { + // throw any exception, except: + // ignore unique constraint-related exceptions if we ignoreDuplicates + if (e.getResultCode().code == SQLiteErrorCode.SQLITE_CONSTRAINT_UNIQUE.code) { + // ignore duplicates + } else { + throw e; + } + } } } } - public List get(long subkeyId) throws SQLException { + public List selectValues(long subkeyId) throws SQLException { List results = new ArrayList<>(); try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(QUERY_STMT)) { statement.setLong(1, subkeyId); @@ -77,7 +85,7 @@ public class SqliteSubkeyLookup implements SubkeyLookup { Entry entry = new Entry( resultSet.getInt("id"), resultSet.getLong("subkey_id"), - resultSet.getString("identifier")); + resultSet.getString("certificate")); results.add(entry); } } @@ -86,27 +94,26 @@ public class SqliteSubkeyLookup implements SubkeyLookup { } @Override - public Set getIdentifiersForSubkeyId(long subkeyId) throws IOException { + public Set getCertificatesForSubkeyId(long subkeyId) throws IOException { try { - List entries = get(subkeyId); - Set identifiers = new HashSet<>(); + List entries = selectValues(subkeyId); + Set certificates = new HashSet<>(); for (Entry entry : entries) { - identifiers.add(entry.getIdentifier()); + certificates.add(entry.getCertificate()); } - return Collections.unmodifiableSet(identifiers); + return Collections.unmodifiableSet(certificates); } catch (SQLException e) { throw new IOException("Cannot query for subkey lookup entries.", e); } } @Override - public void storeIdentifierForSubkeyId(long subkeyId, String identifier) - throws IOException { + public void storeCertificateSubkeyIds(String certificate, List subkeyIds) throws IOException { try { - insert(subkeyId, identifier, true); + insertValues(certificate, subkeyIds); } catch (SQLException e) { - throw new IOException("Cannot store subkey lookup entry in database.", e); + throw new IOException("Cannot store subkey lookup entries in database.", e); } } } diff --git a/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/EntryTest.java b/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/EntryTest.java index 69b1a121..93236b1e 100644 --- a/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/EntryTest.java +++ b/pgp-cert-d-java-jdbc-sqlite-lookup/src/test/java/pgp/cert_d/jdbc/sqlite/EntryTest.java @@ -16,6 +16,6 @@ public class EntryTest { assertEquals(1, entry.getId()); assertEquals(123L, entry.getSubkeyId()); - assertEquals("eb85bb5fa33a75e15e944e63f231550c4f47e38e", entry.getIdentifier()); + assertEquals("eb85bb5fa33a75e15e944e63f231550c4f47e38e", entry.getCertificate()); } } 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 25e78a9e..9622d60e 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 @@ -5,7 +5,6 @@ package pgp.cert_d.jdbc.sqlite; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -34,40 +33,35 @@ public class SqliteSubkeyLookupTest { @Test public void simpleInsertAndGet() throws IOException { - lookup.storeIdentifierForSubkeyId(123L, "eb85bb5fa33a75e15e944e63f231550c4f47e38e"); - lookup.storeIdentifierForSubkeyId(234L, "eb85bb5fa33a75e15e944e63f231550c4f47e38e"); - lookup.storeIdentifierForSubkeyId(234L, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); + lookup.storeCertificateSubkeyIds("eb85bb5fa33a75e15e944e63f231550c4f47e38e", Arrays.asList(123L, 234L)); + lookup.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Collections.singletonList(234L)); - assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getIdentifiersForSubkeyId(123L)); + assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getCertificatesForSubkeyId(123L)); assertEquals( new HashSet<>(Arrays.asList("eb85bb5fa33a75e15e944e63f231550c4f47e38e", "d1a66e1a23b182c9980f788cfbfcc82a015e7330")), - lookup.getIdentifiersForSubkeyId(234L)); + lookup.getCertificatesForSubkeyId(234L)); } @Test public void getNonExistingSubkeyYieldsNull() throws IOException, SQLException { - assertTrue(lookup.get(6666666).isEmpty()); - assertTrue(lookup.getIdentifiersForSubkeyId(6666666).isEmpty()); + assertTrue(lookup.selectValues(6666666).isEmpty()); + assertTrue(lookup.getCertificatesForSubkeyId(6666666).isEmpty()); } @Test public void secondInstanceLookupTest() throws IOException, SQLException { - lookup.storeIdentifierForSubkeyId(1337, "eb85bb5fa33a75e15e944e63f231550c4f47e38e"); - assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getIdentifiersForSubkeyId(1337)); + lookup.storeCertificateSubkeyIds("eb85bb5fa33a75e15e944e63f231550c4f47e38e", Collections.singletonList(1337L)); + assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getCertificatesForSubkeyId(1337)); // do the lookup using a second db instance on the same file SqliteSubkeyLookup secondInstance = SqliteSubkeyLookup.forDatabaseFile(databaseFile); - assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), secondInstance.getIdentifiersForSubkeyId(1337)); + assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), secondInstance.getCertificatesForSubkeyId(1337)); } @Test public void ignoreInsertDuplicates() throws IOException { - lookup.storeIdentifierForSubkeyId(123L, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); + lookup.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Arrays.asList(123L, 234L)); // per default we ignore duplicates - lookup.storeIdentifierForSubkeyId(123L, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); - - // if we choose not to ignore duplicates, we raise an exception - assertThrows(SQLException.class, () -> - lookup.insert(123L, "d1a66e1a23b182c9980f788cfbfcc82a015e7330", false)); + lookup.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Arrays.asList(123L, 512L)); } } diff --git a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java index 9d603c1d..ec26fb61 100644 --- a/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java +++ b/pgp-cert-d-java/src/main/java/pgp/cert_d/InMemorySubkeyLookup.java @@ -4,20 +4,21 @@ package pgp.cert_d; -import pgp.certificate_store.SubkeyLookup; - import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import pgp.certificate_store.SubkeyLookup; + public class InMemorySubkeyLookup implements SubkeyLookup { private static final Map> subkeyMap = new HashMap<>(); @Override - public Set getIdentifiersForSubkeyId(long subkeyId) { + public Set getCertificatesForSubkeyId(long subkeyId) { Set identifiers = subkeyMap.get(subkeyId); if (identifiers == null) { return Collections.emptySet(); @@ -26,13 +27,15 @@ public class InMemorySubkeyLookup implements SubkeyLookup { } @Override - public void storeIdentifierForSubkeyId(long subkeyId, String identifier) { - Set identifiers = subkeyMap.get(subkeyId); - if (identifiers == null) { - identifiers = new HashSet<>(); - subkeyMap.put(subkeyId, identifiers); + public void storeCertificateSubkeyIds(String certificate, List subkeyIds) { + for (long subkeyId : subkeyIds) { + Set certificates = subkeyMap.get(subkeyId); + if (certificates == null) { + certificates = new HashSet<>(); + subkeyMap.put(subkeyId, certificates); + } + certificates.add(certificate); } - identifiers.add(identifier); } public void clear() { diff --git a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java index 226a7c15..d4821918 100644 --- a/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java +++ b/pgp-cert-d-java/src/test/java/pgp/cert_d/SubkeyLookupTest.java @@ -55,33 +55,33 @@ public class SubkeyLookupTest { public void testInsertGet(SubkeyLookup subject) throws IOException { // Initially all null - assertTrue(subject.getIdentifiersForSubkeyId(123).isEmpty()); - assertTrue(subject.getIdentifiersForSubkeyId(1337).isEmpty()); - assertTrue(subject.getIdentifiersForSubkeyId(420).isEmpty()); + assertTrue(subject.getCertificatesForSubkeyId(123).isEmpty()); + assertTrue(subject.getCertificatesForSubkeyId(1337).isEmpty()); + assertTrue(subject.getCertificatesForSubkeyId(420).isEmpty()); // Store one val, others still null - subject.storeIdentifierForSubkeyId(123, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); + subject.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Collections.singletonList(123L)); - assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getIdentifiersForSubkeyId(123)); - assertTrue(subject.getIdentifiersForSubkeyId(1337).isEmpty()); - assertTrue(subject.getIdentifiersForSubkeyId(420).isEmpty()); + assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getCertificatesForSubkeyId(123)); + assertTrue(subject.getCertificatesForSubkeyId(1337).isEmpty()); + assertTrue(subject.getCertificatesForSubkeyId(420).isEmpty()); // Store other val, first stays intact - subject.storeIdentifierForSubkeyId(1337, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); - subject.storeIdentifierForSubkeyId(420, "d1a66e1a23b182c9980f788cfbfcc82a015e7330"); + subject.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Collections.singletonList(1337L)); + subject.storeCertificateSubkeyIds("d1a66e1a23b182c9980f788cfbfcc82a015e7330", Collections.singletonList(420L)); - assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getIdentifiersForSubkeyId(123)); - assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getIdentifiersForSubkeyId(1337)); - assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getIdentifiersForSubkeyId(420)); + assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getCertificatesForSubkeyId(123)); + assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getCertificatesForSubkeyId(1337)); + assertEquals(Collections.singleton("d1a66e1a23b182c9980f788cfbfcc82a015e7330"), subject.getCertificatesForSubkeyId(420)); // add additional entry for subkey - subject.storeIdentifierForSubkeyId(123, "eb85bb5fa33a75e15e944e63f231550c4f47e38e"); + subject.storeCertificateSubkeyIds("eb85bb5fa33a75e15e944e63f231550c4f47e38e", Collections.singletonList(123L)); assertEquals( new HashSet<>(Arrays.asList("eb85bb5fa33a75e15e944e63f231550c4f47e38e", "d1a66e1a23b182c9980f788cfbfcc82a015e7330")), - subject.getIdentifiersForSubkeyId(123)); + subject.getCertificatesForSubkeyId(123)); } } diff --git a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java b/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java index 2e8cc60f..917b8fe1 100644 --- a/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java +++ b/pgp-certificate-store/src/main/java/pgp/certificate_store/SubkeyLookup.java @@ -5,6 +5,7 @@ package pgp.certificate_store; import java.io.IOException; +import java.util.List; import java.util.Set; public interface SubkeyLookup { @@ -16,13 +17,15 @@ public interface SubkeyLookup { * @param subkeyId subkey id * @return fingerprint of the certificate */ - Set getIdentifiersForSubkeyId(long subkeyId) throws IOException; + Set getCertificatesForSubkeyId(long subkeyId) throws IOException; /** - * Store a record of the subkey id that points to the fingerprint. + * Record, which certificate the subkey-ids in the list belong to. + * This method does not change the affiliation of subkey-ids not contained in the provided list. * - * @param subkeyId subkey id - * @param identifier fingerprint of the certificate + * @param certificate certificate fingerprint + * @param subkeyIds subkey ids + * @throws IOException in case of an IO error */ - void storeIdentifierForSubkeyId(long subkeyId, String identifier) throws IOException; + void storeCertificateSubkeyIds(String certificate, List subkeyIds) 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 bef57a78..5d40755e 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 @@ -6,7 +6,9 @@ package org.pgpainless.certificate_store; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Set; import pgp.cert_d.SharedPGPCertificateDirectory; @@ -106,18 +108,16 @@ public class SharedPGPCertificateDirectoryAdapter return; } String fingerprint = certificate.getFingerprint(); - for (Long subkeyId : certificate.getSubkeyIds()) { - storeIdentifierForSubkeyId(subkeyId, fingerprint); - } + storeCertificateSubkeyIds(fingerprint, new ArrayList<>(certificate.getSubkeyIds())); } @Override - public Set getIdentifiersForSubkeyId(long subkeyId) throws IOException { - return subkeyLookup.getIdentifiersForSubkeyId(subkeyId); + public Set getCertificatesForSubkeyId(long subkeyId) throws IOException { + return subkeyLookup.getCertificatesForSubkeyId(subkeyId); } @Override - public void storeIdentifierForSubkeyId(long subkeyId, String identifier) throws IOException { - subkeyLookup.storeIdentifierForSubkeyId(subkeyId, identifier); + public void storeCertificateSubkeyIds(String certificate, List subkeyIds) throws IOException { + subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds); } } 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 2a21efd1..ede971a7 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 @@ -84,7 +84,7 @@ public class SharedPGPCertificateDirectoryAdapterTest { Set subkeys = certificate.getSubkeyIds(); assertEquals(expectedSubkeys, subkeys); for (long subkey : subkeys) { - assertEquals(Collections.singleton(fingerprint), store.getIdentifiersForSubkeyId(subkey)); + assertEquals(Collections.singleton(fingerprint), store.getCertificatesForSubkeyId(subkey)); } Certificate retrieved = store.getCertificate(fingerprint); @@ -108,7 +108,7 @@ public class SharedPGPCertificateDirectoryAdapterTest { Set subkeys = certificate.getSubkeyIds(); assertEquals(3, subkeys.size()); for (long subkey : subkeys) { - assertEquals(Collections.singleton(fingerprint), store.getIdentifiersForSubkeyId(subkey)); + assertEquals(Collections.singleton(fingerprint), store.getCertificatesForSubkeyId(subkey)); } Certificate retrieved = store.getCertificate(fingerprint);