mirror of
https://codeberg.org/PGPainless/cert-d-java.git
synced 2024-11-22 15:32:09 +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.CertificateReaderBackend;
|
||||||
import pgp.certificate_store.CertificateMerger;
|
import pgp.certificate_store.CertificateMerger;
|
||||||
|
import pgp.certificate_store.KeyReaderBackend;
|
||||||
|
|
||||||
public abstract class BackendProvider {
|
public abstract class BackendProvider {
|
||||||
|
|
||||||
public abstract CertificateReaderBackend provideCertificateReaderBackend();
|
public abstract CertificateReaderBackend provideCertificateReaderBackend();
|
||||||
|
|
||||||
|
public abstract KeyReaderBackend provideKeyReaderBackend();
|
||||||
|
|
||||||
public abstract CertificateMerger provideDefaultMergeCallback();
|
public abstract CertificateMerger provideDefaultMergeCallback();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package pgp.cert_d;
|
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.BadDataException;
|
||||||
import pgp.certificate_store.exception.BadNameException;
|
import pgp.certificate_store.exception.BadNameException;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
|
@ -21,8 +23,10 @@ import java.util.Map;
|
||||||
public class CachingSharedPGPCertificateDirectoryWrapper
|
public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
implements SharedPGPCertificateDirectory {
|
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, Certificate> certificateMap = new HashMap<>();
|
||||||
|
private static final Map<String, Key> keyMap = new HashMap<>();
|
||||||
private final SharedPGPCertificateDirectory underlyingCertificateDirectory;
|
private final SharedPGPCertificateDirectory underlyingCertificateDirectory;
|
||||||
|
|
||||||
public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) {
|
public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) {
|
||||||
|
@ -38,12 +42,26 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
private void remember(String identifier, Certificate certificate) {
|
private void remember(String identifier, Certificate certificate) {
|
||||||
certificateMap.put(identifier, certificate);
|
certificateMap.put(identifier, certificate);
|
||||||
try {
|
try {
|
||||||
tagMap.put(identifier, certificate.getTag());
|
certTagMap.put(identifier, certificate.getTag());
|
||||||
} catch (IOException e) {
|
} 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.
|
* Returns true, if the cached tag differs from the provided tag.
|
||||||
*
|
*
|
||||||
|
@ -51,8 +69,13 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
* @param tag tag
|
* @param tag tag
|
||||||
* @return true if cached tag differs, false otherwise
|
* @return true if cached tag differs, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean tagChanged(String identifier, String tag) {
|
private boolean certTagChanged(String identifier, String tag) {
|
||||||
String tack = tagMap.get(identifier);
|
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);
|
return !tagEquals(tag, tack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +95,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
*/
|
*/
|
||||||
public void invalidate() {
|
public void invalidate() {
|
||||||
certificateMap.clear();
|
certificateMap.clear();
|
||||||
tagMap.clear();
|
certTagMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,7 +134,7 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
@Override
|
@Override
|
||||||
public Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
public Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
||||||
throws IOException, BadNameException, BadDataException {
|
throws IOException, BadNameException, BadDataException {
|
||||||
if (tagChanged(fingerprint, tag)) {
|
if (certTagChanged(fingerprint, tag)) {
|
||||||
return getByFingerprint(fingerprint);
|
return getByFingerprint(fingerprint);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -120,12 +143,32 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
@Override
|
@Override
|
||||||
public Certificate getBySpecialNameIfChanged(String specialName, String tag)
|
public Certificate getBySpecialNameIfChanged(String specialName, String tag)
|
||||||
throws IOException, BadNameException, BadDataException {
|
throws IOException, BadNameException, BadDataException {
|
||||||
if (tagChanged(specialName, tag)) {
|
if (certTagChanged(specialName, tag)) {
|
||||||
return getBySpecialName(specialName);
|
return getBySpecialName(specialName);
|
||||||
}
|
}
|
||||||
return null;
|
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
|
@Override
|
||||||
public Certificate insert(InputStream data, CertificateMerger merge)
|
public Certificate insert(InputStream data, CertificateMerger merge)
|
||||||
throws IOException, BadDataException, InterruptedException {
|
throws IOException, BadDataException, InterruptedException {
|
||||||
|
@ -144,6 +187,22 @@ public class CachingSharedPGPCertificateDirectoryWrapper
|
||||||
return certificate;
|
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
|
@Override
|
||||||
public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge)
|
public Certificate insertWithSpecialName(String specialName, InputStream data, CertificateMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException, InterruptedException {
|
throws IOException, BadDataException, BadNameException, InterruptedException {
|
||||||
|
|
|
@ -60,6 +60,24 @@ public class FilenameResolver {
|
||||||
return new File(getBaseDirectory(), specialName);
|
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) {
|
private boolean isFingerprint(String fingerprint) {
|
||||||
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
|
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Iterator;
|
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.BadDataException;
|
||||||
import pgp.certificate_store.exception.BadNameException;
|
import pgp.certificate_store.exception.BadNameException;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
|
@ -23,6 +25,18 @@ public interface SharedPGPCertificateDirectory {
|
||||||
Certificate getBySpecialName(String specialName)
|
Certificate getBySpecialName(String specialName)
|
||||||
throws IOException, BadNameException, BadDataException;
|
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)
|
Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
||||||
throws IOException, BadNameException, BadDataException;
|
throws IOException, BadNameException, BadDataException;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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.BadDataException;
|
||||||
import pgp.certificate_store.exception.BadNameException;
|
import pgp.certificate_store.exception.BadNameException;
|
||||||
import pgp.certificate_store.exception.NotAStoreException;
|
import pgp.certificate_store.exception.NotAStoreException;
|
||||||
|
@ -28,33 +31,41 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
||||||
private final FilenameResolver resolver;
|
private final FilenameResolver resolver;
|
||||||
private final LockingMechanism writeLock;
|
private final LockingMechanism writeLock;
|
||||||
private final CertificateReaderBackend certificateReaderBackend;
|
private final CertificateReaderBackend certificateReaderBackend;
|
||||||
|
private final KeyReaderBackend keyReaderBackend;
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider)
|
public SharedPGPCertificateDirectoryImpl(BackendProvider backendProvider)
|
||||||
throws NotAStoreException {
|
throws NotAStoreException {
|
||||||
this(backendProvider.provideCertificateReaderBackend());
|
this(backendProvider.provideCertificateReaderBackend(), backendProvider.provideKeyReaderBackend());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend)
|
public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend,
|
||||||
|
KeyReaderBackend keyReaderBackend)
|
||||||
throws NotAStoreException {
|
throws NotAStoreException {
|
||||||
this(
|
this(
|
||||||
BaseDirectoryProvider.getDefaultBaseDir(),
|
BaseDirectoryProvider.getDefaultBaseDir(),
|
||||||
certificateReaderBackend);
|
certificateReaderBackend,
|
||||||
|
keyReaderBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend)
|
public SharedPGPCertificateDirectoryImpl(File baseDirectory,
|
||||||
|
CertificateReaderBackend certificateReaderBackend,
|
||||||
|
KeyReaderBackend keyReaderBackend)
|
||||||
throws NotAStoreException {
|
throws NotAStoreException {
|
||||||
this(
|
this(
|
||||||
certificateReaderBackend,
|
certificateReaderBackend,
|
||||||
|
keyReaderBackend,
|
||||||
new FilenameResolver(baseDirectory),
|
new FilenameResolver(baseDirectory),
|
||||||
FileLockingMechanism.defaultDirectoryFileLock(baseDirectory));
|
FileLockingMechanism.defaultDirectoryFileLock(baseDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl(
|
public SharedPGPCertificateDirectoryImpl(
|
||||||
CertificateReaderBackend certificateReaderBackend,
|
CertificateReaderBackend certificateReaderBackend,
|
||||||
|
KeyReaderBackend keyReaderBackend,
|
||||||
FilenameResolver filenameResolver,
|
FilenameResolver filenameResolver,
|
||||||
LockingMechanism writeLock)
|
LockingMechanism writeLock)
|
||||||
throws NotAStoreException {
|
throws NotAStoreException {
|
||||||
this.certificateReaderBackend = certificateReaderBackend;
|
this.certificateReaderBackend = certificateReaderBackend;
|
||||||
|
this.keyReaderBackend = keyReaderBackend;
|
||||||
this.resolver = filenameResolver;
|
this.resolver = filenameResolver;
|
||||||
this.writeLock = writeLock;
|
this.writeLock = writeLock;
|
||||||
|
|
||||||
|
@ -130,6 +141,36 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
||||||
return certificate;
|
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
|
@Override
|
||||||
public Certificate insert(InputStream data, CertificateMerger merge)
|
public Certificate insert(InputStream data, CertificateMerger merge)
|
||||||
throws IOException, BadDataException, InterruptedException {
|
throws IOException, BadDataException, InterruptedException {
|
||||||
|
@ -170,28 +211,63 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
||||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCertificate(newCertificate, certFile);
|
writeToFile(newCertificate.getInputStream(), certFile);
|
||||||
|
|
||||||
return newCertificate;
|
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 {
|
throws IOException {
|
||||||
certFile.getParentFile().mkdirs();
|
certFile.getParentFile().mkdirs();
|
||||||
if (!certFile.exists() && !certFile.createNewFile()) {
|
if (!certFile.exists() && !certFile.createNewFile()) {
|
||||||
throw new IOException("Could not create cert file " + certFile.getAbsolutePath());
|
throw new IOException("Could not create cert file " + certFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream certIn = certificate.getInputStream();
|
|
||||||
FileOutputStream fileOut = new FileOutputStream(certFile);
|
FileOutputStream fileOut = new FileOutputStream(certFile);
|
||||||
|
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
int read;
|
int read;
|
||||||
while ((read = certIn.read(buffer)) != -1) {
|
while ((read = inputStream.read(buffer)) != -1) {
|
||||||
fileOut.write(buffer, 0, read);
|
fileOut.write(buffer, 0, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
certIn.close();
|
inputStream.close();
|
||||||
fileOut.close();
|
fileOut.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +305,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
||||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCertificate(newCertificate, certFile);
|
writeToFile(newCertificate.getInputStream(), certFile);
|
||||||
|
|
||||||
return newCertificate;
|
return newCertificate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenPGP certificate (public key).
|
||||||
|
*/
|
||||||
public abstract class Certificate {
|
public abstract class Certificate {
|
||||||
/**
|
/**
|
||||||
* Return the fingerprint of the certificate as 40 lowercase hex characters.
|
* Return the fingerprint of the certificate as 40 lowercase hex characters.
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
|
|
||||||
package pgp.certificate_store;
|
package pgp.certificate_store;
|
||||||
|
|
||||||
public interface CertificateStore extends CertificateDirectory, SubkeyLookup {
|
public interface CertificateStore extends CertificateDirectory, SubkeyLookup, TrustRootStore {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue