Compare commits

..

No commits in common. "main" and "0.2.0" have entirely different histories.
main ... 0.2.0

12 changed files with 60 additions and 269 deletions

View file

@ -5,13 +5,6 @@ SPDX-License-Identifier: CC0-1.0
# Cert-D-Java Changelog # Cert-D-Java Changelog
## 0.2.2
- Bump Bouncy Castle to `1.75`
- Bump `sqlite-jdbc` to `3.42.0.0`
## 0.2.1
- Throw `NoSuchElementException` when querying non-existent certificates
## 0.2.0 ## 0.2.0
- `pgp-certificate-store`: - `pgp-certificate-store`:
- Rework `Certificate`, `Key` to inherit from `KeyMaterial` - Rework `Certificate`, `Key` to inherit from `KeyMaterial`

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0
This repository contains a number of modules defining OpenPGP certificate storage for Java and Android applications. This repository contains a number of modules defining OpenPGP certificate storage for Java and Android applications.
The module [pgp-certificate-store](pgp-certificate-store) defines generalized The module [pgp-certificate-store](pgp-certificate-store] defines generalized
interfaces for OpenPGP Certificate storage. interfaces for OpenPGP Certificate storage.
It can be used by applications and libraries such as It can be used by applications and libraries such as
[PGPainless](https://pgpainless.org/) for certificate management. [PGPainless](https://pgpainless.org/) for certificate management.

View file

@ -32,7 +32,7 @@ dependencies {
testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup") testImplementation project(":pgp-cert-d-java-jdbc-sqlite-lookup")
testImplementation "org.bouncycastle:bcprov-jdk15to18:$bouncycastleVersion" testImplementation "org.bouncycastle:bcprov-jdk15to18:$bouncycastleVersion"
testImplementation "org.bouncycastle:bcpg-jdk15to18:$bouncyPgVersion" testImplementation "org.bouncycastle:bcpg-jdk15to18:$bouncycastleVersion"
} }
animalsniffer { animalsniffer {

View file

@ -15,8 +15,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -48,17 +46,13 @@ public class PGPCertificateDirectory
if (!openPgpV4FingerprintPattern.matcher(fingerprint).matches()) { if (!openPgpV4FingerprintPattern.matcher(fingerprint).matches()) {
throw new BadNameException(); throw new BadNameException();
} }
Certificate certificate = backend.readByFingerprint(fingerprint); return backend.readByFingerprint(fingerprint);
if (certificate == null) {
throw new NoSuchElementException();
}
return certificate;
} }
@Override @Override
public Certificate getByFingerprintIfChanged(String fingerprint, long tag) public Certificate getByFingerprintIfChanged(String fingerprint, long tag)
throws IOException, BadNameException, BadDataException { throws IOException, BadNameException, BadDataException {
if (!Objects.equals(tag, backend.getTagForFingerprint(fingerprint))) { if (tag != backend.getTagForFingerprint(fingerprint)) {
return getByFingerprint(fingerprint); return getByFingerprint(fingerprint);
} }
return null; return null;
@ -72,13 +66,13 @@ public class PGPCertificateDirectory
if (keyMaterial != null) { if (keyMaterial != null) {
return keyMaterial.asCertificate(); return keyMaterial.asCertificate();
} }
throw new NoSuchElementException(); return null;
} }
@Override @Override
public Certificate getBySpecialNameIfChanged(String specialName, long tag) public Certificate getBySpecialNameIfChanged(String specialName, long tag)
throws IOException, BadNameException, BadDataException { throws IOException, BadNameException, BadDataException {
if (!Objects.equals(tag, backend.getTagForSpecialName(specialName))) { if (tag != backend.getTagForSpecialName(specialName)) {
return getBySpecialName(specialName); return getBySpecialName(specialName);
} }
return null; return null;
@ -127,11 +121,7 @@ public class PGPCertificateDirectory
@Override @Override
public KeyMaterial getTrustRoot() throws IOException, BadDataException { public KeyMaterial getTrustRoot() throws IOException, BadDataException {
try { try {
KeyMaterial keyMaterial = backend.readBySpecialName(SpecialNames.TRUST_ROOT); return backend.readBySpecialName(SpecialNames.TRUST_ROOT);
if (keyMaterial == null) {
throw new NoSuchElementException();
}
return keyMaterial;
} catch (BadNameException e) { } catch (BadNameException e) {
throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is implementation MUST"); throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is implementation MUST");
} }

View file

@ -10,7 +10,6 @@ import pgp.certificate_store.exception.BadNameException;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException;
/** /**
* Interface for a read-only OpenPGP certificate directory. * Interface for a read-only OpenPGP certificate directory.
@ -20,12 +19,12 @@ public interface ReadOnlyPGPCertificateDirectory {
/** /**
* Get the trust-root certificate. This is a certificate which is stored under the special name * Get the trust-root certificate. This is a certificate which is stored under the special name
* <pre>trust-root</pre>. * <pre>trust-root</pre>.
* If no such certificate is found, <pre>null</pre> is returned.
* *
* @return trust-root certificate * @return trust-root certificate
* *
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getTrustRootCertificate() Certificate getTrustRootCertificate()
throws IOException, BadDataException; throws IOException, BadDataException;
@ -37,25 +36,24 @@ public interface ReadOnlyPGPCertificateDirectory {
* Otherwise. the changed certificate is returned. * Otherwise. the changed certificate is returned.
* *
* @param tag tag * @param tag tag
* @return changed certificate, or null if the certificate is unchanged. * @return changed certificate, or null if the certificate is unchanged or not found.
* *
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getTrustRootCertificateIfChanged(long tag) Certificate getTrustRootCertificateIfChanged(long tag)
throws IOException, BadDataException; throws IOException, BadDataException;
/** /**
* Get the certificate identified by the given fingerprint. * Get the certificate identified by the given fingerprint.
* If no such certificate is found, return <pre>null</pre>.
* *
* @param fingerprint lower-case fingerprint of the certificate * @param fingerprint lower-case fingerprint of the certificate
* @return certificate * @return certificate or null if no such certificate has been found
* *
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws BadNameException if the fingerprint is malformed * @throws BadNameException if the fingerprint is malformed
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getByFingerprint(String fingerprint) Certificate getByFingerprint(String fingerprint)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;
@ -68,26 +66,25 @@ public interface ReadOnlyPGPCertificateDirectory {
* *
* @param fingerprint lower-case fingerprint of the certificate * @param fingerprint lower-case fingerprint of the certificate
* @param tag tag * @param tag tag
* @return certificate or null if the certificate has not been changed * @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 IOException in case of an IO error
* @throws BadNameException if the fingerprint is malformed * @throws BadNameException if the fingerprint is malformed
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getByFingerprintIfChanged(String fingerprint, long tag) Certificate getByFingerprintIfChanged(String fingerprint, long tag)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;
/** /**
* Get the certificate identified by the given special name. * Get the certificate identified by the given special name.
* If no such certificate is found, <pre>null</pre> is returned.
* *
* @param specialName special name * @param specialName special name
* @return certificate * @return certificate or null
* *
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws BadNameException if the special name is not known * @throws BadNameException if the special name is not known
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getBySpecialName(String specialName) Certificate getBySpecialName(String specialName)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;
@ -105,7 +102,6 @@ public interface ReadOnlyPGPCertificateDirectory {
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws BadNameException if the special name is not known * @throws BadNameException if the special name is not known
* @throws BadDataException if the certificate contains bad data * @throws BadDataException if the certificate contains bad data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getBySpecialNameIfChanged(String specialName, long tag) Certificate getBySpecialNameIfChanged(String specialName, long tag)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;

View file

@ -33,7 +33,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -349,7 +348,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
private Long getTag(File file) throws IOException { private Long getTag(File file) throws IOException {
if (!file.exists()) { if (!file.exists()) {
throw new NoSuchElementException(); throw new IllegalArgumentException("File MUST exist.");
} }
Path path = file.toPath(); Path path = file.toPath();
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

View file

@ -18,7 +18,7 @@ public class BaseDirectoryProviderTest {
public void testGetDefaultBaseDir_Linux() { public void testGetDefaultBaseDir_Linux() {
assumeTrue(System.getProperty("os.name").equalsIgnoreCase("linux")); assumeTrue(System.getProperty("os.name").equalsIgnoreCase("linux"));
File baseDir = BaseDirectoryProvider.getDefaultBaseDirForOS("linux"); File baseDir = BaseDirectoryProvider.getDefaultBaseDirForOS("linux");
assertTrue(baseDir.getAbsolutePath().endsWith("pgp.cert.d")); assertTrue(baseDir.getAbsolutePath().endsWith("/.local/share/pgp.cert.d"));
} }
@Test @Test

View file

@ -1,131 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.cert_d;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Test;
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.cert_d.subkey_lookup.SubkeyLookup;
import pgp.certificate_store.certificate.Certificate;
import pgp.certificate_store.certificate.KeyMaterialMerger;
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
import pgp.certificate_store.exception.BadDataException;
import pgp.certificate_store.exception.BadNameException;
import pgp.certificate_store.exception.NotAStoreException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
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;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class FileBasedPGPCertificateDirectoryTest {
private static final KeyMaterialMerger merger = new TestKeyMaterialMerger();
@Test
public void testFileBasedCertificateDirectoryTagChangesWhenFileChanges()
throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException {
File tempDir = Files.createTempDirectory("file-based-changes").toFile();
tempDir.deleteOnExit();
PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory(
new TestKeyMaterialReaderBackend(),
tempDir,
new InMemorySubkeyLookup());
FileBasedCertificateDirectoryBackend.FilenameResolver resolver =
new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir);
// Insert certificate
Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger);
Long tag = certificate.getTag();
assertNotNull(tag);
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
Long oldTag = tag;
Thread.sleep(10);
// Change the file on disk directly, this invalidates the tag due to changed modification date
File certFile = resolver.getCertFileByFingerprint(certificate.getFingerprint());
FileOutputStream fileOut = new FileOutputStream(certFile);
Streams.pipeAll(certificate.getInputStream(), fileOut);
fileOut.write("\n".getBytes());
fileOut.close();
// Old invalidated tag indicates a change, so the modified certificate is returned
certificate = directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag);
assertNotNull(certificate);
// new tag is valid
tag = certificate.getTag();
assertNotEquals(oldTag, tag);
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
}
@Test
public void fileBasedStoreInWriteProtectedAreaThrows() {
File root = new File("/");
assumeTrue(root.exists(), "This test only runs on unix-like systems");
File baseDirectory = new File(root, "pgp.cert.d");
assumeFalse(baseDirectory.mkdirs(), "This test assumes that we cannot create dirs in /");
KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend();
SubkeyLookup lookup = new InMemorySubkeyLookup();
assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory(
reader, baseDirectory, lookup));
}
@Test
public void fileBasedStoreOnFileThrows()
throws IOException {
File tempDir = Files.createTempDirectory("containsAFile").toFile();
tempDir.deleteOnExit();
File baseDir = new File(tempDir, "pgp.cert.d");
baseDir.createNewFile(); // this is a file, not a dir
KeyMaterialReaderBackend reader = new TestKeyMaterialReaderBackend();
SubkeyLookup lookup = new InMemorySubkeyLookup();
assertThrows(NotAStoreException.class, () -> PGPCertificateDirectories.fileBasedCertificateDirectory(
reader, baseDir, lookup));
}
@Test
public void testCertificateStoredUnderWrongFingerprintThrowsBadData()
throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException {
File tempDir = Files.createTempDirectory("wrong-fingerprint").toFile();
tempDir.deleteOnExit();
PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory(
new TestKeyMaterialReaderBackend(),
tempDir,
new InMemorySubkeyLookup());
FileBasedCertificateDirectoryBackend.FilenameResolver resolver =
new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir);
// Insert Rons certificate
directory.insert(TestKeys.getRonCert(), merger);
// Copy Rons cert to Cedrics cert file
File ronCert = resolver.getCertFileByFingerprint(TestKeys.RON_FP);
FileInputStream inputStream = new FileInputStream(ronCert);
File cedricCert = resolver.getCertFileByFingerprint(TestKeys.CEDRIC_FP);
cedricCert.getParentFile().mkdirs();
cedricCert.createNewFile();
FileOutputStream outputStream = new FileOutputStream(cedricCert);
Streams.pipeAll(inputStream, outputStream);
inputStream.close();
outputStream.close();
// Reading cedrics cert will fail, as it has Rons fingerprint
assertThrows(BadDataException.class, () -> directory.getByFingerprint(TestKeys.CEDRIC_FP));
}
}

View file

@ -6,9 +6,11 @@ package pgp.cert_d;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import pgp.cert_d.backend.FileBasedCertificateDirectoryBackend;
import pgp.cert_d.dummy.TestKeyMaterialMerger; import pgp.cert_d.dummy.TestKeyMaterialMerger;
import pgp.cert_d.dummy.TestKeyMaterialReaderBackend; import pgp.cert_d.dummy.TestKeyMaterialReaderBackend;
import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup; import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup;
@ -22,13 +24,13 @@ import pgp.certificate_store.exception.NotAStoreException;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -66,55 +68,6 @@ public class PGPCertificateDirectoryTest {
Arguments.of(Named.of("FileBasedCertificateDirectory", fileBased))); Arguments.of(Named.of("FileBasedCertificateDirectory", fileBased)));
} }
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentCertByFingerprintThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getByFingerprint("0000000000000000000000000000000000000000"));
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentCertByFingerprintIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getByFingerprintIfChanged("0000000000000000000000000000000000000000", 12));
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentCertBySpecialNameThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getBySpecialName(SpecialNames.TRUST_ROOT));
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentCertBySpecialNameIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, 12));
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentTrustRootThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getTrustRoot());
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentTrustRootIfChangedThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getTrustRootCertificateIfChanged(12));
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void getNonExistentTrustRootCertificateThrowsNoSuchElementException(PGPCertificateDirectory directory) {
assertThrows(NoSuchElementException.class, () ->
directory.getTrustRootCertificate());
}
@ParameterizedTest @ParameterizedTest
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void lockDirectoryAndTryInsertWillFail(PGPCertificateDirectory directory) public void lockDirectoryAndTryInsertWillFail(PGPCertificateDirectory directory)
@ -177,7 +130,7 @@ public class PGPCertificateDirectoryTest {
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void testInsertAndGetSingleCert(PGPCertificateDirectory directory) public void testInsertAndGetSingleCert(PGPCertificateDirectory directory)
throws BadDataException, IOException, InterruptedException, BadNameException { throws BadDataException, IOException, InterruptedException, BadNameException {
assertThrows(NoSuchElementException.class, () -> directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate"); assertNull(directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate");
Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger); Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger);
assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match"); assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match");
@ -195,7 +148,7 @@ public class PGPCertificateDirectoryTest {
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void testInsertAndGetTrustRootAndCert(PGPCertificateDirectory directory) public void testInsertAndGetTrustRootAndCert(PGPCertificateDirectory directory)
throws BadDataException, IOException, InterruptedException { throws BadDataException, IOException, InterruptedException {
assertThrows(NoSuchElementException.class, () -> directory.getTrustRoot()); assertNull(directory.getTrustRoot());
KeyMaterial trustRootMaterial = directory.insertTrustRoot( KeyMaterial trustRootMaterial = directory.insertTrustRoot(
TestKeys.getHarryKey(), merger); TestKeys.getHarryKey(), merger);
@ -235,7 +188,6 @@ public class PGPCertificateDirectoryTest {
assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1)); assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1));
Long oldTag = tag; Long oldTag = tag;
Thread.sleep(10);
// "update" key // "update" key
trustRootMaterial = directory.insertTrustRoot( trustRootMaterial = directory.insertTrustRoot(
TestKeys.getHarryKey(), merger); TestKeys.getHarryKey(), merger);
@ -270,42 +222,38 @@ public class PGPCertificateDirectoryTest {
assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1)); assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1));
} }
@ParameterizedTest @Test
@MethodSource("provideTestSubjects") public void testFileBasedCertificateDirectoryTagChangesWhenFileChanges() throws IOException, NotAStoreException, BadDataException, InterruptedException, BadNameException {
public void testOverwriteTrustRoot(PGPCertificateDirectory directory) File tempDir = Files.createTempDirectory("file-based-changes").toFile();
throws BadDataException, IOException, InterruptedException { tempDir.deleteOnExit();
directory.insertTrustRoot(TestKeys.getHarryKey(), merger); PGPCertificateDirectory directory = PGPCertificateDirectories.fileBasedCertificateDirectory(
assertEquals(HARRY_FP, directory.getTrustRoot().getFingerprint()); new TestKeyMaterialReaderBackend(),
assertTrue(directory.getTrustRoot() instanceof Key); tempDir,
new InMemorySubkeyLookup());
FileBasedCertificateDirectoryBackend.FilenameResolver resolver =
new FileBasedCertificateDirectoryBackend.FilenameResolver(tempDir);
directory.insertTrustRoot(TestKeys.getCedricCert(), merger); // Insert certificate
assertEquals(CEDRIC_FP, directory.getTrustRoot().getFingerprint()); Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger);
assertTrue(directory.getTrustRoot() instanceof Certificate); Long tag = certificate.getTag();
assertNotNull(tag);
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
Long oldTag = tag;
// Change the file on disk directly, this invalidates the tag due to changed modification date
File certFile = resolver.getCertFileByFingerprint(certificate.getFingerprint());
FileOutputStream fileOut = new FileOutputStream(certFile);
Streams.pipeAll(certificate.getInputStream(), fileOut);
fileOut.close();
// Old invalidated tag indicates a change, so the modified certificate is returned
certificate = directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag);
assertNotNull(certificate);
// new tag is valid
tag = certificate.getTag();
assertNotEquals(oldTag, tag);
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
} }
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void testOverwriteSpecialName(PGPCertificateDirectory directory)
throws BadDataException, IOException, InterruptedException, BadNameException {
directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getRonCert(), merger);
KeyMaterial bySpecialName = directory.getBySpecialName(SpecialNames.TRUST_ROOT);
assertEquals(RON_FP, bySpecialName.getFingerprint());
directory.insertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger);
assertEquals(HARRY_FP, directory.getBySpecialName(SpecialNames.TRUST_ROOT).getFingerprint());
}
@ParameterizedTest
@MethodSource("provideTestSubjects")
public void testOverwriteByFingerprint(PGPCertificateDirectory directory)
throws BadDataException, IOException, InterruptedException, BadNameException {
directory.insert(TestKeys.getRonCert(), merger);
Certificate extracted = directory.getByFingerprint(RON_FP);
assertEquals(RON_FP, extracted.getFingerprint());
directory.insert(TestKeys.getRonCert(), merger);
extracted = directory.getByFingerprint(RON_FP);
assertEquals(RON_FP, extracted.getFingerprint());
}
} }

View file

@ -17,7 +17,6 @@ import pgp.certificate_store.exception.BadNameException;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
@ -53,7 +52,7 @@ public class PGPCertificateStoreAdapterTest {
@Test @Test
public void testInsertGetCertificate() public void testInsertGetCertificate()
throws BadDataException, IOException, InterruptedException, BadNameException { throws BadDataException, IOException, InterruptedException, BadNameException {
assertThrows(NoSuchElementException.class, () -> adapter.getCertificate(TestKeys.CEDRIC_FP)); assertNull(adapter.getCertificate(TestKeys.CEDRIC_FP));
assertFalse(adapter.getCertificates().hasNext()); assertFalse(adapter.getCertificates().hasNext());
Certificate certificate = adapter.insertCertificate(TestKeys.getCedricCert(), merger); Certificate certificate = adapter.insertCertificate(TestKeys.getCedricCert(), merger);
@ -71,7 +70,7 @@ public class PGPCertificateStoreAdapterTest {
@Test @Test
public void testInsertGetTrustRoot() public void testInsertGetTrustRoot()
throws BadDataException, BadNameException, IOException, InterruptedException { throws BadDataException, BadNameException, IOException, InterruptedException {
assertThrows(NoSuchElementException.class, () -> adapter.getCertificate(SpecialNames.TRUST_ROOT)); assertNull(adapter.getCertificate(SpecialNames.TRUST_ROOT));
Certificate certificate = adapter.insertCertificateBySpecialName( Certificate certificate = adapter.insertCertificateBySpecialName(
SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger); SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger);

View file

@ -12,7 +12,6 @@ import pgp.certificate_store.exception.BadNameException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException;
/** /**
* Interface for an OpenPGP certificate (public key) store. * Interface for an OpenPGP certificate (public key) store.
@ -21,6 +20,7 @@ public interface PGPCertificateStore {
/** /**
* Return the certificate that matches the given identifier. * Return the certificate that matches the given identifier.
* If no matching certificate can be found, return null.
* *
* @param identifier identifier for a certificate. * @param identifier identifier for a certificate.
* @return certificate or null * @return certificate or null
@ -28,7 +28,6 @@ public interface PGPCertificateStore {
* @throws IOException in case of an IO-error * @throws IOException in case of an IO-error
* @throws BadNameException if the identifier is invalid * @throws BadNameException if the identifier is invalid
* @throws BadDataException if the certificate file contains invalid data * @throws BadDataException if the certificate file contains invalid data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getCertificate(String identifier) Certificate getCertificate(String identifier)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;
@ -46,7 +45,6 @@ public interface PGPCertificateStore {
* @throws IOException in case of an IO-error * @throws IOException in case of an IO-error
* @throws BadNameException if the identifier is invalid * @throws BadNameException if the identifier is invalid
* @throws BadDataException if the certificate file contains invalid data * @throws BadDataException if the certificate file contains invalid data
* @throws NoSuchElementException if no such certificate is found
*/ */
Certificate getCertificateIfChanged(String identifier, Long tag) Certificate getCertificateIfChanged(String identifier, Long tag)
throws IOException, BadNameException, BadDataException; throws IOException, BadNameException, BadDataException;

View file

@ -4,16 +4,15 @@
allprojects { allprojects {
ext { ext {
shortVersion = '0.2.3' shortVersion = '0.2.0'
isSnapshot = true isSnapshot = false
minAndroidSdk = 26 minAndroidSdk = 26
animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2" animalsnifferSignatureVersion = "$minAndroidSdk:8.0.0_r2"
javaSourceCompatibility = 1.8 javaSourceCompatibility = 1.8
bouncycastleVersion = '1.75' bouncycastleVersion = '1.71'
bouncyPgVersion = "$bouncycastleVersion"
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'
logbackVersion = '1.2.11' logbackVersion = '1.2.11'
junitVersion = '5.8.2' junitVersion = '5.8.2'
sqliteJdbcVersion = '3.42.0.0' sqliteJdbcVersion = '3.36.0.3'
} }
} }