mirror of
https://codeberg.org/PGPainless/cert-d-java.git
synced 2024-12-22 13:47:59 +01:00
Implement storing of trust-root keys
This commit is contained in:
parent
304d6c29e4
commit
17d2f45e83
7 changed files with 192 additions and 19 deletions
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
package pgp.certificate_store;
|
||||
|
||||
public interface CertificateStore extends CertificateDirectory, SubkeyLookup {
|
||||
public interface CertificateStore extends CertificateDirectory, SubkeyLookup, TrustRootStore {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue