2022-08-23 15:19:01 +02:00
|
|
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package pgp.cert_d;
|
|
|
|
|
|
|
|
import org.bouncycastle.util.io.Streams;
|
2022-08-24 23:18:54 +02:00
|
|
|
import org.junit.jupiter.api.Named;
|
2022-08-23 15:36:26 +02:00
|
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
2022-08-24 23:18:54 +02:00
|
|
|
import org.junit.jupiter.params.provider.Arguments;
|
2022-08-23 15:36:26 +02:00
|
|
|
import org.junit.jupiter.params.provider.MethodSource;
|
2022-08-24 13:04:28 +02:00
|
|
|
import pgp.cert_d.dummy.TestKeyMaterialMerger;
|
|
|
|
import pgp.cert_d.dummy.TestKeyMaterialReaderBackend;
|
2022-08-23 15:36:26 +02:00
|
|
|
import pgp.cert_d.subkey_lookup.InMemorySubkeyLookup;
|
2022-08-23 15:19:01 +02:00
|
|
|
import pgp.certificate_store.certificate.Certificate;
|
|
|
|
import pgp.certificate_store.certificate.Key;
|
|
|
|
import pgp.certificate_store.certificate.KeyMaterial;
|
2022-08-24 13:04:28 +02:00
|
|
|
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
2022-08-23 15:19:01 +02:00
|
|
|
import pgp.certificate_store.exception.BadDataException;
|
|
|
|
import pgp.certificate_store.exception.BadNameException;
|
2022-08-23 15:36:26 +02:00
|
|
|
import pgp.certificate_store.exception.NotAStoreException;
|
2022-08-23 15:19:01 +02:00
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
2022-08-23 15:36:26 +02:00
|
|
|
import java.io.File;
|
2022-08-23 15:19:01 +02:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.charset.Charset;
|
2022-08-23 15:36:26 +02:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.HashSet;
|
2022-08-23 15:19:01 +02:00
|
|
|
import java.util.Iterator;
|
2022-08-27 12:16:53 +02:00
|
|
|
import java.util.NoSuchElementException;
|
2022-08-23 15:36:26 +02:00
|
|
|
import java.util.Set;
|
|
|
|
import java.util.stream.Stream;
|
2022-08-23 15:19:01 +02:00
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
2022-08-24 13:04:28 +02:00
|
|
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
2022-08-23 15:19:01 +02:00
|
|
|
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.Assertions.assertTrue;
|
2022-08-24 23:18:54 +02:00
|
|
|
import static pgp.cert_d.TestKeys.CEDRIC_FP;
|
|
|
|
import static pgp.cert_d.TestKeys.HARRY_FP;
|
|
|
|
import static pgp.cert_d.TestKeys.RON_FP;
|
2022-08-23 15:19:01 +02:00
|
|
|
|
|
|
|
public class PGPCertificateDirectoryTest {
|
|
|
|
|
|
|
|
|
2022-08-24 13:04:28 +02:00
|
|
|
private static final KeyMaterialMerger merger = new TestKeyMaterialMerger();
|
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
private static Stream<Arguments> provideTestSubjects()
|
2022-08-24 13:04:28 +02:00
|
|
|
throws IOException, NotAStoreException {
|
2022-08-23 15:36:26 +02:00
|
|
|
PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory(
|
2022-08-23 15:19:01 +02:00
|
|
|
new TestKeyMaterialReaderBackend());
|
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
File tempDir = Files.createTempDirectory("pgp-cert-d-test").toFile();
|
|
|
|
tempDir.deleteOnExit();
|
|
|
|
PGPCertificateDirectory fileBased = PGPCertificateDirectories.fileBasedCertificateDirectory(
|
|
|
|
new TestKeyMaterialReaderBackend(),
|
|
|
|
tempDir,
|
|
|
|
new InMemorySubkeyLookup());
|
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
return Stream.of(
|
|
|
|
Arguments.of(Named.of("InMemoryCertificateDirectory", inMemory)),
|
|
|
|
Arguments.of(Named.of("FileBasedCertificateDirectory", fileBased)));
|
2022-08-23 15:36:26 +02:00
|
|
|
}
|
|
|
|
|
2022-08-27 12:16:53 +02:00
|
|
|
@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());
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
2022-08-25 11:26:12 +02:00
|
|
|
public void lockDirectoryAndTryInsertWillFail(PGPCertificateDirectory directory)
|
2022-08-24 13:04:28 +02:00
|
|
|
throws IOException, InterruptedException, BadDataException {
|
2022-08-23 15:19:01 +02:00
|
|
|
// Manually lock the dir
|
|
|
|
assertFalse(directory.backend.getLock().isLocked());
|
|
|
|
directory.backend.getLock().lockDirectory();
|
|
|
|
assertTrue(directory.backend.getLock().isLocked());
|
|
|
|
assertFalse(directory.backend.getLock().tryLockDirectory());
|
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
Certificate inserted = directory.tryInsert(TestKeys.getCedricCert(), merger);
|
2022-08-23 15:19:01 +02:00
|
|
|
assertNull(inserted);
|
|
|
|
|
|
|
|
directory.backend.getLock().releaseDirectory();
|
2022-08-24 23:18:54 +02:00
|
|
|
inserted = directory.tryInsert(TestKeys.getCedricCert(), merger);
|
2022-08-23 15:19:01 +02:00
|
|
|
assertNotNull(inserted);
|
|
|
|
}
|
|
|
|
|
2022-08-25 11:26:12 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void lockDirectoryAndTryInsertTrustRootWillFail(PGPCertificateDirectory directory)
|
|
|
|
throws IOException, InterruptedException, BadDataException {
|
|
|
|
// Manually lock the dir
|
|
|
|
assertFalse(directory.backend.getLock().isLocked());
|
|
|
|
directory.backend.getLock().lockDirectory();
|
|
|
|
assertTrue(directory.backend.getLock().isLocked());
|
|
|
|
|
|
|
|
KeyMaterial inserted = directory.tryInsertTrustRoot(TestKeys.getHarryKey(), merger);
|
|
|
|
assertNull(inserted);
|
|
|
|
|
|
|
|
directory.backend.getLock().releaseDirectory();
|
|
|
|
inserted = directory.tryInsertTrustRoot(TestKeys.getHarryKey(), merger);
|
|
|
|
assertNotNull(inserted);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void lockDirectoryAndTryInsertWithSpecialNameWillFail(PGPCertificateDirectory directory)
|
|
|
|
throws IOException, InterruptedException, BadDataException, BadNameException {
|
|
|
|
// Manually lock the dir
|
|
|
|
assertFalse(directory.backend.getLock().isLocked());
|
|
|
|
directory.backend.getLock().lockDirectory();
|
|
|
|
assertTrue(directory.backend.getLock().isLocked());
|
|
|
|
|
|
|
|
Certificate inserted = directory.tryInsertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger);
|
|
|
|
assertNull(inserted);
|
|
|
|
|
|
|
|
directory.backend.getLock().releaseDirectory();
|
|
|
|
inserted = directory.tryInsertWithSpecialName(SpecialNames.TRUST_ROOT, TestKeys.getHarryKey(), merger);
|
|
|
|
assertNotNull(inserted);
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void getByInvalidNameFails(PGPCertificateDirectory directory) {
|
2022-08-23 15:19:01 +02:00
|
|
|
assertThrows(BadNameException.class, () -> directory.getBySpecialName("invalid"));
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testInsertAndGetSingleCert(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException, BadNameException {
|
2022-08-27 12:16:53 +02:00
|
|
|
assertThrows(NoSuchElementException.class, () -> directory.getByFingerprint(CEDRIC_FP), "Empty directory MUST NOT contain certificate");
|
2022-08-23 15:19:01 +02:00
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger);
|
2022-08-23 15:19:01 +02:00
|
|
|
assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match");
|
|
|
|
|
|
|
|
Certificate get = directory.getByFingerprint(CEDRIC_FP);
|
|
|
|
assertEquals(CEDRIC_FP, get.getFingerprint(), "Fingerprint of retrieved cert MUST match");
|
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
byte[] expected = TestKeys.CEDRIC_CERT.getBytes(Charset.forName("UTF8"));
|
2022-08-23 15:19:01 +02:00
|
|
|
ByteArrayOutputStream actual = new ByteArrayOutputStream();
|
|
|
|
Streams.pipeAll(get.getInputStream(), actual);
|
|
|
|
assertArrayEquals(expected, actual.toByteArray(), "InputStream of cert MUST match what we gave in");
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testInsertAndGetTrustRootAndCert(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException {
|
2022-08-27 12:16:53 +02:00
|
|
|
assertThrows(NoSuchElementException.class, () -> directory.getTrustRoot());
|
2022-08-23 15:19:01 +02:00
|
|
|
|
|
|
|
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
2022-08-24 23:18:54 +02:00
|
|
|
TestKeys.getHarryKey(), merger);
|
2022-08-23 15:19:01 +02:00
|
|
|
assertNotNull(trustRootMaterial);
|
|
|
|
assertTrue(trustRootMaterial instanceof Key);
|
|
|
|
assertEquals(HARRY_FP, trustRootMaterial.getFingerprint());
|
|
|
|
|
|
|
|
Key trustRoot = (Key) directory.getTrustRoot();
|
|
|
|
assertEquals(HARRY_FP, trustRoot.getFingerprint());
|
|
|
|
Certificate trustRootCert = directory.getTrustRootCertificate();
|
|
|
|
assertEquals(HARRY_FP, trustRootCert.getFingerprint());
|
|
|
|
|
2022-08-24 23:18:54 +02:00
|
|
|
directory.tryInsert(TestKeys.getRonCert(), merger);
|
|
|
|
directory.insert(TestKeys.getCedricCert(), merger);
|
2022-08-23 15:19:01 +02:00
|
|
|
|
2022-08-23 15:36:26 +02:00
|
|
|
Set<String> expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP));
|
|
|
|
|
|
|
|
Set<String> actual = new HashSet<>();
|
2022-08-23 15:19:01 +02:00
|
|
|
Iterator<String> fingerprints = directory.fingerprints();
|
2022-08-23 15:36:26 +02:00
|
|
|
actual.add(fingerprints.next());
|
|
|
|
actual.add(fingerprints.next());
|
2022-08-23 15:19:01 +02:00
|
|
|
assertFalse(fingerprints.hasNext());
|
2022-08-23 15:36:26 +02:00
|
|
|
|
|
|
|
assertEquals(expected, actual);
|
2022-08-23 15:19:01 +02:00
|
|
|
}
|
2022-08-24 13:04:28 +02:00
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testGetTrustRootIfChanged(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException {
|
|
|
|
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
2022-08-24 23:18:54 +02:00
|
|
|
TestKeys.getHarryKey(), merger);
|
2022-08-24 13:04:28 +02:00
|
|
|
|
|
|
|
assertNotNull(trustRootMaterial.getTag());
|
|
|
|
Long tag = trustRootMaterial.getTag();
|
|
|
|
assertNull(directory.getTrustRootCertificateIfChanged(tag));
|
|
|
|
assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1));
|
|
|
|
|
|
|
|
Long oldTag = tag;
|
2022-08-27 12:16:53 +02:00
|
|
|
Thread.sleep(10);
|
2022-08-24 13:04:28 +02:00
|
|
|
// "update" key
|
|
|
|
trustRootMaterial = directory.insertTrustRoot(
|
2022-08-24 23:18:54 +02:00
|
|
|
TestKeys.getHarryKey(), merger);
|
2022-08-24 13:04:28 +02:00
|
|
|
tag = trustRootMaterial.getTag();
|
|
|
|
|
|
|
|
assertNotEquals(oldTag, tag);
|
|
|
|
assertNotNull(directory.getTrustRootCertificateIfChanged(oldTag));
|
|
|
|
}
|
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testGetBySpecialNameIfChanged(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException, BadNameException {
|
|
|
|
KeyMaterial specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT,
|
2022-08-24 23:18:54 +02:00
|
|
|
TestKeys.getHarryKey(), merger);
|
2022-08-24 13:04:28 +02:00
|
|
|
|
|
|
|
assertNotNull(specialName.getTag());
|
|
|
|
Long tag = specialName.getTag();
|
|
|
|
assertNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag));
|
|
|
|
assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag + 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testGetByFingerprintIfChanged(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException, BadNameException {
|
2022-08-24 23:18:54 +02:00
|
|
|
Certificate certificate = directory.insert(TestKeys.getCedricCert(), merger);
|
2022-08-24 13:04:28 +02:00
|
|
|
Long tag = certificate.getTag();
|
|
|
|
assertNotNull(tag);
|
|
|
|
|
|
|
|
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
|
|
|
|
assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1));
|
|
|
|
}
|
|
|
|
|
2022-08-27 13:11:11 +02:00
|
|
|
@ParameterizedTest
|
|
|
|
@MethodSource("provideTestSubjects")
|
|
|
|
public void testOverwriteTrustRoot(PGPCertificateDirectory directory)
|
|
|
|
throws BadDataException, IOException, InterruptedException {
|
|
|
|
directory.insertTrustRoot(TestKeys.getHarryKey(), merger);
|
|
|
|
assertEquals(HARRY_FP, directory.getTrustRoot().getFingerprint());
|
|
|
|
assertTrue(directory.getTrustRoot() instanceof Key);
|
2022-08-24 13:04:28 +02:00
|
|
|
|
2022-08-27 13:11:11 +02:00
|
|
|
directory.insertTrustRoot(TestKeys.getCedricCert(), merger);
|
|
|
|
assertEquals(CEDRIC_FP, directory.getTrustRoot().getFingerprint());
|
|
|
|
assertTrue(directory.getTrustRoot() instanceof Certificate);
|
2022-08-24 13:04:28 +02:00
|
|
|
}
|
2022-08-27 12:36:07 +02:00
|
|
|
|
2022-08-27 13:11:11 +02:00
|
|
|
@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());
|
2022-08-27 12:36:07 +02:00
|
|
|
}
|
|
|
|
|
2022-08-27 13:11:11 +02:00
|
|
|
@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());
|
2022-08-27 12:36:07 +02:00
|
|
|
|
2022-08-27 13:11:11 +02:00
|
|
|
directory.insert(TestKeys.getRonCert(), merger);
|
|
|
|
extracted = directory.getByFingerprint(RON_FP);
|
|
|
|
assertEquals(RON_FP, extracted.getFingerprint());
|
2022-08-27 12:36:07 +02:00
|
|
|
}
|
2022-08-27 13:11:11 +02:00
|
|
|
|
2022-08-23 15:19:01 +02:00
|
|
|
}
|