mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-16 01:12:05 +01:00
Wip
This commit is contained in:
parent
e817aaef4c
commit
5402306cca
17 changed files with 478 additions and 189 deletions
|
@ -4,12 +4,11 @@
|
|||
|
||||
package pgp.cert_d.cli;
|
||||
|
||||
import org.pgpainless.certificate_store.CertificateCertificateReader;
|
||||
import org.pgpainless.certificate_store.CertificateReader;
|
||||
import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter;
|
||||
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
||||
import pgp.cert_d.cli.commands.Get;
|
||||
import pgp.cert_d.cli.commands.Insert;
|
||||
import pgp.cert_d.cli.commands.PrintDirectory;
|
||||
import pgp.cert_d.cli.commands.Import;
|
||||
import pgp.cert_d.exception.NotAStoreException;
|
||||
import pgp.certificate_store.CertificateStore;
|
||||
import picocli.CommandLine;
|
||||
|
@ -18,8 +17,7 @@ import java.io.File;
|
|||
|
||||
@CommandLine.Command(
|
||||
subcommands = {
|
||||
Insert.class,
|
||||
PrintDirectory.class,
|
||||
Import.class,
|
||||
Get.class,
|
||||
}
|
||||
)
|
||||
|
@ -29,7 +27,6 @@ public class PGPCertDCli {
|
|||
File baseDirectory;
|
||||
|
||||
private static CertificateStore certificateStore;
|
||||
private static String baseDir;
|
||||
|
||||
private int executionStrategy(CommandLine.ParseResult parseResult) {
|
||||
try {
|
||||
|
@ -45,12 +42,11 @@ public class PGPCertDCli {
|
|||
if (baseDirectory != null) {
|
||||
certificateDirectory = new SharedPGPCertificateDirectoryImpl(
|
||||
baseDirectory,
|
||||
new CertificateCertificateReader());
|
||||
new CertificateReader());
|
||||
} else {
|
||||
certificateDirectory = new SharedPGPCertificateDirectoryImpl(
|
||||
new CertificateCertificateReader());
|
||||
new CertificateReader());
|
||||
}
|
||||
baseDir = certificateDirectory.getBaseDirectory().getAbsolutePath();
|
||||
certificateStore = new SharedPGPCertificateDirectoryAdapter(certificateDirectory);
|
||||
}
|
||||
|
||||
|
@ -64,8 +60,4 @@ public class PGPCertDCli {
|
|||
public static CertificateStore getCertificateDirectory() {
|
||||
return certificateStore;
|
||||
}
|
||||
|
||||
public static String getBaseDir() {
|
||||
return baseDir;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import pgp.certificate_store.Certificate;
|
|||
import pgp.certificate_store.MergeCallback;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "insert",
|
||||
description = "Insert or update a certificate")
|
||||
public class Insert implements Runnable {
|
||||
@CommandLine.Command(name = "import",
|
||||
description = "Import or update a certificate")
|
||||
public class Import implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Insert.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Import.class);
|
||||
|
||||
@Override
|
||||
public void run() {
|
|
@ -1,22 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d.cli.commands;
|
||||
|
||||
import pgp.cert_d.cli.PGPCertDCli;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "print-directory",
|
||||
description = "Print the location of the certificate directory"
|
||||
)
|
||||
public class PrintDirectory implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(PGPCertDCli.getBaseDir());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ package pgp.cert_d;
|
|||
|
||||
import java.io.File;
|
||||
|
||||
public class OSUtil {
|
||||
public class BaseDirectoryProvider {
|
||||
|
||||
public static File getDefaultBaseDir() {
|
||||
// Check for environment variable
|
|
@ -0,0 +1,185 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import pgp.cert_d.exception.BadDataException;
|
||||
import pgp.cert_d.exception.BadNameException;
|
||||
import pgp.certificate_store.Certificate;
|
||||
import pgp.certificate_store.MergeCallback;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Caching wrapper for {@link SharedPGPCertificateDirectory} implementations.
|
||||
*/
|
||||
public class CachingSharedPGPCertificateDirectoryWrapper
|
||||
implements SharedPGPCertificateDirectory {
|
||||
|
||||
private static final Map<String, String> tagMap = new HashMap<>();
|
||||
private static final Map<String, Certificate> certificateMap = new HashMap<>();
|
||||
private final SharedPGPCertificateDirectory underlyingCertificateDirectory;
|
||||
|
||||
public CachingSharedPGPCertificateDirectoryWrapper(SharedPGPCertificateDirectory wrapped) {
|
||||
this.underlyingCertificateDirectory = wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given certificate under the given identifier into the cache.
|
||||
*
|
||||
* @param identifier fingerprint or special name
|
||||
* @param certificate certificate
|
||||
*/
|
||||
private void remember(String identifier, Certificate certificate) {
|
||||
certificateMap.put(identifier, certificate);
|
||||
try {
|
||||
tagMap.put(identifier, certificate.getTag());
|
||||
} catch (IOException e) {
|
||||
tagMap.put(identifier, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the cached tag differs from the provided tag.
|
||||
*
|
||||
* @param identifier fingerprint or special name
|
||||
* @param tag tag
|
||||
* @return true if cached tag differs, false otherwise
|
||||
*/
|
||||
private boolean tagChanged(String identifier, String tag) {
|
||||
String tack = tagMap.get(identifier);
|
||||
return !tagEquals(tag, tack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if tag and tack are equal, false otherwise.
|
||||
* @param tag tag
|
||||
* @param tack other tag
|
||||
* @return true if equal
|
||||
*/
|
||||
private static boolean tagEquals(String tag, String tack) {
|
||||
return (tag == null && tack == null)
|
||||
|| tag != null && tag.equals(tack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
public void invalidate() {
|
||||
certificateMap.clear();
|
||||
tagMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getByFingerprint(String fingerprint)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
Certificate certificate = certificateMap.get(fingerprint);
|
||||
if (certificate == null) {
|
||||
certificate = underlyingCertificateDirectory.getByFingerprint(fingerprint);
|
||||
if (certificate != null) {
|
||||
remember(fingerprint, certificate);
|
||||
}
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getBySpecialName(String specialName)
|
||||
throws IOException, BadNameException {
|
||||
Certificate certificate = certificateMap.get(specialName);
|
||||
if (certificate == null) {
|
||||
certificate = underlyingCertificateDirectory.getBySpecialName(specialName);
|
||||
if (certificate != null) {
|
||||
remember(specialName, certificate);
|
||||
}
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
if (tagChanged(fingerprint, tag)) {
|
||||
return getByFingerprint(fingerprint);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getBySpecialNameIfChanged(String specialName, String tag)
|
||||
throws IOException, BadNameException {
|
||||
if (tagChanged(specialName, tag)) {
|
||||
return getBySpecialName(specialName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate insert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, InterruptedException {
|
||||
Certificate certificate = underlyingCertificateDirectory.insert(data, merge);
|
||||
remember(certificate.getFingerprint(), certificate);
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate tryInsert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException {
|
||||
Certificate certificate = underlyingCertificateDirectory.tryInsert(data, merge);
|
||||
if (certificate != null) {
|
||||
remember(certificate.getFingerprint(), certificate);
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, BadNameException, InterruptedException {
|
||||
Certificate certificate = underlyingCertificateDirectory.insertWithSpecialName(specialName, data, merge);
|
||||
remember(specialName, certificate);
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, BadNameException {
|
||||
Certificate certificate = underlyingCertificateDirectory.tryInsertWithSpecialName(specialName, data, merge);
|
||||
if (certificate != null) {
|
||||
remember(specialName, certificate);
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Certificate> items() {
|
||||
|
||||
Iterator<Certificate> iterator = underlyingCertificateDirectory.items();
|
||||
|
||||
return new Iterator<Certificate>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate next() {
|
||||
Certificate certificate = iterator.next();
|
||||
remember(certificate.getFingerprint(), certificate);
|
||||
return certificate;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> fingerprints() {
|
||||
return underlyingCertificateDirectory.fingerprints();
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,10 @@ public class FileLockingMechanism implements LockingMechanism {
|
|||
this.lockFile = lockFile;
|
||||
}
|
||||
|
||||
public static FileLockingMechanism defaultDirectoryFileLock(File baseDirectory) {
|
||||
return new FileLockingMechanism(new File(baseDirectory, "writelock"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void lockDirectory() throws IOException, InterruptedException {
|
||||
if (randomAccessFile != null) {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import pgp.cert_d.exception.BadNameException;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FilenameResolver {
|
||||
|
||||
private final File baseDirectory;
|
||||
private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$");
|
||||
|
||||
public FilenameResolver(File baseDirectory) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
}
|
||||
|
||||
public File getBaseDirectory() {
|
||||
return baseDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the file location for the certificate addressed by the given
|
||||
* lowercase hexadecimal OpenPGP fingerprint.
|
||||
*
|
||||
* @param fingerprint fingerprint
|
||||
* @return absolute certificate file location
|
||||
* @throws BadNameException
|
||||
*/
|
||||
public File getCertFileByFingerprint(String fingerprint) throws BadNameException {
|
||||
if (!isFingerprint(fingerprint)) {
|
||||
throw new BadNameException();
|
||||
}
|
||||
|
||||
// is fingerprint
|
||||
File subdirectory = new File(getBaseDirectory(), fingerprint.substring(0, 2));
|
||||
File file = new File(subdirectory, fingerprint.substring(2));
|
||||
return file;
|
||||
}
|
||||
|
||||
public File getCertFileBySpecialName(String specialName) throws BadNameException {
|
||||
if (!isSpecialName(specialName)) {
|
||||
throw new BadNameException();
|
||||
}
|
||||
|
||||
return new File(getBaseDirectory(), specialName);
|
||||
}
|
||||
|
||||
private boolean isFingerprint(String fingerprint) {
|
||||
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
|
||||
}
|
||||
|
||||
private boolean isSpecialName(String specialName) {
|
||||
return SpecialNames.lookupSpecialName(specialName) != null;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,21 +15,29 @@ import pgp.certificate_store.MergeCallback;
|
|||
|
||||
public interface SharedPGPCertificateDirectory {
|
||||
|
||||
Certificate get(String fingerprint) throws IOException, BadNameException;
|
||||
Certificate getByFingerprint(String fingerprint)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Certificate get(SpecialName specialName) throws IOException, BadNameException;
|
||||
Certificate getBySpecialName(String specialName)
|
||||
throws IOException, BadNameException;
|
||||
|
||||
Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException;
|
||||
Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
Certificate getIfChanged(SpecialName specialName, String tag) throws IOException, BadNameException;
|
||||
Certificate getBySpecialNameIfChanged(String specialName, String tag)
|
||||
throws IOException, BadNameException;
|
||||
|
||||
Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException;
|
||||
Certificate insert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, InterruptedException;
|
||||
|
||||
Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
|
||||
Certificate tryInsert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException;
|
||||
|
||||
Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException, InterruptedException;
|
||||
Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, BadNameException, InterruptedException;
|
||||
|
||||
Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
|
||||
Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, BadNameException;
|
||||
|
||||
Iterator<Certificate> items();
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import java.io.InputStream;
|
|||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import pgp.cert_d.exception.BadDataException;
|
||||
import pgp.cert_d.exception.BadNameException;
|
||||
|
@ -25,77 +24,70 @@ import pgp.certificate_store.CertificateReaderBackend;
|
|||
|
||||
public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory {
|
||||
|
||||
private final File baseDirectory;
|
||||
private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$");
|
||||
|
||||
private final FilenameResolver resolver;
|
||||
private final LockingMechanism writeLock;
|
||||
private final CertificateReaderBackend certificateReaderBackend;
|
||||
|
||||
public SharedPGPCertificateDirectoryImpl(CertificateReaderBackend certificateReaderBackend)
|
||||
throws NotAStoreException {
|
||||
this(OSUtil.getDefaultBaseDir(), certificateReaderBackend);
|
||||
this(
|
||||
BaseDirectoryProvider.getDefaultBaseDir(),
|
||||
certificateReaderBackend);
|
||||
}
|
||||
|
||||
public SharedPGPCertificateDirectoryImpl(File baseDirectory, CertificateReaderBackend certificateReaderBackend)
|
||||
throws NotAStoreException {
|
||||
this(
|
||||
certificateReaderBackend,
|
||||
new FilenameResolver(baseDirectory),
|
||||
FileLockingMechanism.defaultDirectoryFileLock(baseDirectory));
|
||||
}
|
||||
|
||||
public SharedPGPCertificateDirectoryImpl(
|
||||
CertificateReaderBackend certificateReaderBackend,
|
||||
FilenameResolver filenameResolver,
|
||||
LockingMechanism writeLock)
|
||||
throws NotAStoreException {
|
||||
this.certificateReaderBackend = certificateReaderBackend;
|
||||
this.baseDirectory = baseDirectory;
|
||||
this.resolver = filenameResolver;
|
||||
this.writeLock = writeLock;
|
||||
|
||||
File baseDirectory = resolver.getBaseDirectory();
|
||||
if (!baseDirectory.exists()) {
|
||||
if (!baseDirectory.mkdirs()) {
|
||||
throw new NotAStoreException("Cannot create base directory '" + getBaseDirectory().getAbsolutePath() + "'");
|
||||
throw new NotAStoreException("Cannot create base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "'");
|
||||
}
|
||||
} else {
|
||||
if (baseDirectory.isFile()) {
|
||||
throw new NotAStoreException("Base directory '" + getBaseDirectory().getAbsolutePath() + "' appears to be a file.");
|
||||
throw new NotAStoreException("Base directory '" + resolver.getBaseDirectory().getAbsolutePath() + "' appears to be a file.");
|
||||
}
|
||||
}
|
||||
writeLock = new FileLockingMechanism(new File(getBaseDirectory(), "writelock"));
|
||||
}
|
||||
|
||||
public File getBaseDirectory() {
|
||||
return baseDirectory;
|
||||
}
|
||||
|
||||
private File getCertFile(String fingerprint) throws BadNameException {
|
||||
if (!isFingerprint(fingerprint)) {
|
||||
throw new BadNameException();
|
||||
}
|
||||
|
||||
// is fingerprint
|
||||
File subdirectory = new File(getBaseDirectory(), fingerprint.substring(0, 2));
|
||||
File file = new File(subdirectory, fingerprint.substring(2));
|
||||
return file;
|
||||
}
|
||||
|
||||
private File getCertFile(SpecialName specialName) {
|
||||
return new File(getBaseDirectory(), specialName.getValue());
|
||||
}
|
||||
|
||||
private boolean isFingerprint(String fingerprint) {
|
||||
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate get(String fingerprint) throws IOException, BadNameException {
|
||||
File certFile = getCertFile(fingerprint);
|
||||
public Certificate getByFingerprint(String fingerprint)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
File certFile = resolver.getCertFileByFingerprint(fingerprint);
|
||||
if (!certFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInputStream fileIn = new FileInputStream(certFile);
|
||||
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||
Certificate certificate = certificateReaderBackend.readCertificate(bufferedIn);
|
||||
|
||||
if (!certificate.getFingerprint().equals(fingerprint)) {
|
||||
// TODO: Figure out more suitable exception
|
||||
throw new BadNameException();
|
||||
throw new BadDataException();
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate get(SpecialName specialName) throws IOException {
|
||||
File certFile = getCertFile(specialName);
|
||||
public Certificate getBySpecialName(String specialName)
|
||||
throws IOException, BadNameException {
|
||||
File certFile = resolver.getCertFileBySpecialName(specialName);
|
||||
if (!certFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -108,8 +100,9 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException {
|
||||
Certificate certificate = get(fingerprint);
|
||||
public Certificate getByFingerprintIfChanged(String fingerprint, String tag)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
Certificate certificate = getByFingerprint(fingerprint);
|
||||
if (certificate.getTag().equals(tag)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -117,8 +110,9 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate getIfChanged(SpecialName specialName, String tag) throws IOException {
|
||||
Certificate certificate = get(specialName);
|
||||
public Certificate getBySpecialNameIfChanged(String specialName, String tag)
|
||||
throws IOException, BadNameException {
|
||||
Certificate certificate = getBySpecialName(specialName);
|
||||
if (certificate.getTag().equals(tag)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -126,7 +120,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException {
|
||||
public Certificate insert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException, InterruptedException {
|
||||
writeLock.lockDirectory();
|
||||
|
||||
Certificate certificate = _insert(data, merge);
|
||||
|
@ -136,7 +131,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
||||
public Certificate tryInsert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException {
|
||||
if (!writeLock.tryLockDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -147,13 +143,14 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
return certificate;
|
||||
}
|
||||
|
||||
private Certificate _insert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
||||
private Certificate _insert(InputStream data, MergeCallback merge)
|
||||
throws IOException, BadDataException {
|
||||
Certificate newCertificate = certificateReaderBackend.readCertificate(data);
|
||||
Certificate existingCertificate;
|
||||
File certFile;
|
||||
try {
|
||||
existingCertificate = get(newCertificate.getFingerprint());
|
||||
certFile = getCertFile(newCertificate.getFingerprint());
|
||||
existingCertificate = getByFingerprint(newCertificate.getFingerprint());
|
||||
certFile = resolver.getCertFileByFingerprint(newCertificate.getFingerprint());
|
||||
} catch (BadNameException e) {
|
||||
throw new BadDataException();
|
||||
}
|
||||
|
@ -167,7 +164,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
return newCertificate;
|
||||
}
|
||||
|
||||
private void writeCertificate(Certificate certificate, File certFile) throws IOException {
|
||||
private void writeCertificate(Certificate certificate, File certFile)
|
||||
throws IOException {
|
||||
certFile.getParentFile().mkdirs();
|
||||
if (!certFile.exists() && !certFile.createNewFile()) {
|
||||
throw new IOException("Could not create cert file " + certFile.getAbsolutePath());
|
||||
|
@ -187,7 +185,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException, InterruptedException {
|
||||
public Certificate insertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadNameException, BadDataException, InterruptedException {
|
||||
writeLock.lockDirectory();
|
||||
|
||||
Certificate certificate = _insertSpecial(specialName, data, merge);
|
||||
|
@ -197,7 +196,8 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
|
||||
public Certificate tryInsertWithSpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
if (!writeLock.tryLockDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -208,10 +208,11 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
return certificate;
|
||||
}
|
||||
|
||||
private Certificate _insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
|
||||
private Certificate _insertSpecial(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, BadNameException, BadDataException {
|
||||
Certificate newCertificate = certificateReaderBackend.readCertificate(data);
|
||||
Certificate existingCertificate = get(specialName);
|
||||
File certFile = getCertFile(specialName);
|
||||
Certificate existingCertificate = getBySpecialName(specialName);
|
||||
File certFile = resolver.getCertFileBySpecialName(specialName);
|
||||
|
||||
if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) {
|
||||
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||
|
@ -230,24 +231,7 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
|||
|
||||
// Constructor... wtf.
|
||||
{
|
||||
for (SpecialName specialName : SpecialName.values()) {
|
||||
File certFile = getCertFile(specialName);
|
||||
if (certFile.exists()) {
|
||||
certificateQueue.add(
|
||||
new Lazy<Certificate>() {
|
||||
@Override
|
||||
Certificate get() {
|
||||
try {
|
||||
return certificateReaderBackend.readCertificate(new FileInputStream(certFile));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("File got deleted.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File[] subdirectories = baseDirectory.listFiles(new FileFilter() {
|
||||
File[] subdirectories = resolver.getBaseDirectory().listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isDirectory() && file.getName().matches("^[a-f0-9]{2}$");
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Enum of known special names.
|
||||
*/
|
||||
public enum SpecialName {
|
||||
/**
|
||||
* Certificate acting as trust root.
|
||||
* This certificate is used to delegate other trustworthy certificates and to bind pet names to certificates.
|
||||
*/
|
||||
TRUST_ROOT("trust-root"),
|
||||
;
|
||||
|
||||
static Map<String, SpecialName> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (SpecialName specialName : values()) {
|
||||
MAP.put(specialName.getValue(), specialName);
|
||||
}
|
||||
}
|
||||
|
||||
final String value;
|
||||
|
||||
SpecialName(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static SpecialName fromString(String value) {
|
||||
return MAP.get(value);
|
||||
}
|
||||
}
|
22
pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java
Normal file
22
pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialNames.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SpecialNames {
|
||||
|
||||
private static final Map<String, String> SPECIAL_NAMES = new HashMap<>();
|
||||
|
||||
static {
|
||||
SPECIAL_NAMES.put("TRUST-ROOT", "trust-root");
|
||||
SPECIAL_NAMES.put("trust-root", "trust-root");
|
||||
}
|
||||
|
||||
public static String lookupSpecialName(String specialName) {
|
||||
return SPECIAL_NAMES.get(specialName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import pgp.cert_d.exception.BadNameException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class FilenameResolverTest {
|
||||
|
||||
private File baseDir;
|
||||
private FilenameResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws IOException {
|
||||
baseDir = Files.createTempDirectory("filenameresolver").toFile();
|
||||
baseDir.deleteOnExit();
|
||||
resolver = new FilenameResolver(baseDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileForFingerprint1() throws BadNameException {
|
||||
String fingerprint = "d1a66e1a23b182c9980f788cfbfcc82a015e7330";
|
||||
|
||||
File subDir = new File(baseDir, "d1");
|
||||
File expected = new File(subDir, "a66e1a23b182c9980f788cfbfcc82a015e7330");
|
||||
|
||||
assertEquals(resolver.getCertFileByFingerprint(fingerprint).getAbsolutePath(), expected.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileForFingerprint2() throws BadNameException {
|
||||
String fingerprint = "eb85bb5fa33a75e15e944e63f231550c4f47e38e";
|
||||
|
||||
File subDir = new File(baseDir, "eb");
|
||||
File expected = new File(subDir, "85bb5fa33a75e15e944e63f231550c4f47e38e");
|
||||
|
||||
assertEquals(resolver.getCertFileByFingerprint(fingerprint).getAbsolutePath(), expected.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileForInvalidNonHexFingerprint() {
|
||||
String invalidFingerprint = "thisisnothexadecimalthisisnothexadecimal";
|
||||
assertThrows(BadNameException.class, () -> resolver.getCertFileByFingerprint(invalidFingerprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileForInvalidWrongLengthFingerprint() {
|
||||
String invalidFingerprint = "d1a66e1a23b182c9980f788cfbfcc82a015e73301234";
|
||||
assertThrows(BadNameException.class, () -> resolver.getCertFileByFingerprint(invalidFingerprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileForNullFingerprint() {
|
||||
assertThrows(NullPointerException.class, () -> resolver.getCertFileByFingerprint(null));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.cert_d;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
public class SpecialNamesTest {
|
||||
|
||||
@Test
|
||||
public void bothTrustRootNotationsAreRecognized() {
|
||||
assertEquals("trust-root", SpecialNames.lookupSpecialName("trust-root"));
|
||||
assertEquals("trust-root", SpecialNames.lookupSpecialName("TRUST-ROOT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidSpecialNameReturnsNull() {
|
||||
assertNull(SpecialNames.lookupSpecialName("invalid"));
|
||||
assertNull(SpecialNames.lookupSpecialName("trust root"));
|
||||
assertNull(SpecialNames.lookupSpecialName("writelock"));
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Merge a given certificate (update) with an existing certificate.
|
||||
*/
|
||||
|
@ -18,6 +20,6 @@ public interface MergeCallback {
|
|||
* @param existing optional already existing copy of the certificate
|
||||
* @return merged certificate
|
||||
*/
|
||||
Certificate merge(Certificate data, Certificate existing);
|
||||
Certificate merge(Certificate data, Certificate existing) throws IOException;
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import java.io.InputStream;
|
|||
import java.util.Iterator;
|
||||
|
||||
import pgp.cert_d.SharedPGPCertificateDirectory;
|
||||
import pgp.cert_d.SpecialName;
|
||||
import pgp.cert_d.SpecialNames;
|
||||
import pgp.cert_d.exception.BadDataException;
|
||||
import pgp.cert_d.exception.BadNameException;
|
||||
import pgp.certificate_store.Certificate;
|
||||
|
@ -37,38 +37,42 @@ public class SharedPGPCertificateDirectoryAdapter
|
|||
@Override
|
||||
public Certificate getCertificate(String identifier)
|
||||
throws IOException {
|
||||
SpecialName specialName = SpecialName.fromString(identifier);
|
||||
String specialName = SpecialNames.lookupSpecialName(identifier);
|
||||
if (specialName != null) {
|
||||
try {
|
||||
return directory.get(specialName);
|
||||
return directory.getBySpecialName(specialName);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Unknown special name " + identifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return directory.get(identifier);
|
||||
return directory.getByFingerprint(identifier);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e);
|
||||
throw new IllegalArgumentException("Invalid fingerprint " + identifier, e);
|
||||
} catch (BadDataException e) {
|
||||
throw new IOException("Bad data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate getCertificateIfChanged(String identifier, String tag)
|
||||
throws IOException {
|
||||
SpecialName specialName = SpecialName.fromString(identifier);
|
||||
String specialName = SpecialNames.lookupSpecialName(identifier);
|
||||
if (specialName != null) {
|
||||
try {
|
||||
return directory.getIfChanged(specialName, tag);
|
||||
return directory.getBySpecialNameIfChanged(specialName, tag);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Unknown special name " + identifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return directory.getIfChanged(identifier, tag);
|
||||
return directory.getByFingerprintIfChanged(identifier, tag);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Invalid fingerprint or unknown special name " + identifier, e);
|
||||
throw new IllegalArgumentException("Invalid fingerprint " + identifier, e);
|
||||
} catch (BadDataException e) {
|
||||
throw new IOException("Bad data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,12 +100,12 @@ public class SharedPGPCertificateDirectoryAdapter
|
|||
public Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException, InterruptedException {
|
||||
try {
|
||||
SpecialName specialNameEnum = SpecialName.fromString(specialName);
|
||||
if (specialNameEnum == null) {
|
||||
String specialNameValidated = SpecialNames.lookupSpecialName(specialName);
|
||||
if (specialNameValidated == null) {
|
||||
throw new IllegalArgumentException("Unknown special name " + specialName);
|
||||
}
|
||||
|
||||
return directory.insertSpecial(specialNameEnum, data, merge);
|
||||
return directory.insertWithSpecialName(specialNameValidated, data, merge);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Unknown special name " + specialName);
|
||||
} catch (BadDataException e) {
|
||||
|
@ -113,12 +117,12 @@ public class SharedPGPCertificateDirectoryAdapter
|
|||
public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
|
||||
throws IOException {
|
||||
try {
|
||||
SpecialName specialNameEnum = SpecialName.fromString(specialName);
|
||||
if (specialNameEnum == null) {
|
||||
String specialNameValidated = SpecialNames.lookupSpecialName(specialName);
|
||||
if (specialNameValidated == null) {
|
||||
throw new IllegalArgumentException("Unknown special name " + specialName);
|
||||
}
|
||||
|
||||
return directory.tryInsertSpecial(specialNameEnum, data, merge);
|
||||
return directory.tryInsertWithSpecialName(specialNameValidated, data, merge);
|
||||
} catch (BadNameException e) {
|
||||
throw new IllegalArgumentException("Unknown special name " + specialName);
|
||||
} catch (BadDataException e) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.platform.commons.logging.Logger;
|
||||
import org.junit.platform.commons.logging.LoggerFactory;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.certificate_store.CertificateCertificateReader;
|
||||
import org.pgpainless.certificate_store.CertificateReader;
|
||||
import org.pgpainless.key.OpenPgpFingerprint;
|
||||
import pgp.cert_d.FileLockingMechanism;
|
||||
import pgp.cert_d.LockingMechanism;
|
||||
|
@ -37,8 +37,9 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
|
||||
public class SharedPGPCertificateDirectoryTest {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class);
|
||||
SharedPGPCertificateDirectory directory;
|
||||
private static final Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class);
|
||||
private File tempDir;
|
||||
private SharedPGPCertificateDirectory directory;
|
||||
|
||||
private static MergeCallback dummyMerge = new MergeCallback() {
|
||||
@Override
|
||||
|
@ -49,29 +50,29 @@ public class SharedPGPCertificateDirectoryTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws IOException, NotAStoreException {
|
||||
File tempDir = Files.createTempDirectory("pgp.cert.d-").toFile();
|
||||
tempDir = Files.createTempDirectory("pgp.cert.d-").toFile();
|
||||
tempDir.deleteOnExit();
|
||||
directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateCertificateReader());
|
||||
directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateReader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleInsertGet() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException, BadNameException {
|
||||
logger.info(() -> "simpleInsertGet: " + ((SharedPGPCertificateDirectoryImpl) directory).getBaseDirectory().getAbsolutePath());
|
||||
logger.info(() -> "simpleInsertGet: " + tempDir.getAbsolutePath());
|
||||
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(key);
|
||||
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert);
|
||||
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
|
||||
|
||||
// standard case: get() is null
|
||||
assertNull(directory.get(fingerprint.toString().toLowerCase()));
|
||||
assertNull(directory.getByFingerprint(fingerprint.toString().toLowerCase()));
|
||||
|
||||
// insert and check returned certs fingerprint
|
||||
Certificate certificate = directory.insert(certIn, dummyMerge);
|
||||
assertEquals(fingerprint.toString().toLowerCase(), certificate.getFingerprint());
|
||||
|
||||
// getIfChanged
|
||||
assertNull(directory.getIfChanged(certificate.getFingerprint(), certificate.getTag()));
|
||||
assertNotNull(directory.getIfChanged(certificate.getFingerprint(), "invalidTag"));
|
||||
assertNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), certificate.getTag()));
|
||||
assertNotNull(directory.getByFingerprintIfChanged(certificate.getFingerprint(), "invalidTag"));
|
||||
|
||||
// tryInsert
|
||||
certIn = new ByteArrayInputStream(cert.getEncoded());
|
||||
|
@ -80,13 +81,12 @@ public class SharedPGPCertificateDirectoryTest {
|
|||
|
||||
@Test
|
||||
public void tryInsertFailsWithLockedStore() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException {
|
||||
SharedPGPCertificateDirectoryImpl fileDirectory = (SharedPGPCertificateDirectoryImpl) directory;
|
||||
logger.info(() -> "tryInsertFailsWithLockedStore: " + fileDirectory.getBaseDirectory().getAbsolutePath());
|
||||
logger.info(() -> "tryInsertFailsWithLockedStore: " + tempDir.getAbsolutePath());
|
||||
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(key);
|
||||
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
|
||||
|
||||
File lockFile = new File(fileDirectory.getBaseDirectory(), "writelock");
|
||||
File lockFile = new File(tempDir, "writelock");
|
||||
LockingMechanism lock = new FileLockingMechanism(lockFile);
|
||||
lock.lockDirectory();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.pgpainless.key.OpenPgpFingerprint;
|
|||
import pgp.certificate_store.Certificate;
|
||||
import pgp.certificate_store.CertificateReaderBackend;
|
||||
|
||||
public class CertificateCertificateReader implements CertificateReaderBackend {
|
||||
public class CertificateReader implements CertificateReaderBackend {
|
||||
|
||||
@Override
|
||||
public Certificate readCertificate(InputStream inputStream) throws IOException {
|
Loading…
Reference in a new issue