1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-16 01:01:08 +01:00

Some testing and SubkeyLookup mechanism

This commit is contained in:
Paul Schaub 2022-02-07 10:31:36 +01:00
parent 53e8c03b8a
commit 2e6a1dcbc5
13 changed files with 226 additions and 49 deletions

View file

@ -38,7 +38,7 @@ allprojects {
} }
// For library modules, enable android api compatibility check // For library modules, enable android api compatibility check
if (it.name != 'pgpainless-cli') { if (it.name != 'pgpainless-cli' && it.name != 'pgpainless-cert-d-cli' && name != 'pgp-cert-d-java') {
// animalsniffer // animalsniffer
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
dependencies { dependencies {

View file

@ -14,6 +14,7 @@ repositories {
dependencies { dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Logging // Logging

View file

@ -5,6 +5,7 @@
package pgp.cert_d; package pgp.cert_d;
import java.io.File; import java.io.File;
import java.nio.file.Paths;
public class BaseDirectoryProvider { public class BaseDirectoryProvider {
@ -18,29 +19,28 @@ public class BaseDirectoryProvider {
// return OS-specific default dir // return OS-specific default dir
String osName = System.getProperty("os.name", "generic") String osName = System.getProperty("os.name", "generic")
.toLowerCase(); .toLowerCase();
return getDefaultBaseDirForOS(osName, File.separator); return getDefaultBaseDirForOS(osName);
} }
public static File getDefaultBaseDirForOS(String osName, String separator) { public static File getDefaultBaseDirForOS(String osName) {
String STORE_NAME = "pgp.cert.d"; String STORE_NAME = "pgp.cert.d";
if (osName.contains("win")) { if (osName.contains("win")) {
String appData = System.getenv("APPDATA"); // %APPDATA%\Roaming\pgp.cert.d
String roaming = appData + separator + "Roaming"; return Paths.get(System.getenv("APPDATA"), "Roaming", STORE_NAME).toFile();
return new File(roaming, STORE_NAME);
} }
if (osName.contains("nux")) { if (osName.contains("nux")) {
// $XDG_DATA_HOME/pgp.cert.d
String xdg_data_home = System.getenv("XDG_DATA_HOME"); String xdg_data_home = System.getenv("XDG_DATA_HOME");
String rootPath = xdg_data_home; if (xdg_data_home != null) {
if (xdg_data_home == null) { return Paths.get(xdg_data_home, STORE_NAME).toFile();
rootPath = System.getProperty("user.home") + separator + ".local" + separator + "share";
} }
return new File(rootPath, STORE_NAME); // $HOME/.local/share/pgp.cert.d
return Paths.get(System.getProperty("user.home"), ".local", "share", STORE_NAME).toFile();
} }
if (osName.contains("mac")) { if (osName.contains("mac")) {
String home = System.getenv("HOME"); return Paths.get(System.getenv("HOME"), "Library", "Application Support", STORE_NAME).toFile();
return new File(home + separator + "Library" + separator + "Application Support", STORE_NAME);
} }
throw new IllegalArgumentException("Unknown OS " + osName); throw new IllegalArgumentException("Unknown OS " + osName);

View file

@ -75,6 +75,11 @@ public class CachingSharedPGPCertificateDirectoryWrapper
tagMap.clear(); tagMap.clear();
} }
@Override
public LockingMechanism getLock() {
return underlyingCertificateDirectory.getLock();
}
@Override @Override
public Certificate getByFingerprint(String fingerprint) public Certificate getByFingerprint(String fingerprint)
throws IOException, BadNameException, BadDataException { throws IOException, BadNameException, BadDataException {

View file

@ -0,0 +1,23 @@
package pgp.cert_d;
import java.util.HashMap;
import java.util.Map;
public class InMemorySubkeyLookup implements SubkeyLookup {
private static final Map<Long, String> subkeyMap = new HashMap<>();
@Override
public String getIdentifierForSubkeyId(long subkeyId) {
return subkeyMap.get(subkeyId);
}
@Override
public void storeIdentifierForSubkeyId(long subkeyId, String identifier) {
subkeyMap.put(subkeyId, identifier);
}
public void clear() {
subkeyMap.clear();
}
}

View file

@ -15,6 +15,8 @@ import pgp.certificate_store.MergeCallback;
public interface SharedPGPCertificateDirectory { public interface SharedPGPCertificateDirectory {
LockingMechanism getLock();
Certificate getByFingerprint(String fingerprint) Certificate getByFingerprint(String fingerprint)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;

View file

@ -69,6 +69,11 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
} }
} }
@Override
public LockingMechanism getLock() {
return writeLock;
}
@Override @Override
public Certificate getByFingerprint(String fingerprint) public Certificate getByFingerprint(String fingerprint)
throws IOException, BadNameException, BadDataException { throws IOException, BadNameException, BadDataException {

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.cert_d;
public interface SubkeyLookup {
/**
* Lookup the identifier of the certificate that contains the given subkey.
* If no record is found, return null.
*
* @param subkeyId subkey id
* @return identifier (fingerprint or special name) of the certificate
*/
String getIdentifierForSubkeyId(long subkeyId);
/**
* Store a record of the subkey id that points to the identifier.
*
* @param subkeyId subkey id
* @param identifier fingerprint or special name of the certificate
*/
void storeIdentifierForSubkeyId(long subkeyId, String identifier);
}

View file

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.cert_d;
public class SubkeyLookupImpl implements SubkeyLookup {
@Override
public String getIdentifierForSubkeyId(long subkeyId) {
return null;
}
@Override
public void storeIdentifierForSubkeyId(long subkeyId, String identifier) {
}
}

View file

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.cert_d;
import org.junit.jupiter.api.Test;
import java.io.File;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class BaseDirectoryProviderTest {
@Test
public void testGetDefaultBaseDir_Linux() {
assumeTrue(System.getProperty("os.name").equalsIgnoreCase("linux"));
File baseDir = BaseDirectoryProvider.getDefaultBaseDirForOS("linux");
assertTrue(baseDir.getAbsolutePath().endsWith("/.local/share/pgp.cert.d"));
}
}

View file

@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.cert_d;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class SubkeyLookupTest {
private static final List<SubkeyLookup> testSubjects = new ArrayList<>();
@BeforeAll
public static void setupLookupTestSubjects() {
testSubjects.add(new InMemorySubkeyLookup());
}
@AfterAll
public static void tearDownLookupTestSubjects() {
((InMemorySubkeyLookup) testSubjects.get(0)).clear();
}
private static Stream<SubkeyLookup> provideSubkeyLookupsForTest() {
return testSubjects.stream();
}
@ParameterizedTest
@MethodSource("provideSubkeyLookupsForTest")
public void testInsertGet(SubkeyLookup subject) {
// Initially all null
assertNull(subject.getIdentifierForSubkeyId(123));
assertNull(subject.getIdentifierForSubkeyId(1337));
assertNull(subject.getIdentifierForSubkeyId(420));
// Store one val, others still null
subject.storeIdentifierForSubkeyId(123, "trust-root");
assertEquals("trust-root", subject.getIdentifierForSubkeyId(123));
assertNull(subject.getIdentifierForSubkeyId(1337));
assertNull(subject.getIdentifierForSubkeyId(420));
// Store other val, first stays intact
subject.storeIdentifierForSubkeyId(1337, "d1a66e1a23b182c9980f788cfbfcc82a015e7330");
subject.storeIdentifierForSubkeyId(420, "d1a66e1a23b182c9980f788cfbfcc82a015e7330");
assertEquals("trust-root", subject.getIdentifierForSubkeyId(123));
assertEquals("d1a66e1a23b182c9980f788cfbfcc82a015e7330", subject.getIdentifierForSubkeyId(1337));
assertEquals("d1a66e1a23b182c9980f788cfbfcc82a015e7330", subject.getIdentifierForSubkeyId(420));
// overwrite existing
subject.storeIdentifierForSubkeyId(123, "d1a66e1a23b182c9980f788cfbfcc82a015e7330");
assertEquals("d1a66e1a23b182c9980f788cfbfcc82a015e7330", subject.getIdentifierForSubkeyId(123));
}
}

View file

@ -14,6 +14,7 @@ repositories {
dependencies { dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Logging // Logging

View file

@ -4,18 +4,29 @@
package org.pgpainless.cert_d; package org.pgpainless.cert_d;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.stream.Stream;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.certificate_store.CertificateReader; import org.pgpainless.certificate_store.CertificateReader;
import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpFingerprint;
import pgp.cert_d.CachingSharedPGPCertificateDirectoryWrapper;
import pgp.cert_d.FileLockingMechanism; import pgp.cert_d.FileLockingMechanism;
import pgp.cert_d.LockingMechanism;
import pgp.cert_d.SharedPGPCertificateDirectory; import pgp.cert_d.SharedPGPCertificateDirectory;
import pgp.cert_d.SharedPGPCertificateDirectoryImpl; import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
import pgp.cert_d.exception.BadDataException; import pgp.cert_d.exception.BadDataException;
@ -24,23 +35,8 @@ import pgp.cert_d.exception.NotAStoreException;
import pgp.certificate_store.Certificate; import pgp.certificate_store.Certificate;
import pgp.certificate_store.MergeCallback; import pgp.certificate_store.MergeCallback;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class SharedPGPCertificateDirectoryTest { public class SharedPGPCertificateDirectoryTest {
private static final Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class);
private File tempDir;
private SharedPGPCertificateDirectory directory;
private static MergeCallback dummyMerge = new MergeCallback() { private static MergeCallback dummyMerge = new MergeCallback() {
@Override @Override
public Certificate merge(Certificate data, Certificate existing) { public Certificate merge(Certificate data, Certificate existing) {
@ -48,16 +44,25 @@ public class SharedPGPCertificateDirectoryTest {
} }
}; };
@BeforeEach private static Stream<SharedPGPCertificateDirectory> provideTestSubjects() throws IOException, NotAStoreException {
public void beforeEach() throws IOException, NotAStoreException { return Stream.of(
tempDir = Files.createTempDirectory("pgp.cert.d-").toFile(); new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()),
tempDir.deleteOnExit(); new CachingSharedPGPCertificateDirectoryWrapper(
directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateReader()); new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()))
);
} }
@Test private static File tempDir() throws IOException {
public void simpleInsertGet() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException, BadNameException { File tempDir = Files.createTempDirectory("pgp.cert.d-").toFile();
logger.info(() -> "simpleInsertGet: " + tempDir.getAbsolutePath()); tempDir.deleteOnExit();
return tempDir;
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void simpleInsertGet(SharedPGPCertificateDirectory directory)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException,
BadDataException, InterruptedException, BadNameException {
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null); PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
PGPPublicKeyRing cert = PGPainless.extractCertificate(key); PGPPublicKeyRing cert = PGPainless.extractCertificate(key);
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert);
@ -79,21 +84,21 @@ public class SharedPGPCertificateDirectoryTest {
assertNotNull(directory.tryInsert(certIn, dummyMerge)); assertNotNull(directory.tryInsert(certIn, dummyMerge));
} }
@Test @ParameterizedTest
public void tryInsertFailsWithLockedStore() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException { @MethodSource("provideTestSubjects")
logger.info(() -> "tryInsertFailsWithLockedStore: " + tempDir.getAbsolutePath()); public void tryInsertFailsWithLockedStore(SharedPGPCertificateDirectory directory)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException,
BadDataException, InterruptedException {
assumeTrue(directory.getLock() instanceof FileLockingMechanism);
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null); PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
PGPPublicKeyRing cert = PGPainless.extractCertificate(key); PGPPublicKeyRing cert = PGPainless.extractCertificate(key);
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
File lockFile = new File(tempDir, "writelock"); directory.getLock().lockDirectory();
LockingMechanism lock = new FileLockingMechanism(lockFile);
lock.lockDirectory();
assertNull(directory.tryInsert(certIn, dummyMerge)); assertNull(directory.tryInsert(certIn, dummyMerge));
lock.releaseDirectory(); directory.getLock().releaseDirectory();
assertNotNull(directory.tryInsert(certIn, dummyMerge)); assertNotNull(directory.tryInsert(certIn, dummyMerge));
} }
} }