Implement storing of trust-root keys

This commit is contained in:
Paul Schaub 2022-07-04 19:42:02 +02:00
parent 304d6c29e4
commit 17d2f45e83
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 192 additions and 19 deletions

View file

@ -6,11 +6,14 @@ package pgp.cert_d;
import pgp.certificate_store.CertificateReaderBackend;
import pgp.certificate_store.CertificateMerger;
import pgp.certificate_store.KeyReaderBackend;
public abstract class BackendProvider {
public abstract CertificateReaderBackend provideCertificateReaderBackend();
public abstract KeyReaderBackend provideKeyReaderBackend();
public abstract CertificateMerger provideDefaultMergeCallback();
}

View file

@ -4,6 +4,8 @@
package pgp.cert_d;
import pgp.certificate_store.Key;
import pgp.certificate_store.KeyMerger;
import pgp.certificate_store.exception.BadDataException;
import pgp.certificate_store.exception.BadNameException;
import pgp.certificate_store.Certificate;
@ -21,8 +23,10 @@ import java.util.Map;
public class CachingSharedPGPCertificateDirectoryWrapper
implements SharedPGPCertificateDirectory {
private static final Map<String, String> tagMap = new HashMap<>();
private static final Map<String, String> certTagMap = new HashMap<>();
private static final Map<String, String> keyTagMap = new HashMap<>();
private static final Map<String, Certificate> certificateMap = new HashMap<>();
private static final Map<String, Key> keyMap = new HashMap<>();
private final SharedPGPCertificateDirectory underlyingCertificateDirectory;
public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) {
@ -38,12 +42,26 @@ public class CachingSharedPGPCertificateDirectoryWrapper
private void remember(String identifier, Certificate certificate) {
certificateMap.put(identifier, certificate);
try {
tagMap.put(identifier, certificate.getTag());
certTagMap.put(identifier, certificate.getTag());
} catch (IOException e) {
tagMap.put(identifier, null);
certTagMap.put(identifier, null);
}
}
/**
* Store the given key under the given identifier into the cache.
*
* @param identifier fingerprint or special name
* @param key key
*/
private void remember(String identifier, Key key) {
keyMap.put(identifier, key);
try {
keyTagMap.put(identifier, key.getTag());
} catch (IOException e) {
keyTagMap.put(identifier, null);
}
}
/**
* Returns true, if the cached tag differs from the provided tag.
*
@ -51,8 +69,13 @@ public class CachingSharedPGPCertificateDirectoryWrapper
* @param tag tag
* @return true if cached tag differs, false otherwise
*/
private boolean tagChanged(String identifier, String tag) {
String tack = tagMap.get(identifier);
private boolean certTagChanged(String identifier, String tag) {
String tack = certTagMap.get(identifier);
return !tagEquals(tag, tack);
}
private boolean keyTagChanged(String identifier, String tag) {
String tack = keyTagMap.get(identifier);
return !tagEquals(tag, tack);
}
@ -72,7 +95,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper
*/
public void invalidate() {
certificateMap.clear();
tagMap.clear();
certTagMap.clear();
}
@Override
@ -111,7 +134,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper
@Override
public Certificate getByFingerprintIfChanged(String fingerprint, String tag)
throws IOException, BadNameException, BadDataException {
if (tagChanged(fingerprint, tag)) {
if (certTagChanged(fingerprint, tag)) {
return getByFingerprint(fingerprint);
}
return null;
@ -120,12 +143,32 @@ public class CachingSharedPGPCertificateDirectoryWrapper
@Override
public Certificate getBySpecialNameIfChanged(String specialName, String tag)
throws IOException, BadNameException, BadDataException {
if (tagChanged(specialName, tag)) {
if (certTagChanged(specialName, tag)) {
return getBySpecialName(specialName);
}
return null;
}
@Override
public Key getTrustRoot() throws IOException, BadDataException {
Key key = keyMap.get(SpecialNames.TRUST_ROOT);
if (key == null) {
key = underlyingCertificateDirectory.getTrustRoot();
if (key != null) {
remember(SpecialNames.TRUST_ROOT, key);
}
}
return key;
}
@Override
public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException {
if (keyTagChanged(SpecialNames.TRUST_ROOT, tag)) {
return getTrustRoot();
}
return null;
}
@Override
public Certificate insert(InputStream data, CertificateMerger merge)
throws IOException, BadDataException, InterruptedException {
@ -144,6 +187,22 @@ public class CachingSharedPGPCertificateDirectoryWrapper
return certificate;
}
@Override
public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException {
Key key = underlyingCertificateDirectory.insertTrustRoot(data, merge);
remember(SpecialNames.TRUST_ROOT, key);
return key;
}
@Override
public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException {
Key key = underlyingCertificateDirectory.tryInsertTrustRoot(data, merge);
if (key != null) {
remember(SpecialNames.TRUST_ROOT, key);
}
return key;
}
@Override
public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge)
throws IOException, BadDataException, BadNameException, InterruptedException {

View file

@ -60,6 +60,24 @@ public class FilenameResolver {
return new File(getBaseDirectory(), specialName);
}
/**
* Calculate the file location for the key addressed using the given special name.
* For known special names, see {@link SpecialNames}.
*
* @param specialName special name (e.g. "trust-root")
* @return absolute key file location
*
* @throws BadNameException in case the given special name is not known
*/
public File getKeyFileBySpecialName(String specialName)
throws BadNameException {
if (!isSpecialName(specialName)) {
throw new BadNameException(String.format("%s is not a known special name", specialName));
}
return new File(getBaseDirectory(), specialName + ".key");
}
private boolean isFingerprint(String fingerprint) {
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
}

View file

@ -8,6 +8,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import pgp.certificate_store.Key;
import pgp.certificate_store.KeyMerger;
import pgp.certificate_store.exception.BadDataException;
import pgp.certificate_store.exception.BadNameException;
import pgp.certificate_store.Certificate;
@ -23,6 +25,18 @@ public interface SharedPGPCertificateDirectory {
Certificate getBySpecialName(String specialName)
throws IOException, BadNameException, BadDataException;
Key getTrustRoot()
throws IOException, BadDataException;
Key getTrustRootIfChanged(String tag)
throws IOException, BadDataException;
Key insertTrustRoot(InputStream data, KeyMerger merge)
throws IOException, BadDataException, InterruptedException;
Key tryInsertTrustRoot(InputStream data, KeyMerger merge)
throws IOException, BadDataException;
Certificate getByFingerprintIfChanged(String fingerprint, String tag)
throws IOException, BadNameException, BadDataException;

View file

@ -16,6 +16,9 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import pgp.certificate_store.Key;
import pgp.certificate_store.KeyMerger;
import pgp.certificate_store.KeyReaderBackend;
import pgp.certificate_store.exception.BadDataException;
import pgp.certificate_store.exception.BadNameException;
import pgp.certificate_store.exception.NotAStoreException;
@ -28,33 +31,41 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
private final FilenameResolver resolver;
private final LockingMechanism writeLock;
private final CertificateReaderBackend certificateReaderBackend;
private final KeyReaderBackend keyReaderBackend;
public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider)
throws NotAStoreException {
this(backendProvider.provideCertificateReaderBackend());
this(backendProvider.provideCertificateReaderBackend(), backendProvider.provideKeyReaderBackend());
}
public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend)
public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend,
KeyReaderBackend keyReaderBackend)
throws NotAStoreException {
this(
BaseDirectoryProvider.getDefaultBaseDir(),
certificateReaderBackend);
certificateReaderBackend,
keyReaderBackend);
}
public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend)
public SharedPGPCertificateDirectoryImpl(File baseDirectory,
CertificateReaderBackend certificateReaderBackend,
KeyReaderBackend keyReaderBackend)
throws NotAStoreException {
this(
certificateReaderBackend,
keyReaderBackend,
new FilenameResolver(baseDirectory),
FileLockingMechanism.defaultDirectoryFileLock(baseDirectory));
}
public SharedPGPCertificateDirectoryImpl(
CertificateReaderBackend certificateReaderBackend,
KeyReaderBackend keyReaderBackend,
FilenameResolver filenameResolver,
LockingMechanism writeLock)
throws NotAStoreException {
this.certificateReaderBackend = certificateReaderBackend;
this.keyReaderBackend = keyReaderBackend;
this.resolver = filenameResolver;
this.writeLock = writeLock;
@ -130,6 +141,36 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
return certificate;
}
@Override
public Key getTrustRoot() throws IOException, BadDataException {
File keyFile;
try {
keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT);
} catch (BadNameException e) {
throw new RuntimeException("trust-root MUST be a known special name", e);
}
if (!keyFile.exists()) {
return null;
}
FileInputStream fileIn = new FileInputStream(keyFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileIn);
Key key = keyReaderBackend.readKey(bufferedInputStream);
return key;
}
@Override
public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException {
// TODO: The tag is likely intended for performance improvements,
// so really we should look it up somewhere without the need to parse the whole key.
Key key = getTrustRoot();
if (key.getTag().equals(tag)) {
return null;
}
return key;
}
@Override
public Certificate insert(InputStream data, CertificateMerger merge)
throws IOException, BadDataException, InterruptedException {
@ -170,28 +211,63 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
newCertificate = merge.merge(newCertificate, existingCertificate);
}
writeCertificate(newCertificate, certFile);
writeToFile(newCertificate.getInputStream(), certFile);
return newCertificate;
}
private void writeCertificate(Certificate certificate, File certFile)
private Key _insertTrustRoot(InputStream data, KeyMerger merge)
throws IOException, BadDataException {
Key newKey = keyReaderBackend.readKey(data);
Key existingKey;
File keyFile;
try {
existingKey = getTrustRoot();
keyFile = resolver.getKeyFileBySpecialName(SpecialNames.TRUST_ROOT);
} catch (BadNameException e) {
throw new RuntimeException(String.format("trust-root MUST be known special name.", e));
}
if (existingKey != null && !existingKey.getTag().equals(newKey.getTag())) {
newKey = merge.merge(newKey, existingKey);
}
writeToFile(newKey.getInputStream(), keyFile);
return newKey;
}
@Override
public Key insertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException, InterruptedException {
writeLock.lockDirectory();
Key key = _insertTrustRoot(data, merge);
writeLock.releaseDirectory();
return key;
}
@Override
public Key tryInsertTrustRoot(InputStream data, KeyMerger merge) throws IOException, BadDataException {
return null;
}
private void writeToFile(InputStream inputStream, File certFile)
throws IOException {
certFile.getParentFile().mkdirs();
if (!certFile.exists() && !certFile.createNewFile()) {
throw new IOException("Could not create cert file " + certFile.getAbsolutePath());
}
InputStream certIn = certificate.getInputStream();
FileOutputStream fileOut = new FileOutputStream(certFile);
byte[] buffer = new byte[4096];
int read;
while ((read = certIn.read(buffer)) != -1) {
while ((read = inputStream.read(buffer)) != -1) {
fileOut.write(buffer, 0, read);
}
certIn.close();
inputStream.close();
fileOut.close();
}
@ -229,7 +305,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
newCertificate = merge.merge(newCertificate, existingCertificate);
}
writeCertificate(newCertificate, certFile);
writeToFile(newCertificate.getInputStream(), certFile);
return newCertificate;
}

View file

@ -8,6 +8,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
/**
* OpenPGP certificate (public key).
*/
public abstract class Certificate {
/**
* Return the fingerprint of the certificate as 40 lowercase hex characters.

View file

@ -4,6 +4,6 @@
package pgp.certificate_store;
public interface CertificateStore extends CertificateDirectory, SubkeyLookup {
public interface CertificateStore extends CertificateDirectory, SubkeyLookup, TrustRootStore {
}