mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-12 23:42:06 +01:00
Module structure
This commit is contained in:
parent
a5d592a102
commit
7703cc263d
14 changed files with 381 additions and 58 deletions
27
pgp-cert-d-java/build.gradle
Normal file
27
pgp-cert-d-java/build.gradle
Normal 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()
|
||||
}
|
|
@ -1,41 +1,10 @@
|
|||
package org.pgpainless.key.storage;
|
||||
package pgp.cert_d;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CertDStore {
|
||||
public class OSUtil {
|
||||
|
||||
private final File baseDirectory;
|
||||
private static final String STORE_NAME = "pgp.cert.d";
|
||||
|
||||
public CertDStore() {
|
||||
this(getDefaultBaseDir());
|
||||
}
|
||||
|
||||
public CertDStore(File baseDirectory) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
}
|
||||
|
||||
public File fingerprintToPrefixDir(String fingerprint) {
|
||||
String dirName = fingerprint.toLowerCase().substring(0, 2);
|
||||
return new File(baseDirectory, dirName);
|
||||
}
|
||||
|
||||
public String fingerprintToCertFileName(String fingerprint) {
|
||||
String certFileName = fingerprint.toLowerCase().substring(2);
|
||||
return certFileName;
|
||||
}
|
||||
|
||||
public File fingerprintToCertFile(String fingerprint) {
|
||||
File dir = fingerprintToPrefixDir(fingerprint);
|
||||
File certFile = new File(dir, fingerprintToCertFileName(fingerprint));
|
||||
return certFile;
|
||||
}
|
||||
|
||||
public File getBaseDirectory() {
|
||||
return baseDirectory;
|
||||
}
|
||||
|
||||
private static File getDefaultBaseDir() {
|
||||
public static File getDefaultBaseDir() {
|
||||
// Check for environment variable
|
||||
String baseDirFromEnv = System.getenv("PGP_CERT_D");
|
||||
if (baseDirFromEnv != null) {
|
||||
|
@ -49,6 +18,7 @@ public class CertDStore {
|
|||
}
|
||||
|
||||
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";
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialName.java
Normal file
38
pgp-cert-d-java/src/main/java/pgp/cert_d/SpecialName.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
25
pgp-certificate-store/build.gradle
Normal file
25
pgp-certificate-store/build.gradle
Normal file
|
@ -0,0 +1,25 @@
|
|||
// 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"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.pgpainless.key.storage;
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -9,7 +9,7 @@ public interface CertificateStore {
|
|||
Item get(String identifier) throws IOException;
|
||||
|
||||
Item getIfChanged(String identifier, String tag) throws IOException;
|
||||
<
|
||||
|
||||
Item insert(InputStream data, MergeCallback merge) throws IOException;
|
||||
|
||||
Item tryInsert(InputStream data, MergeCallback merge) throws IOException;
|
|
@ -1,4 +1,4 @@
|
|||
package org.pgpainless.key.storage;
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
@ -14,14 +14,29 @@ public class Item {
|
|||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the certificate.
|
||||
*
|
||||
* @return certificate fingerprint
|
||||
*/
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a tag used to check if the certificate was changed between retrievals.
|
||||
*
|
||||
* @return tag
|
||||
*/
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link InputStream} containing the certificate data.
|
||||
*
|
||||
* @return data
|
||||
*/
|
||||
public InputStream getData() {
|
||||
return data;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package org.pgpainless.key.storage;
|
||||
package pgp.certificate_store;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
@ -18,6 +17,6 @@ public interface MergeCallback {
|
|||
* @param existing optional input stream containing an already existing copy of the certificate
|
||||
* @return output stream containing the binary representation of the merged certificate
|
||||
*/
|
||||
OutputStream merge(InputStream data, @Nullable InputStream existing);
|
||||
OutputStream merge(InputStream data, InputStream existing);
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package org.pgpainless.key.storage;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CertDStoreTest {
|
||||
|
||||
@Test
|
||||
public void testGetDefaultBaseDir() {
|
||||
CertDStore store = new CertDStore();
|
||||
File baseDir = store.getBaseDirectory();
|
||||
assertEquals("pgp.cert.d", baseDir.getName());
|
||||
}
|
||||
}
|
|
@ -6,5 +6,7 @@ rootProject.name = 'PGPainless'
|
|||
|
||||
include 'pgpainless-core',
|
||||
'pgpainless-sop',
|
||||
'pgpainless-cli'
|
||||
'pgpainless-cli',
|
||||
'pgp-certificate-store',
|
||||
'pgp-cert-d-java'
|
||||
|
||||
|
|
Loading…
Reference in a new issue