Add back support for getXIfChanged(Y, tag)

This commit is contained in:
Paul Schaub 2022-08-24 13:04:28 +02:00
parent d050cb5516
commit 27f4598437
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
15 changed files with 423 additions and 101 deletions

View file

@ -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"

View file

@ -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 {

View file

@ -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 {

View file

@ -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();

View file

@ -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 {

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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'