mirror of
https://codeberg.org/PGPainless/cert-d-java.git
synced 2024-11-21 23:22:04 +01:00
Add back support for getXIfChanged(Y, tag)
This commit is contained in:
parent
d050cb5516
commit
27f4598437
15 changed files with 423 additions and 101 deletions
|
@ -16,7 +16,7 @@ apply plugin: 'ru.vyarus.animalsniffer'
|
|||
|
||||
dependencies {
|
||||
// animal sniffer for ensuring Android API compatibility
|
||||
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature"
|
||||
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:8.0.0_r2@signature"
|
||||
|
||||
// JUnit
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
|
|
|
@ -33,6 +33,16 @@ public class PGPCertificateDirectory
|
|||
return backend.readByFingerprint(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getByFingerprintIfChanged(String fingerprint, long tag)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
if (tag != backend.getTagForFingerprint(fingerprint)) {
|
||||
return getByFingerprint(fingerprint);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Certificate getBySpecialName(String specialName)
|
||||
throws BadNameException, BadDataException, IOException {
|
||||
|
@ -43,6 +53,15 @@ public class PGPCertificateDirectory
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getBySpecialNameIfChanged(String specialName, long tag)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
if (tag != backend.getTagForSpecialName(specialName)) {
|
||||
return getBySpecialName(specialName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getTrustRootCertificate()
|
||||
throws IOException, BadDataException {
|
||||
|
@ -53,6 +72,15 @@ public class PGPCertificateDirectory
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getTrustRootCertificateIfChanged(long tag) throws IOException, BadDataException {
|
||||
try {
|
||||
return getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag);
|
||||
} catch (BadNameException e) {
|
||||
throw new AssertionError("'" + SpecialNames.TRUST_ROOT + "' is an implementation MUST");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Certificate> items() {
|
||||
return backend.readItems();
|
||||
|
@ -179,6 +207,10 @@ public class PGPCertificateDirectory
|
|||
|
||||
Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, BadDataException, BadNameException;
|
||||
|
||||
Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException;
|
||||
|
||||
Long getTagForSpecialName(String specialName) throws BadNameException, IOException;
|
||||
}
|
||||
|
||||
public interface LockingMechanism {
|
||||
|
|
|
@ -37,6 +37,16 @@ public class PGPCertificateStoreAdapter implements PGPCertificateStore {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getCertificateIfChanged(String identifier, Long tag)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
if (SpecialNames.lookupSpecialName(identifier) != null) {
|
||||
return directory.getBySpecialNameIfChanged(identifier, tag);
|
||||
} else {
|
||||
return directory.getByFingerprintIfChanged(identifier.toLowerCase(), tag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Certificate> getCertificatesBySubkeyId(long subkeyId)
|
||||
throws IOException, BadDataException {
|
||||
|
|
|
@ -16,12 +16,21 @@ public interface ReadOnlyPGPCertificateDirectory {
|
|||
Certificate getTrustRootCertificate()
|
||||
throws IOException, BadDataException;
|
||||
|
||||
Certificate getTrustRootCertificateIfChanged(long tag)
|
||||
throws IOException, BadDataException;
|
||||
|
||||
Certificate getByFingerprint(String fingerprint)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Certificate getByFingerprintIfChanged(String fingerprint, long tag)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Certificate getBySpecialName(String specialName)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Certificate getBySpecialNameIfChanged(String specialName, long tag)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Iterator<Certificate> items();
|
||||
|
||||
Iterator<String> fingerprints();
|
||||
|
|
|
@ -7,6 +7,7 @@ package pgp.cert_d.backend;
|
|||
import pgp.cert_d.PGPCertificateDirectory;
|
||||
import pgp.cert_d.SpecialNames;
|
||||
import pgp.certificate_store.certificate.Certificate;
|
||||
import pgp.certificate_store.certificate.Key;
|
||||
import pgp.certificate_store.certificate.KeyMaterial;
|
||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
||||
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
||||
|
@ -25,6 +26,9 @@ import java.io.InputStream;
|
|||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@ -160,10 +164,12 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
return null;
|
||||
}
|
||||
|
||||
long tag = getTagForFingerprint(fingerprint);
|
||||
|
||||
FileInputStream fileIn = new FileInputStream(certFile);
|
||||
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||
|
||||
Certificate certificate = reader.read(bufferedIn).asCertificate();
|
||||
Certificate certificate = reader.read(bufferedIn, tag).asCertificate();
|
||||
if (!certificate.getFingerprint().equals(fingerprint)) {
|
||||
// TODO: Figure out more suitable exception
|
||||
throw new BadDataException();
|
||||
|
@ -179,9 +185,11 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
return null;
|
||||
}
|
||||
|
||||
long tag = getTagForSpecialName(specialName);
|
||||
|
||||
FileInputStream fileIn = new FileInputStream(certFile);
|
||||
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||
KeyMaterial keyMaterial = reader.read(bufferedIn);
|
||||
KeyMaterial keyMaterial = reader.read(bufferedIn, tag);
|
||||
|
||||
return keyMaterial;
|
||||
}
|
||||
|
@ -214,7 +222,8 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
@Override
|
||||
Certificate get() throws BadDataException {
|
||||
try {
|
||||
Certificate certificate = reader.read(new FileInputStream(certFile)).asCertificate();
|
||||
long tag = getTag(certFile);
|
||||
Certificate certificate = reader.read(new FileInputStream(certFile), tag).asCertificate();
|
||||
if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) {
|
||||
throw new BadDataException();
|
||||
}
|
||||
|
@ -246,7 +255,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
|
||||
@Override
|
||||
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException {
|
||||
KeyMaterial newCertificate = reader.read(data);
|
||||
KeyMaterial newCertificate = reader.read(data, null);
|
||||
KeyMaterial existingCertificate;
|
||||
File certFile;
|
||||
try {
|
||||
|
@ -256,18 +265,22 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
throw new BadDataException();
|
||||
}
|
||||
|
||||
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||
if (existingCertificate != null) {
|
||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||
}
|
||||
|
||||
writeToFile(newCertificate.getInputStream(), certFile);
|
||||
|
||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
||||
if (newCertificate instanceof Key) {
|
||||
newCertificate = new Key((Key) newCertificate, tag);
|
||||
} else {
|
||||
newCertificate = new Certificate((Certificate) newCertificate, tag);
|
||||
}
|
||||
return newCertificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException {
|
||||
KeyMaterial newCertificate = reader.read(data);
|
||||
KeyMaterial newCertificate = reader.read(data, null);
|
||||
Certificate existingCertificate;
|
||||
File certFile;
|
||||
try {
|
||||
|
@ -277,18 +290,17 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
throw new BadDataException();
|
||||
}
|
||||
|
||||
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||
if (existingCertificate != null) {
|
||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||
}
|
||||
|
||||
writeToFile(newCertificate.getInputStream(), certFile);
|
||||
|
||||
return newCertificate.asCertificate();
|
||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
||||
return new Certificate(newCertificate.asCertificate(), tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException {
|
||||
KeyMaterial newCertificate = reader.read(data);
|
||||
KeyMaterial newCertificate = reader.read(data, null);
|
||||
KeyMaterial existingCertificate;
|
||||
File certFile;
|
||||
try {
|
||||
|
@ -298,16 +310,41 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
throw new BadDataException();
|
||||
}
|
||||
|
||||
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||
if (existingCertificate != null) {
|
||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||
}
|
||||
|
||||
writeToFile(newCertificate.getInputStream(), certFile);
|
||||
|
||||
return newCertificate.asCertificate();
|
||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
||||
return new Certificate(newCertificate.asCertificate(), tag);
|
||||
}
|
||||
|
||||
private void writeToFile(InputStream inputStream, File certFile)
|
||||
@Override
|
||||
public Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException {
|
||||
File file = resolver.getCertFileByFingerprint(fingerprint);
|
||||
return getTag(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTagForSpecialName(String specialName) throws BadNameException, IOException {
|
||||
File file = resolver.getCertFileBySpecialName(specialName);
|
||||
return getTag(file);
|
||||
}
|
||||
|
||||
private Long getTag(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
throw new IllegalArgumentException("File MUST exist.");
|
||||
}
|
||||
Path path = file.toPath();
|
||||
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
|
||||
// On UNIX file systems, for example, fileKey() will return the device ID and inode
|
||||
int fileId = attrs.fileKey().hashCode();
|
||||
long lastMod = attrs.lastModifiedTime().toMillis();
|
||||
|
||||
return lastMod + (11L * fileId);
|
||||
}
|
||||
|
||||
private long writeToFile(InputStream inputStream, File certFile)
|
||||
throws IOException {
|
||||
certFile.getParentFile().mkdirs();
|
||||
if (!certFile.exists() && !certFile.createNewFile()) {
|
||||
|
@ -324,6 +361,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
|||
|
||||
inputStream.close();
|
||||
fileOut.close();
|
||||
return getTag(certFile);
|
||||
}
|
||||
|
||||
public static class FilenameResolver {
|
||||
|
|
|
@ -7,6 +7,7 @@ package pgp.cert_d.backend;
|
|||
import pgp.cert_d.PGPCertificateDirectory;
|
||||
import pgp.cert_d.SpecialNames;
|
||||
import pgp.certificate_store.certificate.Certificate;
|
||||
import pgp.certificate_store.certificate.Key;
|
||||
import pgp.certificate_store.certificate.KeyMaterial;
|
||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
||||
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
||||
|
@ -91,7 +92,7 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
|||
@Override
|
||||
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
||||
throws BadDataException, IOException {
|
||||
KeyMaterial update = reader.read(data);
|
||||
KeyMaterial update = reader.read(data, null);
|
||||
KeyMaterial existing = null;
|
||||
try {
|
||||
existing = readBySpecialName(SpecialNames.TRUST_ROOT);
|
||||
|
@ -100,6 +101,11 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
KeyMaterial merged = merge.merge(update, existing);
|
||||
if (merged instanceof Key) {
|
||||
merged = new Key((Key) merged, System.currentTimeMillis());
|
||||
} else {
|
||||
merged = new Certificate((Certificate) merged, System.currentTimeMillis());
|
||||
}
|
||||
keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged);
|
||||
return merged;
|
||||
}
|
||||
|
@ -108,9 +114,10 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
|||
@Override
|
||||
public Certificate doInsert(InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, BadDataException {
|
||||
KeyMaterial update = reader.read(data);
|
||||
KeyMaterial update = reader.read(data, null);
|
||||
Certificate existing = readByFingerprint(update.getFingerprint());
|
||||
Certificate merged = merge.merge(update, existing).asCertificate();
|
||||
merged = new Certificate(merged, System.currentTimeMillis());
|
||||
certificateFingerprintMap.put(update.getFingerprint(), merged);
|
||||
return merged;
|
||||
}
|
||||
|
@ -118,10 +125,36 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
|||
@Override
|
||||
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, BadDataException, BadNameException {
|
||||
KeyMaterial keyMaterial = reader.read(data);
|
||||
KeyMaterial keyMaterial = reader.read(data, null);
|
||||
KeyMaterial existing = readBySpecialName(specialName);
|
||||
KeyMaterial merged = merge.merge(keyMaterial, existing);
|
||||
if (merged instanceof Key) {
|
||||
merged = new Key((Key) merged, System.currentTimeMillis());
|
||||
} else {
|
||||
merged = new Certificate((Certificate) merged, System.currentTimeMillis());
|
||||
}
|
||||
keyMaterialSpecialNameMap.put(specialName, merged);
|
||||
return merged.asCertificate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException {
|
||||
Certificate certificate = certificateFingerprintMap.get(fingerprint);
|
||||
if (certificate == null) {
|
||||
return null;
|
||||
}
|
||||
return certificate.getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTagForSpecialName(String specialName) throws BadNameException, IOException {
|
||||
if (SpecialNames.lookupSpecialName(specialName) == null) {
|
||||
throw new BadNameException("Invalid special name " + specialName);
|
||||
}
|
||||
KeyMaterial tagged = keyMaterialSpecialNameMap.get(specialName);
|
||||
if (tagged == null) {
|
||||
return null;
|
||||
}
|
||||
return tagged.getTag();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,17 @@
|
|||
package pgp.cert_d;
|
||||
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
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.certificate_store.certificate.Certificate;
|
||||
import pgp.certificate_store.certificate.Key;
|
||||
import pgp.certificate_store.certificate.KeyMaterial;
|
||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
||||
import pgp.certificate_store.exception.BadDataException;
|
||||
import pgp.certificate_store.exception.BadNameException;
|
||||
import pgp.certificate_store.exception.NotAStoreException;
|
||||
|
@ -18,6 +23,7 @@ import pgp.certificate_store.exception.NotAStoreException;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
|
@ -30,6 +36,7 @@ import java.util.stream.Stream;
|
|||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
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;
|
||||
|
@ -37,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
public class PGPCertificateDirectoryTest {
|
||||
|
||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||
private static final Charset UTF8 = Charset.forName("UTF8");
|
||||
|
||||
private static final String HARRY_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
|
@ -143,7 +151,10 @@ public class PGPCertificateDirectoryTest {
|
|||
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||
private static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021";
|
||||
|
||||
private static Stream<PGPCertificateDirectory> provideTestSubjects() throws IOException, NotAStoreException {
|
||||
private static final KeyMaterialMerger merger = new TestKeyMaterialMerger();
|
||||
|
||||
private static Stream<PGPCertificateDirectory> provideTestSubjects()
|
||||
throws IOException, NotAStoreException {
|
||||
PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory(
|
||||
new TestKeyMaterialReaderBackend());
|
||||
|
||||
|
@ -159,18 +170,19 @@ public class PGPCertificateDirectoryTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestSubjects")
|
||||
public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) throws IOException, InterruptedException, BadDataException {
|
||||
public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory)
|
||||
throws IOException, InterruptedException, BadDataException {
|
||||
// Manually lock the dir
|
||||
assertFalse(directory.backend.getLock().isLocked());
|
||||
directory.backend.getLock().lockDirectory();
|
||||
assertTrue(directory.backend.getLock().isLocked());
|
||||
assertFalse(directory.backend.getLock().tryLockDirectory());
|
||||
|
||||
Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||
Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
assertNull(inserted);
|
||||
|
||||
directory.backend.getLock().releaseDirectory();
|
||||
inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||
inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
assertNotNull(inserted);
|
||||
}
|
||||
|
||||
|
@ -188,7 +200,7 @@ public class PGPCertificateDirectoryTest {
|
|||
|
||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8));
|
||||
|
||||
Certificate certificate = directory.insert(bytesIn, new TestKeyMaterialMerger());
|
||||
Certificate certificate = directory.insert(bytesIn, merger);
|
||||
assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match");
|
||||
|
||||
Certificate get = directory.getByFingerprint(CEDRIC_FP);
|
||||
|
@ -207,7 +219,7 @@ public class PGPCertificateDirectoryTest {
|
|||
assertNull(directory.getTrustRoot());
|
||||
|
||||
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
||||
assertNotNull(trustRootMaterial);
|
||||
assertTrue(trustRootMaterial instanceof Key);
|
||||
assertEquals(HARRY_FP, trustRootMaterial.getFingerprint());
|
||||
|
@ -217,8 +229,8 @@ public class PGPCertificateDirectoryTest {
|
|||
Certificate trustRootCert = directory.getTrustRootCertificate();
|
||||
assertEquals(HARRY_FP, trustRootCert.getFingerprint());
|
||||
|
||||
directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||
directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||
directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), merger);
|
||||
directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
|
||||
Set<String> expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP));
|
||||
|
||||
|
@ -230,4 +242,104 @@ public class PGPCertificateDirectoryTest {
|
|||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestSubjects")
|
||||
public void testGetTrustRootIfChanged(PGPCertificateDirectory directory)
|
||||
throws BadDataException, IOException, InterruptedException {
|
||||
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
||||
|
||||
assertNotNull(trustRootMaterial.getTag());
|
||||
Long tag = trustRootMaterial.getTag();
|
||||
assertNull(directory.getTrustRootCertificateIfChanged(tag));
|
||||
assertNotNull(directory.getTrustRootCertificateIfChanged(tag + 1));
|
||||
|
||||
Long oldTag = tag;
|
||||
// "update" key
|
||||
trustRootMaterial = directory.insertTrustRoot(
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
||||
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,
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
||||
|
||||
assertNotNull(specialName.getTag());
|
||||
Long tag = specialName.getTag();
|
||||
assertNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag));
|
||||
assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, tag + 1));
|
||||
|
||||
Long oldTag = tag;
|
||||
// "update" key
|
||||
specialName = directory.insertWithSpecialName(SpecialNames.TRUST_ROOT,
|
||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
||||
tag = specialName.getTag();
|
||||
|
||||
assertNotEquals(oldTag, tag);
|
||||
assertNotNull(directory.getBySpecialNameIfChanged(SpecialNames.TRUST_ROOT, oldTag));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestSubjects")
|
||||
public void testGetByFingerprintIfChanged(PGPCertificateDirectory directory)
|
||||
throws BadDataException, IOException, InterruptedException, BadNameException {
|
||||
Certificate certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
Long tag = certificate.getTag();
|
||||
assertNotNull(tag);
|
||||
|
||||
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
|
||||
assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag + 1));
|
||||
|
||||
Long oldTag = tag;
|
||||
// "update" cert
|
||||
certificate = directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
tag = certificate.getTag();
|
||||
|
||||
assertNotEquals(oldTag, tag);
|
||||
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), tag));
|
||||
assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), oldTag));
|
||||
}
|
||||
|
||||
@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(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
package pgp.cert_d.dummy;
|
||||
|
||||
import pgp.certificate_store.certificate.KeyMaterial;
|
||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
package pgp.cert_d.dummy;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
|
@ -33,24 +33,22 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
|||
KeyFingerPrintCalculator fpCalc = new BcKeyFingerprintCalculator();
|
||||
|
||||
@Override
|
||||
public KeyMaterial read(InputStream data) throws IOException, BadDataException {
|
||||
public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(data, out);
|
||||
|
||||
try {
|
||||
Key key = readKey(new ByteArrayInputStream(out.toByteArray()));
|
||||
return key;
|
||||
return readKey(new ByteArrayInputStream(out.toByteArray()), tag);
|
||||
} catch (IOException | PGPException e) {
|
||||
try {
|
||||
Certificate certificate = readCertificate(new ByteArrayInputStream(out.toByteArray()));
|
||||
return certificate;
|
||||
return readCertificate(new ByteArrayInputStream(out.toByteArray()), tag);
|
||||
} catch (IOException e1) {
|
||||
throw new BadDataException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Key readKey(InputStream inputStream) throws IOException, PGPException {
|
||||
private Key readKey(InputStream inputStream, Long tag) throws IOException, PGPException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(inputStream, buffer);
|
||||
inputStream.close();
|
||||
|
@ -60,64 +58,21 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
|||
PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(decoderStream, fpCalc);
|
||||
PGPPublicKeyRing cert = extractCert(secretKeys);
|
||||
ByteArrayInputStream encoded = new ByteArrayInputStream(cert.getEncoded());
|
||||
Certificate certificate = readCertificate(encoded);
|
||||
Certificate certificate = readCertificate(encoded, tag);
|
||||
|
||||
return new Key() {
|
||||
@Override
|
||||
public Certificate getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
return certificate.getFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(buffer.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSubkeyIds() throws IOException {
|
||||
return certificate.getSubkeyIds();
|
||||
}
|
||||
};
|
||||
return new Key(buffer.toByteArray(), certificate, tag);
|
||||
}
|
||||
|
||||
private Certificate readCertificate(InputStream inputStream) throws IOException {
|
||||
private Certificate readCertificate(InputStream inputStream, Long tag) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(inputStream, buffer);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(buffer.toByteArray());
|
||||
InputStream decoderStream = PGPUtil.getDecoderStream(in);
|
||||
|
||||
PGPPublicKeyRing cert = new PGPPublicKeyRing(decoderStream, fpCalc);
|
||||
return new Certificate() {
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
return Hex.toHexString(cert.getPublicKey().getFingerprint()).toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(buffer.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSubkeyIds() throws IOException {
|
||||
return TestKeyMaterialReaderBackend.getSubkeyIds(cert);
|
||||
}
|
||||
};
|
||||
String fingerprint = Hex.toHexString(cert.getPublicKey().getFingerprint()).toLowerCase();
|
||||
List<Long> subKeyIds = getSubkeyIds(cert);
|
||||
return new Certificate(buffer.toByteArray(), fingerprint, subKeyIds, tag);
|
||||
}
|
||||
|
||||
private PGPPublicKeyRing extractCert(PGPSecretKeyRing secretKeys) {
|
||||
|
@ -126,8 +81,7 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
|||
while (publicKeyIterator.hasNext()) {
|
||||
publicKeyList.add(publicKeyIterator.next());
|
||||
}
|
||||
PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList);
|
||||
return publicKeyRing;
|
||||
return new PGPPublicKeyRing(publicKeyList);
|
||||
}
|
||||
|
||||
private static List<Long> getSubkeyIds(PGPKeyRing keyRing) {
|
|
@ -32,6 +32,23 @@ public interface PGPCertificateStore {
|
|||
Certificate getCertificate(String identifier)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
/**
|
||||
* Return the certificate that matches the given identifier, but only if it has been changed.
|
||||
* Whether it has been changed is determined by calculating the tag in the directory
|
||||
* (e.g. by looking at the inode and last modification date) and comparing the result with the tag provided by
|
||||
* the caller.
|
||||
*
|
||||
* @param identifier certificate identifier
|
||||
* @param tag tag by the caller
|
||||
* @return certificate if it has been changed, null otherwise
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws BadNameException if the identifier is invalid
|
||||
* @throws BadDataException if the certificate file contains invalid data
|
||||
*/
|
||||
Certificate getCertificateIfChanged(String identifier, Long tag)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} over all certificates in the store that contain a subkey with the given
|
||||
* subkey id.
|
||||
|
@ -42,7 +59,7 @@ public interface PGPCertificateStore {
|
|||
* @throws BadDataException if any of the certificate files contains invalid data
|
||||
*/
|
||||
Iterator<Certificate> getCertificatesBySubkeyId(long subkeyId)
|
||||
throws IOException, BadDataException;
|
||||
throws IOException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert a certificate into the store.
|
||||
|
|
|
@ -4,13 +4,67 @@
|
|||
|
||||
package pgp.certificate_store.certificate;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenPGP certificate (public key).
|
||||
*/
|
||||
public abstract class Certificate implements KeyMaterial {
|
||||
public class Certificate implements KeyMaterial {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final String fingerprint;
|
||||
private final List<Long> subkeyIds;
|
||||
private final Long tag;
|
||||
|
||||
/**
|
||||
* Certificate constructor.
|
||||
*
|
||||
* @param bytes encoding of the certificate
|
||||
* @param fingerprint fingerprint (lowercase hex characters)
|
||||
* @param subkeyIds list of subkey ids
|
||||
* @param tag tag
|
||||
*/
|
||||
public Certificate(byte[] bytes, String fingerprint, List<Long> subkeyIds, Long tag) {
|
||||
this.bytes = bytes;
|
||||
this.fingerprint = fingerprint;
|
||||
this.subkeyIds = subkeyIds;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor to assign a new tag to the {@link Certificate}.
|
||||
*
|
||||
* @param cert certificate
|
||||
* @param tag tag
|
||||
*/
|
||||
public Certificate(Certificate cert, Long tag) {
|
||||
this(cert.bytes, cert.fingerprint, cert.subkeyIds, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate asCertificate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSubkeyIds() {
|
||||
return subkeyIds;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,21 +4,74 @@
|
|||
|
||||
package pgp.certificate_store.certificate;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenPGP key (secret key).
|
||||
*/
|
||||
public abstract class Key implements KeyMaterial {
|
||||
public class Key implements KeyMaterial {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final Certificate certificate;
|
||||
private final Long tag;
|
||||
|
||||
/**
|
||||
* Key constructor.
|
||||
*
|
||||
* @param bytes encoding of the key
|
||||
* @param certificate associated certificate
|
||||
* @param tag tag
|
||||
*/
|
||||
public Key(byte[] bytes, Certificate certificate, Long tag) {
|
||||
this.bytes = bytes;
|
||||
this.certificate = certificate;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor to change the tag of both the {@link Key} and its {@link Certificate}.
|
||||
*
|
||||
* @param key key
|
||||
* @param tag tag
|
||||
*/
|
||||
public Key(Key key, Long tag) {
|
||||
this(key.bytes, new Certificate(key.certificate, tag), tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the certificate part of this OpenPGP key.
|
||||
*
|
||||
* @return OpenPGP certificate
|
||||
*/
|
||||
public abstract Certificate getCertificate();
|
||||
public Certificate getCertificate() {
|
||||
return new Certificate(certificate, getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
return certificate.getFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate asCertificate() {
|
||||
return getCertificate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSubkeyIds() {
|
||||
return certificate.getSubkeyIds();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
package pgp.certificate_store.certificate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -19,23 +18,34 @@ public interface KeyMaterial {
|
|||
*/
|
||||
String getFingerprint();
|
||||
|
||||
/**
|
||||
* Return the {@link Certificate} belonging to this key material.
|
||||
* If this is already a {@link Certificate}, return this.
|
||||
* If this is a {@link Key}, extract the {@link Certificate} and return it.
|
||||
*
|
||||
* @return certificate
|
||||
*/
|
||||
Certificate asCertificate();
|
||||
|
||||
/**
|
||||
* Return an {@link InputStream} of the binary representation of the secret key.
|
||||
*
|
||||
* @return input stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
InputStream getInputStream() throws IOException;
|
||||
InputStream getInputStream();
|
||||
|
||||
String getTag() throws IOException;
|
||||
/**
|
||||
* Return the tag belonging to this key material.
|
||||
* The tag can be used to keep an application cache in sync with what is in the directory.
|
||||
*
|
||||
* @return tag
|
||||
*/
|
||||
Long getTag();
|
||||
|
||||
/**
|
||||
* Return a {@link Set} containing key-ids of subkeys.
|
||||
*
|
||||
* @return subkeys
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
List<Long> getSubkeyIds() throws IOException;
|
||||
List<Long> getSubkeyIds();
|
||||
}
|
||||
|
|
|
@ -20,5 +20,5 @@ public interface KeyMaterialReaderBackend {
|
|||
* @throws IOException in case of an IO error
|
||||
* @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate
|
||||
*/
|
||||
KeyMaterial read(InputStream data) throws IOException, BadDataException;
|
||||
KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ allprojects {
|
|||
ext {
|
||||
shortVersion = '0.1.2'
|
||||
isSnapshot = true
|
||||
minAndroidSdk = 10
|
||||
minAndroidSdk = 26
|
||||
javaSourceCompatibility = 1.8
|
||||
bouncycastleVersion = '1.71'
|
||||
slf4jVersion = '1.7.36'
|
||||
|
|
Loading…
Reference in a new issue