mirror of
https://codeberg.org/PGPainless/cert-d-java.git
synced 2025-09-09 19:29:41 +02:00
Compare commits
No commits in common. "662f96ff7727edee9470e0c5e8e927b607227411" and "d050cb55167843cef94a59be8d13d311f6ec4df0" have entirely different histories.
662f96ff77
...
d050cb5516
19 changed files with 109 additions and 730 deletions
|
@ -16,19 +16,9 @@ import java.sql.SQLException;
|
||||||
*/
|
*/
|
||||||
public class DatabaseSubkeyLookupFactory implements SubkeyLookupFactory {
|
public class DatabaseSubkeyLookupFactory implements SubkeyLookupFactory {
|
||||||
|
|
||||||
private String databaseName;
|
|
||||||
|
|
||||||
public DatabaseSubkeyLookupFactory() {
|
|
||||||
this("_pgpainless_subkey_map.db");
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSubkeyLookupFactory(String databaseName) {
|
|
||||||
this.databaseName = databaseName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubkeyLookup createFileBasedInstance(File baseDirectory) {
|
public SubkeyLookup createFileBasedInstance(File baseDirectory) {
|
||||||
File databaseFile = new File(baseDirectory, databaseName);
|
File databaseFile = new File(baseDirectory, "_pgpainless_subkey_map.db");
|
||||||
SubkeyLookupDao dao;
|
SubkeyLookupDao dao;
|
||||||
try {
|
try {
|
||||||
if (!databaseFile.exists()) {
|
if (!databaseFile.exists()) {
|
||||||
|
|
|
@ -22,15 +22,15 @@ import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class SqliteSubkeyLookupTest {
|
public class SqliteSubkeyLookupTest {
|
||||||
|
|
||||||
private File tempDir;
|
private File databaseFile;
|
||||||
private DatabaseSubkeyLookup lookup;
|
private DatabaseSubkeyLookup lookup;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setupLookup() throws IOException {
|
public void setupLookup() throws IOException, SQLException {
|
||||||
tempDir = Files.createTempDirectory("pgp.cert.d").toFile();
|
databaseFile = Files.createTempFile("pgp.cert.d-", "lookup.db").toFile();
|
||||||
tempDir.deleteOnExit();
|
databaseFile.createNewFile();
|
||||||
lookup = (DatabaseSubkeyLookup) new DatabaseSubkeyLookupFactory()
|
databaseFile.deleteOnExit();
|
||||||
.createFileBasedInstance(tempDir);
|
lookup = new DatabaseSubkeyLookup(SqliteSubkeyLookupDaoImpl.forDatabaseFile(databaseFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -55,7 +55,7 @@ public class SqliteSubkeyLookupTest {
|
||||||
assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getCertificateFingerprintsForSubkeyId(1337));
|
assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), lookup.getCertificateFingerprintsForSubkeyId(1337));
|
||||||
|
|
||||||
// do the lookup using a second db instance on the same file
|
// do the lookup using a second db instance on the same file
|
||||||
DatabaseSubkeyLookup secondInstance = (DatabaseSubkeyLookup) new DatabaseSubkeyLookupFactory().createFileBasedInstance(tempDir);
|
DatabaseSubkeyLookup secondInstance = new DatabaseSubkeyLookup(SqliteSubkeyLookupDaoImpl.forDatabaseFile(databaseFile));
|
||||||
assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), secondInstance.getCertificateFingerprintsForSubkeyId(1337));
|
assertEquals(Collections.singleton("eb85bb5fa33a75e15e944e63f231550c4f47e38e"), secondInstance.getCertificateFingerprintsForSubkeyId(1337));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ apply plugin: 'ru.vyarus.animalsniffer'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// animal sniffer for ensuring Android API compatibility
|
// animal sniffer for ensuring Android API compatibility
|
||||||
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:8.0.0_r2@signature"
|
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature"
|
||||||
|
|
||||||
// JUnit
|
// JUnit
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||||
|
|
|
@ -6,16 +6,6 @@ package pgp.cert_d;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
|
||||||
* Provider class that is responsible for resolving the pgp.cert.d base directory of the system.
|
|
||||||
* The result can be overwritten by setting the <pre>PGP_CERT_D</pre> environment variable.
|
|
||||||
* If this variable is not set, the system-specific default directory will be returned.
|
|
||||||
*
|
|
||||||
* On Windows systems, this is <pre>%APPDATA%\pgp.cert.d</pre>.
|
|
||||||
* On Linux systems it is either <pre>$XDG_DATA_HOME/pgp.cert.d</pre> or, if <pre>$XDG_DATA_HOME</pre> is not set,
|
|
||||||
* it is <pre>$HOME/.local/share/pgp.cert.d</pre>
|
|
||||||
* On Mac systems it is <pre>$HOME/Library/Application Support/pgp.cert.d</pre>.
|
|
||||||
*/
|
|
||||||
public class BaseDirectoryProvider {
|
public class BaseDirectoryProvider {
|
||||||
|
|
||||||
public static File getDefaultBaseDir() {
|
public static File getDefaultBaseDir() {
|
||||||
|
|
|
@ -17,23 +17,12 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of the Shared PGP Certificate Directory.
|
|
||||||
*
|
|
||||||
* @see <a href="https://sequoia-pgp.gitlab.io/pgp-cert-d/">Shared PGP Certificate Directory Specification</a>
|
|
||||||
*/
|
|
||||||
public class PGPCertificateDirectory
|
public class PGPCertificateDirectory
|
||||||
implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory, SubkeyLookup {
|
implements ReadOnlyPGPCertificateDirectory, WritingPGPCertificateDirectory, SubkeyLookup {
|
||||||
|
|
||||||
final Backend backend;
|
final Backend backend;
|
||||||
final SubkeyLookup subkeyLookup;
|
final SubkeyLookup subkeyLookup;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for a PGP certificate directory.
|
|
||||||
*
|
|
||||||
* @param backend storage backend
|
|
||||||
* @param subkeyLookup subkey lookup mechanism to map subkey-ids to certificates
|
|
||||||
*/
|
|
||||||
public PGPCertificateDirectory(Backend backend, SubkeyLookup subkeyLookup) {
|
public PGPCertificateDirectory(Backend backend, SubkeyLookup subkeyLookup) {
|
||||||
this.backend = backend;
|
this.backend = backend;
|
||||||
this.subkeyLookup = subkeyLookup;
|
this.subkeyLookup = subkeyLookup;
|
||||||
|
@ -44,16 +33,6 @@ public class PGPCertificateDirectory
|
||||||
return backend.readByFingerprint(fingerprint);
|
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
|
@Override
|
||||||
public Certificate getBySpecialName(String specialName)
|
public Certificate getBySpecialName(String specialName)
|
||||||
throws BadNameException, BadDataException, IOException {
|
throws BadNameException, BadDataException, IOException {
|
||||||
|
@ -64,15 +43,6 @@ public class PGPCertificateDirectory
|
||||||
return null;
|
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
|
@Override
|
||||||
public Certificate getTrustRootCertificate()
|
public Certificate getTrustRootCertificate()
|
||||||
throws IOException, BadDataException {
|
throws IOException, BadDataException {
|
||||||
|
@ -83,15 +53,6 @@ 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
|
@Override
|
||||||
public Iterator<Certificate> items() {
|
public Iterator<Certificate> items() {
|
||||||
return backend.readItems();
|
return backend.readItems();
|
||||||
|
@ -200,119 +161,26 @@ public class PGPCertificateDirectory
|
||||||
subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds);
|
subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage backend.
|
|
||||||
*/
|
|
||||||
public interface Backend {
|
public interface Backend {
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the locking mechanism to write-lock the backend.
|
|
||||||
*
|
|
||||||
* @return lock
|
|
||||||
*/
|
|
||||||
LockingMechanism getLock();
|
LockingMechanism getLock();
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a {@link Certificate} by its OpenPGP fingerprint.
|
|
||||||
*
|
|
||||||
* @param fingerprint fingerprint
|
|
||||||
* @return certificate
|
|
||||||
*
|
|
||||||
* @throws BadNameException if the fingerprint is malformed
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate readByFingerprint(String fingerprint) throws BadNameException, IOException, BadDataException;
|
Certificate readByFingerprint(String fingerprint) throws BadNameException, IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a {@link Certificate} or {@link pgp.certificate_store.certificate.Key} by the given special name.
|
|
||||||
*
|
|
||||||
* @param specialName special name
|
|
||||||
* @return certificate or key
|
|
||||||
*
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
KeyMaterial readBySpecialName(String specialName) throws BadNameException, IOException, BadDataException;
|
KeyMaterial readBySpecialName(String specialName) throws BadNameException, IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an {@link Iterator} of all {@link Certificate Certificates} in the store, except for certificates
|
|
||||||
* stored under a special name.
|
|
||||||
*
|
|
||||||
* @return iterator
|
|
||||||
*/
|
|
||||||
Iterator<Certificate> readItems();
|
Iterator<Certificate> readItems();
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a {@link pgp.certificate_store.certificate.Key} or {@link Certificate} as trust-root.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the key material
|
|
||||||
* @param merge callback to merge the key material with existing key material
|
|
||||||
* @return merged or inserted key material
|
|
||||||
*
|
|
||||||
* @throws BadDataException if the data stream or existing key material contains bad data
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
||||||
throws BadDataException, IOException;
|
throws BadDataException, IOException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a {@link Certificate} identified by its fingerprint into the directory.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the certificate data
|
|
||||||
* @param merge callback to merge the certificate with existing key material
|
|
||||||
* @return merged or inserted certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or existing certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate doInsert(InputStream data, KeyMaterialMerger merge)
|
Certificate doInsert(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException;
|
throws IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a {@link pgp.certificate_store.certificate.Key} or {@link Certificate} under the given special name.
|
|
||||||
*
|
|
||||||
* @param specialName special name to identify the key material with
|
|
||||||
* @param data data stream containing the key or certificate
|
|
||||||
* @param merge callback to merge the key/certificate with existing key material
|
|
||||||
* @return certificate component of the merged or inserted key material
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or existing key material contains bad data
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
*/
|
|
||||||
Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException;
|
throws IOException, BadDataException, BadNameException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the tag of the certificate with the given fingerprint.
|
|
||||||
*
|
|
||||||
* @param fingerprint fingerprint
|
|
||||||
* @return tag
|
|
||||||
*
|
|
||||||
* @throws BadNameException if the fingerprint is malformed
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws IllegalArgumentException if the certificate does not exist
|
|
||||||
*/
|
|
||||||
Long getTagForFingerprint(String fingerprint) throws BadNameException, IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the tag of the certificate identified by the given special name.
|
|
||||||
*
|
|
||||||
* @param specialName special name
|
|
||||||
* @return tag
|
|
||||||
*
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws IllegalArgumentException if the certificate or key does not exist
|
|
||||||
*/
|
|
||||||
Long getTagForSpecialName(String specialName) throws BadNameException, IOException;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a write-locking mechanism.
|
|
||||||
*/
|
|
||||||
public interface LockingMechanism {
|
public interface LockingMechanism {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,11 +202,6 @@ public class PGPCertificateDirectory
|
||||||
*/
|
*/
|
||||||
boolean tryLockDirectory() throws IOException;
|
boolean tryLockDirectory() throws IOException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the lock is in locked state.
|
|
||||||
*
|
|
||||||
* @return true if locked
|
|
||||||
*/
|
|
||||||
boolean isLocked();
|
boolean isLocked();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,16 +37,6 @@ 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
|
@Override
|
||||||
public Iterator<Certificate> getCertificatesBySubkeyId(long subkeyId)
|
public Iterator<Certificate> getCertificatesBySubkeyId(long subkeyId)
|
||||||
throws IOException, BadDataException {
|
throws IOException, BadDataException {
|
||||||
|
|
|
@ -11,113 +11,18 @@ import pgp.certificate_store.exception.BadNameException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a read-only OpenPGP certificate directory.
|
|
||||||
*/
|
|
||||||
public interface ReadOnlyPGPCertificateDirectory {
|
public interface ReadOnlyPGPCertificateDirectory {
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the trust-root certificate. This is a certificate which is stored under the special name
|
|
||||||
* <pre>trust-root</pre>.
|
|
||||||
* If no such certificate is found, <pre>null</pre> is returned.
|
|
||||||
*
|
|
||||||
* @return trust-root certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getTrustRootCertificate()
|
Certificate getTrustRootCertificate()
|
||||||
throws IOException, BadDataException;
|
throws IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the trust-root certificate if it has changed.
|
|
||||||
* This method uses the <pre>tag</pre> to calculate if the certificate might have changed.
|
|
||||||
* If the computed tag equals the given tag, the certificate has not changed, so <pre>null</pre> is returned.
|
|
||||||
* Otherwise. the changed certificate is returned.
|
|
||||||
*
|
|
||||||
* @param tag tag
|
|
||||||
* @return changed certificate, or null if the certificate is unchanged or not found.
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getTrustRootCertificateIfChanged(long tag)
|
|
||||||
throws IOException, BadDataException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the certificate identified by the given fingerprint.
|
|
||||||
* If no such certificate is found, return <pre>null</pre>.
|
|
||||||
*
|
|
||||||
* @param fingerprint lower-case fingerprint of the certificate
|
|
||||||
* @return certificate or null if no such certificate has been found
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadNameException if the fingerprint is malformed
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getByFingerprint(String fingerprint)
|
Certificate getByFingerprint(String fingerprint)
|
||||||
throws IOException, BadNameException, BadDataException;
|
throws IOException, BadNameException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the certificate identified by the given fingerprint if it has changed.
|
|
||||||
* This method uses the <pre>tag</pre> to calculate, if the certificate might have changed.
|
|
||||||
* If the computed tag equals the given tag, the certificate has not changed, so <pre>null</pre> is returned.
|
|
||||||
* Otherwise, the changed certificate is returned.
|
|
||||||
*
|
|
||||||
* @param fingerprint lower-case fingerprint of the certificate
|
|
||||||
* @param tag tag
|
|
||||||
* @return certificate or null if the certificate has not been changed or has not been found
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadNameException if the fingerprint is malformed
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getByFingerprintIfChanged(String fingerprint, long tag)
|
|
||||||
throws IOException, BadNameException, BadDataException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the certificate identified by the given special name.
|
|
||||||
* If no such certificate is found, <pre>null</pre> is returned.
|
|
||||||
*
|
|
||||||
* @param specialName special name
|
|
||||||
* @return certificate or null
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getBySpecialName(String specialName)
|
Certificate getBySpecialName(String specialName)
|
||||||
throws IOException, BadNameException, BadDataException;
|
throws IOException, BadNameException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the certificate identified by the given special name or null, if it has not been changed.
|
|
||||||
* This method uses the <pre>tag</pre> to calculate, if the certificate might have changed.
|
|
||||||
* If the computed tag equals the given tag, the certificate has not changed, so <pre>null</pre> is returned.
|
|
||||||
* Otherwise, the changed certificate is returned.
|
|
||||||
*
|
|
||||||
* @param specialName special name
|
|
||||||
* @param tag tag
|
|
||||||
* @return certificate or null
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate getBySpecialNameIfChanged(String specialName, long tag)
|
|
||||||
throws IOException, BadNameException, BadDataException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all certificates in the directory, except for certificates which are stored by special name.
|
|
||||||
*
|
|
||||||
* @return iterator of certificates
|
|
||||||
*/
|
|
||||||
Iterator<Certificate> items();
|
Iterator<Certificate> items();
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the fingerprints of all certificates in the directory, except for certificates which are stored by
|
|
||||||
* special name.
|
|
||||||
*
|
|
||||||
* @return iterator of fingerprints
|
|
||||||
*/
|
|
||||||
Iterator<String> fingerprints();
|
Iterator<String> fingerprints();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,113 +13,26 @@ import pgp.certificate_store.exception.BadNameException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a writing OpenPGP certificate directory.
|
|
||||||
*/
|
|
||||||
public interface WritingPGPCertificateDirectory {
|
public interface WritingPGPCertificateDirectory {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the certificate or key identified by the special name <pre>trust-root</pre>.
|
|
||||||
*
|
|
||||||
* @return trust-root key or certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the certificate contains bad data
|
|
||||||
*/
|
|
||||||
KeyMaterial getTrustRoot()
|
KeyMaterial getTrustRoot()
|
||||||
throws IOException, BadDataException;
|
throws IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a key or certificate under the special name <pre>trust-root</pre>.
|
|
||||||
* This method blocks until the key material has been written.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the key or certificate
|
|
||||||
* @param merge key material merger to merge the key or certificate with existing key material
|
|
||||||
* @return the merged or inserted key or certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or the existing trust-root key material contains bad data
|
|
||||||
* @throws InterruptedException if the thread is interrupted
|
|
||||||
*/
|
|
||||||
KeyMaterial insertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
KeyMaterial insertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, InterruptedException;
|
throws IOException, BadDataException, InterruptedException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a key or certificate under the special name <pre>trust-root</pre>.
|
|
||||||
* Contrary to {@link #insertTrustRoot(InputStream, KeyMaterialMerger)}, this method does not block.
|
|
||||||
* Instead, it returns null if the write-lock cannot be obtained.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the key or certificate
|
|
||||||
* @param merge key material merger to merge the key or certificate with existing key material
|
|
||||||
* @return the merged or inserted key or certificate, or null if the write-lock cannot be obtained
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the thread is interrupted
|
|
||||||
*/
|
|
||||||
KeyMaterial tryInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
KeyMaterial tryInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException;
|
throws IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a certificate identified by its fingerprint.
|
|
||||||
* This method blocks until the certificate has been written.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the certificate data
|
|
||||||
* @param merge merge callback to merge the certificate with existing certificate material
|
|
||||||
* @return the merged or inserted certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or existing certificate contains bad data
|
|
||||||
* @throws InterruptedException if the thread is interrupted
|
|
||||||
*/
|
|
||||||
Certificate insert(InputStream data, KeyMaterialMerger merge)
|
Certificate insert(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, InterruptedException;
|
throws IOException, BadDataException, InterruptedException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a certificate identified by its fingerprint.
|
|
||||||
* Contrary to {@link #insert(InputStream, KeyMaterialMerger)}, this method does not block.
|
|
||||||
* Instead, it returns null if the write-lock cannot be obtained.
|
|
||||||
*
|
|
||||||
* @param data input stream containing the certificate data
|
|
||||||
* @param merge merge callback to merge the certificate with existing certificate material
|
|
||||||
* @return the merged or inserted certificate
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or existing certificate contains bad data
|
|
||||||
*/
|
|
||||||
Certificate tryInsert(InputStream data, KeyMaterialMerger merge)
|
Certificate tryInsert(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException;
|
throws IOException, BadDataException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a certificate or key under the given special name.
|
|
||||||
* This method blocks until the certificate/key has been written.
|
|
||||||
*
|
|
||||||
* @param specialName special name under which the key material shall be inserted
|
|
||||||
* @param data input stream containing the key/certificate data
|
|
||||||
* @param merge callback to merge the key/certificate with existing key material
|
|
||||||
* @return certificate component of the merged or inserted key material data
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or the existing certificate contains bad data
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
* @throws InterruptedException if the thread is interrupted
|
|
||||||
*/
|
|
||||||
Certificate insertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
Certificate insertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException, InterruptedException;
|
throws IOException, BadDataException, BadNameException, InterruptedException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a certificate or key under the given special name.
|
|
||||||
* Contrary to {@link #insertWithSpecialName(String, InputStream, KeyMaterialMerger)}, this method does not block.
|
|
||||||
* Instead, it returns null if the write-lock cannot be obtained.
|
|
||||||
*
|
|
||||||
* @param specialName special name under which the key material shall be inserted
|
|
||||||
* @param data input stream containing the key material
|
|
||||||
* @param merge callback to merge the key/certificate with existing key material
|
|
||||||
* @return certificate component of the merged or inserted key material
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws BadDataException if the data stream or existing key material contains bad data
|
|
||||||
* @throws BadNameException if the special name is not known
|
|
||||||
*/
|
|
||||||
Certificate tryInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
Certificate tryInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException;
|
throws IOException, BadDataException, BadNameException;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ package pgp.cert_d.backend;
|
||||||
import pgp.cert_d.PGPCertificateDirectory;
|
import pgp.cert_d.PGPCertificateDirectory;
|
||||||
import pgp.cert_d.SpecialNames;
|
import pgp.cert_d.SpecialNames;
|
||||||
import pgp.certificate_store.certificate.Certificate;
|
import pgp.certificate_store.certificate.Certificate;
|
||||||
import pgp.certificate_store.certificate.Key;
|
|
||||||
import pgp.certificate_store.certificate.KeyMaterial;
|
import pgp.certificate_store.certificate.KeyMaterial;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
||||||
|
@ -26,9 +25,6 @@ import java.io.InputStream;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
import java.nio.channels.OverlappingFileLockException;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -164,12 +160,10 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
long tag = getTagForFingerprint(fingerprint);
|
|
||||||
|
|
||||||
FileInputStream fileIn = new FileInputStream(certFile);
|
FileInputStream fileIn = new FileInputStream(certFile);
|
||||||
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||||
|
|
||||||
Certificate certificate = reader.read(bufferedIn, tag).asCertificate();
|
Certificate certificate = reader.read(bufferedIn).asCertificate();
|
||||||
if (!certificate.getFingerprint().equals(fingerprint)) {
|
if (!certificate.getFingerprint().equals(fingerprint)) {
|
||||||
// TODO: Figure out more suitable exception
|
// TODO: Figure out more suitable exception
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
|
@ -185,11 +179,9 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
long tag = getTagForSpecialName(specialName);
|
|
||||||
|
|
||||||
FileInputStream fileIn = new FileInputStream(certFile);
|
FileInputStream fileIn = new FileInputStream(certFile);
|
||||||
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||||
KeyMaterial keyMaterial = reader.read(bufferedIn, tag);
|
KeyMaterial keyMaterial = reader.read(bufferedIn);
|
||||||
|
|
||||||
return keyMaterial;
|
return keyMaterial;
|
||||||
}
|
}
|
||||||
|
@ -222,8 +214,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
@Override
|
@Override
|
||||||
Certificate get() throws BadDataException {
|
Certificate get() throws BadDataException {
|
||||||
try {
|
try {
|
||||||
long tag = getTag(certFile);
|
Certificate certificate = reader.read(new FileInputStream(certFile)).asCertificate();
|
||||||
Certificate certificate = reader.read(new FileInputStream(certFile), tag).asCertificate();
|
|
||||||
if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) {
|
if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) {
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
}
|
}
|
||||||
|
@ -255,7 +246,7 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException {
|
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge) throws BadDataException, IOException {
|
||||||
KeyMaterial newCertificate = reader.read(data, null);
|
KeyMaterial newCertificate = reader.read(data);
|
||||||
KeyMaterial existingCertificate;
|
KeyMaterial existingCertificate;
|
||||||
File certFile;
|
File certFile;
|
||||||
try {
|
try {
|
||||||
|
@ -265,22 +256,18 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingCertificate != null) {
|
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
writeToFile(newCertificate.getInputStream(), certFile);
|
||||||
if (newCertificate instanceof Key) {
|
|
||||||
newCertificate = new Key((Key) newCertificate, tag);
|
|
||||||
} else {
|
|
||||||
newCertificate = new Certificate((Certificate) newCertificate, tag);
|
|
||||||
}
|
|
||||||
return newCertificate;
|
return newCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException {
|
public Certificate doInsert(InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException {
|
||||||
KeyMaterial newCertificate = reader.read(data, null);
|
KeyMaterial newCertificate = reader.read(data);
|
||||||
Certificate existingCertificate;
|
Certificate existingCertificate;
|
||||||
File certFile;
|
File certFile;
|
||||||
try {
|
try {
|
||||||
|
@ -290,17 +277,18 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingCertificate != null) {
|
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
writeToFile(newCertificate.getInputStream(), certFile);
|
||||||
return new Certificate(newCertificate.asCertificate(), tag);
|
|
||||||
|
return newCertificate.asCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException {
|
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge) throws IOException, BadDataException, BadNameException {
|
||||||
KeyMaterial newCertificate = reader.read(data, null);
|
KeyMaterial newCertificate = reader.read(data);
|
||||||
KeyMaterial existingCertificate;
|
KeyMaterial existingCertificate;
|
||||||
File certFile;
|
File certFile;
|
||||||
try {
|
try {
|
||||||
|
@ -310,41 +298,16 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingCertificate != null) {
|
if (existingCertificate != null && !newCertificate.getTag().equals(existingCertificate.getTag())) {
|
||||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
long tag = writeToFile(newCertificate.getInputStream(), certFile);
|
writeToFile(newCertificate.getInputStream(), certFile);
|
||||||
return new Certificate(newCertificate.asCertificate(), tag);
|
|
||||||
|
return newCertificate.asCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void writeToFile(InputStream inputStream, File certFile)
|
||||||
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 {
|
throws IOException {
|
||||||
certFile.getParentFile().mkdirs();
|
certFile.getParentFile().mkdirs();
|
||||||
if (!certFile.exists() && !certFile.createNewFile()) {
|
if (!certFile.exists() && !certFile.createNewFile()) {
|
||||||
|
@ -361,7 +324,6 @@ public class FileBasedCertificateDirectoryBackend implements PGPCertificateDirec
|
||||||
|
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
fileOut.close();
|
fileOut.close();
|
||||||
return getTag(certFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FilenameResolver {
|
public static class FilenameResolver {
|
||||||
|
|
|
@ -7,7 +7,6 @@ package pgp.cert_d.backend;
|
||||||
import pgp.cert_d.PGPCertificateDirectory;
|
import pgp.cert_d.PGPCertificateDirectory;
|
||||||
import pgp.cert_d.SpecialNames;
|
import pgp.cert_d.SpecialNames;
|
||||||
import pgp.certificate_store.certificate.Certificate;
|
import pgp.certificate_store.certificate.Certificate;
|
||||||
import pgp.certificate_store.certificate.Key;
|
|
||||||
import pgp.certificate_store.certificate.KeyMaterial;
|
import pgp.certificate_store.certificate.KeyMaterial;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
|
||||||
|
@ -92,7 +91,7 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
||||||
@Override
|
@Override
|
||||||
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
public KeyMaterial doInsertTrustRoot(InputStream data, KeyMaterialMerger merge)
|
||||||
throws BadDataException, IOException {
|
throws BadDataException, IOException {
|
||||||
KeyMaterial update = reader.read(data, null);
|
KeyMaterial update = reader.read(data);
|
||||||
KeyMaterial existing = null;
|
KeyMaterial existing = null;
|
||||||
try {
|
try {
|
||||||
existing = readBySpecialName(SpecialNames.TRUST_ROOT);
|
existing = readBySpecialName(SpecialNames.TRUST_ROOT);
|
||||||
|
@ -101,11 +100,6 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
KeyMaterial merged = merge.merge(update, existing);
|
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);
|
keyMaterialSpecialNameMap.put(SpecialNames.TRUST_ROOT, merged);
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
@ -114,10 +108,9 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
||||||
@Override
|
@Override
|
||||||
public Certificate doInsert(InputStream data, KeyMaterialMerger merge)
|
public Certificate doInsert(InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException {
|
throws IOException, BadDataException {
|
||||||
KeyMaterial update = reader.read(data, null);
|
KeyMaterial update = reader.read(data);
|
||||||
Certificate existing = readByFingerprint(update.getFingerprint());
|
Certificate existing = readByFingerprint(update.getFingerprint());
|
||||||
Certificate merged = merge.merge(update, existing).asCertificate();
|
Certificate merged = merge.merge(update, existing).asCertificate();
|
||||||
merged = new Certificate(merged, System.currentTimeMillis());
|
|
||||||
certificateFingerprintMap.put(update.getFingerprint(), merged);
|
certificateFingerprintMap.put(update.getFingerprint(), merged);
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
@ -125,36 +118,10 @@ public class InMemoryCertificateDirectoryBackend implements PGPCertificateDirect
|
||||||
@Override
|
@Override
|
||||||
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
public Certificate doInsertWithSpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException {
|
throws IOException, BadDataException, BadNameException {
|
||||||
KeyMaterial keyMaterial = reader.read(data, null);
|
KeyMaterial keyMaterial = reader.read(data);
|
||||||
KeyMaterial existing = readBySpecialName(specialName);
|
KeyMaterial existing = readBySpecialName(specialName);
|
||||||
KeyMaterial merged = merge.merge(keyMaterial, existing);
|
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);
|
keyMaterialSpecialNameMap.put(specialName, merged);
|
||||||
return merged.asCertificate();
|
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,17 +5,12 @@
|
||||||
package pgp.cert_d;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
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.cert_d.subkey_lookup.InMemorySubkeyLookup;
|
||||||
import pgp.certificate_store.certificate.Certificate;
|
import pgp.certificate_store.certificate.Certificate;
|
||||||
import pgp.certificate_store.certificate.Key;
|
import pgp.certificate_store.certificate.Key;
|
||||||
import pgp.certificate_store.certificate.KeyMaterial;
|
import pgp.certificate_store.certificate.KeyMaterial;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
|
||||||
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;
|
||||||
|
@ -23,7 +18,6 @@ import pgp.certificate_store.exception.NotAStoreException;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -36,7 +30,6 @@ import java.util.stream.Stream;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
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.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
@ -44,7 +37,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class PGPCertificateDirectoryTest {
|
public class PGPCertificateDirectoryTest {
|
||||||
|
|
||||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
|
||||||
private static final Charset UTF8 = Charset.forName("UTF8");
|
private static final Charset UTF8 = Charset.forName("UTF8");
|
||||||
|
|
||||||
private static final String HARRY_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
private static final String HARRY_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
|
@ -151,10 +143,7 @@ public class PGPCertificateDirectoryTest {
|
||||||
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
private static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021";
|
private static final String CEDRIC_FP = "5e75bf20646bc1a98d3b1bc2fe9cd472987c4021";
|
||||||
|
|
||||||
private static final KeyMaterialMerger merger = new TestKeyMaterialMerger();
|
private static Stream<PGPCertificateDirectory> provideTestSubjects() throws IOException, NotAStoreException {
|
||||||
|
|
||||||
private static Stream<PGPCertificateDirectory> provideTestSubjects()
|
|
||||||
throws IOException, NotAStoreException {
|
|
||||||
PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory(
|
PGPCertificateDirectory inMemory = PGPCertificateDirectories.inMemoryCertificateDirectory(
|
||||||
new TestKeyMaterialReaderBackend());
|
new TestKeyMaterialReaderBackend());
|
||||||
|
|
||||||
|
@ -170,19 +159,18 @@ public class PGPCertificateDirectoryTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("provideTestSubjects")
|
@MethodSource("provideTestSubjects")
|
||||||
public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory)
|
public void lockDirectoryAndInsertWillFail(PGPCertificateDirectory directory) throws IOException, InterruptedException, BadDataException {
|
||||||
throws IOException, InterruptedException, BadDataException {
|
|
||||||
// Manually lock the dir
|
// Manually lock the dir
|
||||||
assertFalse(directory.backend.getLock().isLocked());
|
assertFalse(directory.backend.getLock().isLocked());
|
||||||
directory.backend.getLock().lockDirectory();
|
directory.backend.getLock().lockDirectory();
|
||||||
assertTrue(directory.backend.getLock().isLocked());
|
assertTrue(directory.backend.getLock().isLocked());
|
||||||
assertFalse(directory.backend.getLock().tryLockDirectory());
|
assertFalse(directory.backend.getLock().tryLockDirectory());
|
||||||
|
|
||||||
Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
Certificate inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||||
assertNull(inserted);
|
assertNull(inserted);
|
||||||
|
|
||||||
directory.backend.getLock().releaseDirectory();
|
directory.backend.getLock().releaseDirectory();
|
||||||
inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
inserted = directory.tryInsert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||||
assertNotNull(inserted);
|
assertNotNull(inserted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +188,7 @@ public class PGPCertificateDirectoryTest {
|
||||||
|
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8));
|
||||||
|
|
||||||
Certificate certificate = directory.insert(bytesIn, merger);
|
Certificate certificate = directory.insert(bytesIn, new TestKeyMaterialMerger());
|
||||||
assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match");
|
assertEquals(CEDRIC_FP, certificate.getFingerprint(), "Fingerprint of inserted cert MUST match");
|
||||||
|
|
||||||
Certificate get = directory.getByFingerprint(CEDRIC_FP);
|
Certificate get = directory.getByFingerprint(CEDRIC_FP);
|
||||||
|
@ -219,7 +207,7 @@ public class PGPCertificateDirectoryTest {
|
||||||
assertNull(directory.getTrustRoot());
|
assertNull(directory.getTrustRoot());
|
||||||
|
|
||||||
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
KeyMaterial trustRootMaterial = directory.insertTrustRoot(
|
||||||
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), merger);
|
new ByteArrayInputStream(HARRY_KEY.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||||
assertNotNull(trustRootMaterial);
|
assertNotNull(trustRootMaterial);
|
||||||
assertTrue(trustRootMaterial instanceof Key);
|
assertTrue(trustRootMaterial instanceof Key);
|
||||||
assertEquals(HARRY_FP, trustRootMaterial.getFingerprint());
|
assertEquals(HARRY_FP, trustRootMaterial.getFingerprint());
|
||||||
|
@ -229,8 +217,8 @@ public class PGPCertificateDirectoryTest {
|
||||||
Certificate trustRootCert = directory.getTrustRootCertificate();
|
Certificate trustRootCert = directory.getTrustRootCertificate();
|
||||||
assertEquals(HARRY_FP, trustRootCert.getFingerprint());
|
assertEquals(HARRY_FP, trustRootCert.getFingerprint());
|
||||||
|
|
||||||
directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), merger);
|
directory.tryInsert(new ByteArrayInputStream(RON_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||||
directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), merger);
|
directory.insert(new ByteArrayInputStream(CEDRIC_CERT.getBytes(UTF8)), new TestKeyMaterialMerger());
|
||||||
|
|
||||||
Set<String> expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP));
|
Set<String> expected = new HashSet<>(Arrays.asList(RON_FP, CEDRIC_FP));
|
||||||
|
|
||||||
|
@ -242,104 +230,4 @@ public class PGPCertificateDirectoryTest {
|
||||||
|
|
||||||
assertEquals(expected, actual);
|
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
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d.dummy;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import pgp.certificate_store.certificate.KeyMaterial;
|
import pgp.certificate_store.certificate.KeyMaterial;
|
||||||
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
import pgp.certificate_store.certificate.KeyMaterialMerger;
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d.dummy;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
@ -33,22 +33,24 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
||||||
KeyFingerPrintCalculator fpCalc = new BcKeyFingerprintCalculator();
|
KeyFingerPrintCalculator fpCalc = new BcKeyFingerprintCalculator();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException {
|
public KeyMaterial read(InputStream data) throws IOException, BadDataException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(data, out);
|
Streams.pipeAll(data, out);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return readKey(new ByteArrayInputStream(out.toByteArray()), tag);
|
Key key = readKey(new ByteArrayInputStream(out.toByteArray()));
|
||||||
|
return key;
|
||||||
} catch (IOException | PGPException e) {
|
} catch (IOException | PGPException e) {
|
||||||
try {
|
try {
|
||||||
return readCertificate(new ByteArrayInputStream(out.toByteArray()), tag);
|
Certificate certificate = readCertificate(new ByteArrayInputStream(out.toByteArray()));
|
||||||
|
return certificate;
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
throw new BadDataException();
|
throw new BadDataException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key readKey(InputStream inputStream, Long tag) throws IOException, PGPException {
|
private Key readKey(InputStream inputStream) throws IOException, PGPException {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(inputStream, buffer);
|
Streams.pipeAll(inputStream, buffer);
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
|
@ -58,21 +60,64 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
||||||
PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(decoderStream, fpCalc);
|
PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(decoderStream, fpCalc);
|
||||||
PGPPublicKeyRing cert = extractCert(secretKeys);
|
PGPPublicKeyRing cert = extractCert(secretKeys);
|
||||||
ByteArrayInputStream encoded = new ByteArrayInputStream(cert.getEncoded());
|
ByteArrayInputStream encoded = new ByteArrayInputStream(cert.getEncoded());
|
||||||
Certificate certificate = readCertificate(encoded, tag);
|
Certificate certificate = readCertificate(encoded);
|
||||||
|
|
||||||
return new Key(buffer.toByteArray(), certificate, tag);
|
return new Key() {
|
||||||
|
@Override
|
||||||
|
public Certificate getCertificate() {
|
||||||
|
return certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Certificate readCertificate(InputStream inputStream, Long tag) throws IOException {
|
@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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Certificate readCertificate(InputStream inputStream) throws IOException {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(inputStream, buffer);
|
Streams.pipeAll(inputStream, buffer);
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(buffer.toByteArray());
|
ByteArrayInputStream in = new ByteArrayInputStream(buffer.toByteArray());
|
||||||
InputStream decoderStream = PGPUtil.getDecoderStream(in);
|
InputStream decoderStream = PGPUtil.getDecoderStream(in);
|
||||||
|
|
||||||
PGPPublicKeyRing cert = new PGPPublicKeyRing(decoderStream, fpCalc);
|
PGPPublicKeyRing cert = new PGPPublicKeyRing(decoderStream, fpCalc);
|
||||||
String fingerprint = Hex.toHexString(cert.getPublicKey().getFingerprint()).toLowerCase();
|
return new Certificate() {
|
||||||
List<Long> subKeyIds = getSubkeyIds(cert);
|
@Override
|
||||||
return new Certificate(buffer.toByteArray(), fingerprint, subKeyIds, tag);
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private PGPPublicKeyRing extractCert(PGPSecretKeyRing secretKeys) {
|
private PGPPublicKeyRing extractCert(PGPSecretKeyRing secretKeys) {
|
||||||
|
@ -81,7 +126,8 @@ public class TestKeyMaterialReaderBackend implements KeyMaterialReaderBackend {
|
||||||
while (publicKeyIterator.hasNext()) {
|
while (publicKeyIterator.hasNext()) {
|
||||||
publicKeyList.add(publicKeyIterator.next());
|
publicKeyList.add(publicKeyIterator.next());
|
||||||
}
|
}
|
||||||
return new PGPPublicKeyRing(publicKeyList);
|
PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList);
|
||||||
|
return publicKeyRing;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Long> getSubkeyIds(PGPKeyRing keyRing) {
|
private static List<Long> getSubkeyIds(PGPKeyRing keyRing) {
|
|
@ -32,23 +32,6 @@ public interface PGPCertificateStore {
|
||||||
Certificate getCertificate(String identifier)
|
Certificate getCertificate(String identifier)
|
||||||
throws IOException, BadNameException, BadDataException;
|
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
|
* Return an {@link Iterator} over all certificates in the store that contain a subkey with the given
|
||||||
* subkey id.
|
* subkey id.
|
||||||
|
|
|
@ -4,67 +4,13 @@
|
||||||
|
|
||||||
package pgp.certificate_store.certificate;
|
package pgp.certificate_store.certificate;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenPGP certificate (public key).
|
* OpenPGP certificate (public key).
|
||||||
*/
|
*/
|
||||||
public class Certificate implements KeyMaterial {
|
public abstract 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
|
@Override
|
||||||
public Certificate asCertificate() {
|
public Certificate asCertificate() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return new ByteArrayInputStream(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Long> getSubkeyIds() {
|
|
||||||
return subkeyIds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,74 +4,21 @@
|
||||||
|
|
||||||
package pgp.certificate_store.certificate;
|
package pgp.certificate_store.certificate;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenPGP key (secret key).
|
* OpenPGP key (secret key).
|
||||||
*/
|
*/
|
||||||
public class Key implements KeyMaterial {
|
public abstract 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 the certificate part of this OpenPGP key.
|
||||||
*
|
*
|
||||||
* @return OpenPGP certificate
|
* @return OpenPGP certificate
|
||||||
*/
|
*/
|
||||||
public Certificate getCertificate() {
|
public abstract Certificate getCertificate();
|
||||||
return new Certificate(certificate, getTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFingerprint() {
|
|
||||||
return certificate.getFingerprint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate asCertificate() {
|
public Certificate asCertificate() {
|
||||||
return getCertificate();
|
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,6 +4,7 @@
|
||||||
|
|
||||||
package pgp.certificate_store.certificate;
|
package pgp.certificate_store.certificate;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -18,34 +19,23 @@ public interface KeyMaterial {
|
||||||
*/
|
*/
|
||||||
String getFingerprint();
|
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();
|
Certificate asCertificate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an {@link InputStream} of the binary representation of the secret key.
|
* Return an {@link InputStream} of the binary representation of the secret key.
|
||||||
*
|
*
|
||||||
* @return input stream
|
* @return input stream
|
||||||
|
* @throws IOException in case of an IO error
|
||||||
*/
|
*/
|
||||||
InputStream getInputStream();
|
InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
/**
|
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 a {@link Set} containing key-ids of subkeys.
|
||||||
*
|
*
|
||||||
* @return subkeys
|
* @return subkeys
|
||||||
|
* @throws IOException in case of an IO error
|
||||||
*/
|
*/
|
||||||
List<Long> getSubkeyIds();
|
List<Long> getSubkeyIds() throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,10 @@ public interface KeyMaterialReaderBackend {
|
||||||
* Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}.
|
* Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}.
|
||||||
*
|
*
|
||||||
* @param data input stream containing the binary representation of the key.
|
* @param data input stream containing the binary representation of the key.
|
||||||
* @param tag tag for the key material. Might be null.
|
|
||||||
* @return key or certificate object
|
* @return key or certificate object
|
||||||
*
|
*
|
||||||
* @throws IOException in case of an IO error
|
* @throws IOException in case of an IO error
|
||||||
* @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate
|
* @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate
|
||||||
*/
|
*/
|
||||||
KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException;
|
KeyMaterial read(InputStream data) throws IOException, BadDataException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ allprojects {
|
||||||
ext {
|
ext {
|
||||||
shortVersion = '0.1.2'
|
shortVersion = '0.1.2'
|
||||||
isSnapshot = true
|
isSnapshot = true
|
||||||
minAndroidSdk = 26
|
minAndroidSdk = 10
|
||||||
javaSourceCompatibility = 1.8
|
javaSourceCompatibility = 1.8
|
||||||
bouncycastleVersion = '1.71'
|
bouncycastleVersion = '1.71'
|
||||||
slf4jVersion = '1.7.36'
|
slf4jVersion = '1.7.36'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue