1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-16 09:11:08 +01:00

Module structure

This commit is contained in:
Paul Schaub 2022-01-21 01:53:48 +01:00
parent 9d2f06b14e
commit 3a690079fe
14 changed files with 381 additions and 58 deletions

View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Logging
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
api project(":pgp-certificate-store")
}
test {
useJUnitPlatform()
}

View file

@ -0,0 +1,45 @@
package pgp.cert_d;
import java.io.File;
public class OSUtil {
public static File getDefaultBaseDir() {
// Check for environment variable
String baseDirFromEnv = System.getenv("PGP_CERT_D");
if (baseDirFromEnv != null) {
return new File(baseDirFromEnv);
}
// return OS-specific default dir
String osName = System.getProperty("os.name", "generic")
.toLowerCase();
return getDefaultBaseDirForOS(osName, File.separator);
}
public static File getDefaultBaseDirForOS(String osName, String separator) {
String STORE_NAME = "pgp.cert.d";
if (osName.contains("win")) {
String appData = System.getenv("APPDATA");
String roaming = appData + separator + "Roaming";
return new File(roaming, STORE_NAME);
}
if (osName.contains("nux")) {
String xdg_data_home = System.getenv("XDG_DATA_HOME");
String rootPath = xdg_data_home;
if (xdg_data_home == null) {
rootPath = System.getProperty("user.home") + separator + ".local" + separator + "share";
}
return new File(rootPath, STORE_NAME);
}
if (osName.contains("mac")) {
String home = System.getenv("HOME");
return new File(home + separator + "Library" + separator + "Application Support", STORE_NAME);
}
throw new IllegalArgumentException("Unknown OS " + osName);
}
}

View file

@ -0,0 +1,29 @@
package pgp.cert_d;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import pgp.cert_d.exception.BadDataException;
import pgp.cert_d.exception.BadNameException;
import pgp.certificate_store.Item;
import pgp.certificate_store.MergeCallback;
public interface SharedPGPCertificateDirectory {
Item get(String identifier) throws IOException, BadNameException;
Item getIfChanged(String identifier, String tag) throws IOException, BadNameException;
Item insert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
Item tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
Item insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
Item tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
Iterator<Item> items();
Iterator<String> fingerprints();
}

View file

@ -0,0 +1,204 @@
package pgp.cert_d;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.util.Iterator;
import java.util.regex.Pattern;
import pgp.cert_d.exception.BadDataException;
import pgp.cert_d.exception.BadNameException;
import pgp.cert_d.exception.NotAStoreException;
import pgp.certificate_store.Item;
import pgp.certificate_store.MergeCallback;
public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory {
private final File baseDirectory;
private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$");
private final WriteLock writeLock;
public SharedPGPCertificateDirectoryImpl() throws NotAStoreException {
this(OSUtil.getDefaultBaseDir());
}
public SharedPGPCertificateDirectoryImpl(File baseDirectory) throws NotAStoreException {
this.baseDirectory = baseDirectory;
if (!baseDirectory.exists()) {
if (!baseDirectory.mkdirs()) {
throw new NotAStoreException("Cannot create base directory '" + getBaseDirectory().getAbsolutePath() + "'");
}
} else {
if (baseDirectory.isFile()) {
throw new NotAStoreException("Base directory '" + getBaseDirectory().getAbsolutePath() + "' appears to be a file.");
}
}
writeLock = new WriteLock(new File(getBaseDirectory(), "writelock"));
}
public File getBaseDirectory() {
return baseDirectory;
}
private File getCertFile(String identifier) throws BadNameException {
SpecialName specialName = SpecialName.fromString(identifier);
if (specialName != null) {
// is special name
return new File(getBaseDirectory(), specialName.getValue());
} else {
if (!isFingerprint(identifier)) {
throw new BadNameException();
}
// is fingerprint
File subdirectory = new File(getBaseDirectory(), identifier.substring(0, 2));
File file = new File(subdirectory, identifier.substring(2));
return file;
}
}
private boolean isFingerprint(String identifier) {
return openPgpV4FingerprintPattern.matcher(identifier).matches();
}
@Override
public Item get(String identifier) throws IOException, BadNameException {
File certFile = getCertFile(identifier);
if (certFile.exists()) {
return new Item(identifier, "TAG", new FileInputStream(certFile));
}
return null;
}
@Override
public Item getIfChanged(String identifier, String tag) throws IOException, BadNameException {
return null;
}
@Override
public Item insert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
writeLock.lock();
Item item = _insert(data, merge);
writeLock.release();
return item;
}
@Override
public Item tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
if (!writeLock.tryLock()) {
return null;
}
Item item = _insert(data, merge);
writeLock.release();
return item;
}
private Item _insert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
return null;
}
@Override
public Item insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
writeLock.lock();
Item item = _insertSpecial(specialName, data, merge);
writeLock.release();
return item;
}
@Override
public Item tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
if (!writeLock.tryLock()) {
return null;
}
Item item = _insertSpecial(specialName, data, merge);
writeLock.release();
return item;
}
private Item _insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
return null;
}
@Override
public Iterator<Item> items() {
return null;
}
@Override
public Iterator<String> fingerprints() {
return null;
}
public static class WriteLock {
private final File lockFile;
private RandomAccessFile randomAccessFile;
private FileLock fileLock;
public WriteLock(File lockFile) {
this.lockFile = lockFile;
}
public synchronized void lock() throws IOException {
if (randomAccessFile != null) {
throw new IllegalStateException("File already locked.");
}
try {
randomAccessFile = new RandomAccessFile(lockFile, "rw");
} catch (FileNotFoundException e) {
lockFile.createNewFile();
randomAccessFile = new RandomAccessFile(lockFile, "rw");
}
fileLock = randomAccessFile.getChannel().lock();
}
public synchronized boolean tryLock() throws IOException {
if (randomAccessFile != null) {
return false;
}
try {
randomAccessFile = new RandomAccessFile(lockFile, "rw");
} catch (FileNotFoundException e) {
lockFile.createNewFile();
randomAccessFile = new RandomAccessFile(lockFile, "rw");
}
fileLock = randomAccessFile.getChannel().tryLock();
if (fileLock == null) {
randomAccessFile.close();
randomAccessFile = null;
return false;
}
return true;
}
public synchronized void release() throws IOException {
if (lockFile.exists()) {
lockFile.delete();
}
if (fileLock != null) {
fileLock.release();
fileLock = null;
}
if (randomAccessFile != null) {
randomAccessFile.close();
randomAccessFile = null;
}
}
}
}

View file

@ -0,0 +1,38 @@
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);
}
}

View file

@ -0,0 +1,8 @@
package pgp.cert_d.exception;
/**
* The data was not a valid OpenPGP cert or key in binary format.
*/
public class BadDataException extends Exception {
}

View file

@ -0,0 +1,8 @@
package pgp.cert_d.exception;
/**
* Provided name was neither a valid fingerprint, nor a known special name.
*/
public class BadNameException extends Exception {
}

View file

@ -0,0 +1,15 @@
package pgp.cert_d.exception;
/**
* The base dir cannot possibly contain a store.
*/
public class NotAStoreException extends Exception {
public NotAStoreException() {
super();
}
public NotAStoreException(String message) {
super(message);
}
}