mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-16 01:12:05 +01:00
First working prototype
This commit is contained in:
parent
7703cc263d
commit
d086332677
23 changed files with 707 additions and 219 deletions
|
@ -0,0 +1,92 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.cert_d;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
|
|
||||||
|
public class FileLockingMechanism implements LockingMechanism {
|
||||||
|
|
||||||
|
private final File lockFile;
|
||||||
|
private RandomAccessFile randomAccessFile;
|
||||||
|
private FileLock fileLock;
|
||||||
|
|
||||||
|
public FileLockingMechanism(File lockFile) {
|
||||||
|
this.lockFile = lockFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void lockDirectory() throws IOException, InterruptedException {
|
||||||
|
if (randomAccessFile != null) {
|
||||||
|
// we own the lock already. Let's wait...
|
||||||
|
this.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
randomAccessFile = new RandomAccessFile(lockFile, "rw");
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
lockFile.createNewFile();
|
||||||
|
randomAccessFile = new RandomAccessFile(lockFile, "rw");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLock = randomAccessFile.getChannel().lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean tryLockDirectory() throws IOException {
|
||||||
|
if (randomAccessFile != null) {
|
||||||
|
// We already locked the directory for another write operation.
|
||||||
|
// We fail, since we have not yet released the lock from the other operation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
randomAccessFile = new RandomAccessFile(lockFile, "rw");
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
lockFile.createNewFile();
|
||||||
|
randomAccessFile = new RandomAccessFile(lockFile, "rw");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileLock = randomAccessFile.getChannel().tryLock();
|
||||||
|
if (fileLock == null) {
|
||||||
|
// try-lock failed, file is locked by another process.
|
||||||
|
randomAccessFile.close();
|
||||||
|
randomAccessFile = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (OverlappingFileLockException e) {
|
||||||
|
// Some other object is holding the lock.
|
||||||
|
randomAccessFile.close();
|
||||||
|
randomAccessFile = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void releaseDirectory() throws IOException {
|
||||||
|
// unlock file
|
||||||
|
if (fileLock != null) {
|
||||||
|
fileLock.release();
|
||||||
|
fileLock = null;
|
||||||
|
}
|
||||||
|
// close file
|
||||||
|
if (randomAccessFile != null) {
|
||||||
|
randomAccessFile.close();
|
||||||
|
randomAccessFile = null;
|
||||||
|
}
|
||||||
|
// delete file
|
||||||
|
if (lockFile.exists()) {
|
||||||
|
lockFile.delete();
|
||||||
|
}
|
||||||
|
// notify waiters
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.cert_d;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface LockingMechanism {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock the store for writes.
|
||||||
|
* Readers can continue to use the store and will always see consistent certs.
|
||||||
|
*/
|
||||||
|
void lockDirectory() throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try top lock the store for writes.
|
||||||
|
* Return false without locking the store in case the store was already locked.
|
||||||
|
*
|
||||||
|
* @return true if locking succeeded, false otherwise
|
||||||
|
*/
|
||||||
|
boolean tryLockDirectory() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the directory write-lock acquired via {@link #lockDirectory()}.
|
||||||
|
*/
|
||||||
|
void releaseDirectory() throws IOException;
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -6,24 +10,28 @@ import java.util.Iterator;
|
||||||
|
|
||||||
import pgp.cert_d.exception.BadDataException;
|
import pgp.cert_d.exception.BadDataException;
|
||||||
import pgp.cert_d.exception.BadNameException;
|
import pgp.cert_d.exception.BadNameException;
|
||||||
import pgp.certificate_store.Item;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.MergeCallback;
|
import pgp.certificate_store.MergeCallback;
|
||||||
|
|
||||||
public interface SharedPGPCertificateDirectory {
|
public interface SharedPGPCertificateDirectory {
|
||||||
|
|
||||||
Item get(String identifier) throws IOException, BadNameException;
|
Certificate get(String fingerprint) throws IOException, BadNameException;
|
||||||
|
|
||||||
Item getIfChanged(String identifier, String tag) throws IOException, BadNameException;
|
Certificate get(SpecialName specialName) throws IOException, BadNameException;
|
||||||
|
|
||||||
Item insert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
|
Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException;
|
||||||
|
|
||||||
Item tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
|
Certificate getIfChanged(SpecialName specialName, String tag) throws IOException, BadNameException;
|
||||||
|
|
||||||
Item insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
|
Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException;
|
||||||
|
|
||||||
Item tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
|
Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException;
|
||||||
|
|
||||||
Iterator<Item> items();
|
Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException, InterruptedException;
|
||||||
|
|
||||||
|
Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadDataException, BadNameException;
|
||||||
|
|
||||||
|
Iterator<Certificate> items();
|
||||||
|
|
||||||
Iterator<String> fingerprints();
|
Iterator<String> fingerprints();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,44 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d;
|
package pgp.cert_d;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.channels.FileLock;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import pgp.cert_d.exception.BadDataException;
|
import pgp.cert_d.exception.BadDataException;
|
||||||
import pgp.cert_d.exception.BadNameException;
|
import pgp.cert_d.exception.BadNameException;
|
||||||
import pgp.cert_d.exception.NotAStoreException;
|
import pgp.cert_d.exception.NotAStoreException;
|
||||||
import pgp.certificate_store.Item;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.MergeCallback;
|
import pgp.certificate_store.MergeCallback;
|
||||||
|
import pgp.certificate_store.ParserBackend;
|
||||||
|
|
||||||
public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory {
|
public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDirectory {
|
||||||
|
|
||||||
private final File baseDirectory;
|
private final File baseDirectory;
|
||||||
private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$");
|
private final Pattern openPgpV4FingerprintPattern = Pattern.compile("^[a-f0-9]{40}$");
|
||||||
|
|
||||||
private final WriteLock writeLock;
|
private final LockingMechanism writeLock;
|
||||||
|
private final ParserBackend parserBackend;
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl() throws NotAStoreException {
|
public SharedPGPCertificateDirectoryImpl(ParserBackend parserBackend)
|
||||||
this(OSUtil.getDefaultBaseDir());
|
throws NotAStoreException {
|
||||||
|
this(OSUtil.getDefaultBaseDir(), parserBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPGPCertificateDirectoryImpl(File baseDirectory) throws NotAStoreException {
|
public SharedPGPCertificateDirectoryImpl(File baseDirectory, ParserBackend parserBackend)
|
||||||
|
throws NotAStoreException {
|
||||||
|
this.parserBackend = parserBackend;
|
||||||
this.baseDirectory = baseDirectory;
|
this.baseDirectory = baseDirectory;
|
||||||
if (!baseDirectory.exists()) {
|
if (!baseDirectory.exists()) {
|
||||||
if (!baseDirectory.mkdirs()) {
|
if (!baseDirectory.mkdirs()) {
|
||||||
|
@ -38,167 +49,271 @@ public class SharedPGPCertificateDirectoryImpl implements SharedPGPCertificateDi
|
||||||
throw new NotAStoreException("Base directory '" + getBaseDirectory().getAbsolutePath() + "' appears to be a file.");
|
throw new NotAStoreException("Base directory '" + getBaseDirectory().getAbsolutePath() + "' appears to be a file.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeLock = new WriteLock(new File(getBaseDirectory(), "writelock"));
|
writeLock = new FileLockingMechanism(new File(getBaseDirectory(), "writelock"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getBaseDirectory() {
|
public File getBaseDirectory() {
|
||||||
return baseDirectory;
|
return baseDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getCertFile(String identifier) throws BadNameException {
|
private File getCertFile(String fingerprint) throws BadNameException {
|
||||||
SpecialName specialName = SpecialName.fromString(identifier);
|
if (!isFingerprint(fingerprint)) {
|
||||||
if (specialName != null) {
|
|
||||||
// is special name
|
|
||||||
return new File(getBaseDirectory(), specialName.getValue());
|
|
||||||
} else {
|
|
||||||
if (!isFingerprint(identifier)) {
|
|
||||||
throw new BadNameException();
|
throw new BadNameException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// is fingerprint
|
// is fingerprint
|
||||||
File subdirectory = new File(getBaseDirectory(), identifier.substring(0, 2));
|
File subdirectory = new File(getBaseDirectory(), fingerprint.substring(0, 2));
|
||||||
File file = new File(subdirectory, identifier.substring(2));
|
File file = new File(subdirectory, fingerprint.substring(2));
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File getCertFile(SpecialName specialName) {
|
||||||
|
return new File(getBaseDirectory(), specialName.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFingerprint(String identifier) {
|
private boolean isFingerprint(String fingerprint) {
|
||||||
return openPgpV4FingerprintPattern.matcher(identifier).matches();
|
return openPgpV4FingerprintPattern.matcher(fingerprint).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item get(String identifier) throws IOException, BadNameException {
|
public Certificate get(String fingerprint) throws IOException, BadNameException {
|
||||||
File certFile = getCertFile(identifier);
|
File certFile = getCertFile(fingerprint);
|
||||||
|
if (!certFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
FileInputStream fileIn = new FileInputStream(certFile);
|
||||||
|
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||||
|
Certificate certificate = parserBackend.readCertificate(bufferedIn);
|
||||||
|
|
||||||
|
if (!certificate.getFingerprint().equals(fingerprint)) {
|
||||||
|
// TODO: Figure out more suitable exception
|
||||||
|
throw new BadNameException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate get(SpecialName specialName) throws IOException {
|
||||||
|
File certFile = getCertFile(specialName);
|
||||||
|
if (!certFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream fileIn = new FileInputStream(certFile);
|
||||||
|
BufferedInputStream bufferedIn = new BufferedInputStream(fileIn);
|
||||||
|
Certificate certificate = parserBackend.readCertificate(bufferedIn);
|
||||||
|
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate getIfChanged(String fingerprint, String tag) throws IOException, BadNameException {
|
||||||
|
Certificate certificate = get(fingerprint);
|
||||||
|
if (certificate.getTag().equals(tag)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate getIfChanged(SpecialName specialName, String tag) throws IOException {
|
||||||
|
Certificate certificate = get(specialName);
|
||||||
|
if (certificate.getTag().equals(tag)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate insert(InputStream data, MergeCallback merge) throws IOException, BadDataException, InterruptedException {
|
||||||
|
writeLock.lockDirectory();
|
||||||
|
|
||||||
|
Certificate certificate = _insert(data, merge);
|
||||||
|
|
||||||
|
writeLock.releaseDirectory();
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
||||||
|
if (!writeLock.tryLockDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Certificate certificate = _insert(data, merge);
|
||||||
|
|
||||||
|
writeLock.releaseDirectory();
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Certificate _insert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
||||||
|
Certificate newCertificate = parserBackend.readCertificate(data);
|
||||||
|
Certificate existingCertificate;
|
||||||
|
File certFile;
|
||||||
|
try {
|
||||||
|
existingCertificate = get(newCertificate.getFingerprint());
|
||||||
|
certFile = getCertFile(newCertificate.getFingerprint());
|
||||||
|
} catch (BadNameException e) {
|
||||||
|
throw new BadDataException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) {
|
||||||
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCertificate(newCertificate, certFile);
|
||||||
|
|
||||||
|
return newCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream certIn = certificate.getInputStream();
|
||||||
|
FileOutputStream fileOut = new FileOutputStream(certFile);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = certIn.read(buffer)) != -1) {
|
||||||
|
fileOut.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
certIn.close();
|
||||||
|
fileOut.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException, InterruptedException {
|
||||||
|
writeLock.lockDirectory();
|
||||||
|
|
||||||
|
Certificate certificate = _insertSpecial(specialName, data, merge);
|
||||||
|
|
||||||
|
writeLock.releaseDirectory();
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate tryInsertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
|
||||||
|
if (!writeLock.tryLockDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Certificate certificate = _insertSpecial(specialName, data, merge);
|
||||||
|
|
||||||
|
writeLock.releaseDirectory();
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Certificate _insertSpecial(SpecialName specialName, InputStream data, MergeCallback merge) throws IOException, BadNameException, BadDataException {
|
||||||
|
Certificate newCertificate = parserBackend.readCertificate(data);
|
||||||
|
Certificate existingCertificate = get(specialName);
|
||||||
|
File certFile = getCertFile(specialName);
|
||||||
|
|
||||||
|
if (existingCertificate != null && !existingCertificate.getTag().equals(newCertificate.getTag())) {
|
||||||
|
newCertificate = merge.merge(newCertificate, existingCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCertificate(newCertificate, certFile);
|
||||||
|
|
||||||
|
return newCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Certificate> items() {
|
||||||
|
return new Iterator<Certificate>() {
|
||||||
|
|
||||||
|
private final Queue<Lazy<Certificate>> certificateQueue = new SynchronousQueue<>();
|
||||||
|
|
||||||
|
// Constructor... wtf.
|
||||||
|
{
|
||||||
|
for (SpecialName specialName : SpecialName.values()) {
|
||||||
|
File certFile = getCertFile(specialName);
|
||||||
if (certFile.exists()) {
|
if (certFile.exists()) {
|
||||||
return new Item(identifier, "TAG", new FileInputStream(certFile));
|
certificateQueue.add(
|
||||||
|
new Lazy<Certificate>() {
|
||||||
|
@Override
|
||||||
|
Certificate get() {
|
||||||
|
try {
|
||||||
|
return parserBackend.readCertificate(new FileInputStream(certFile));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("File got deleted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] subdirectories = baseDirectory.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return file.isDirectory() && file.getName().matches("^[a-f0-9]{2}$");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (File subdirectory : subdirectories) {
|
||||||
|
File[] files = subdirectory.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return file.isFile() && file.getName().matches("^[a-f0-9]{38}$");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (File certFile : files) {
|
||||||
|
certificateQueue.add(new Lazy<Certificate>() {
|
||||||
|
@Override
|
||||||
|
Certificate get() throws BadDataException {
|
||||||
|
try {
|
||||||
|
Certificate certificate = parserBackend.readCertificate(new FileInputStream(certFile));
|
||||||
|
if (!(subdirectory.getName() + certFile.getName()).equals(certificate.getFingerprint())) {
|
||||||
|
throw new BadDataException();
|
||||||
|
}
|
||||||
|
return certificate;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("File got deleted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getIfChanged(String identifier, String tag) throws IOException, BadNameException {
|
public boolean hasNext() {
|
||||||
return null;
|
return !certificateQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item insert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
public Certificate next() {
|
||||||
writeLock.lock();
|
try {
|
||||||
|
return certificateQueue.poll().get();
|
||||||
Item item = _insert(data, merge);
|
} catch (BadDataException e) {
|
||||||
|
throw new AssertionError("Could not retrieve item: " + e.getMessage());
|
||||||
writeLock.release();
|
}
|
||||||
return item;
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private abstract static class Lazy<E> {
|
||||||
public Item tryInsert(InputStream data, MergeCallback merge) throws IOException, BadDataException {
|
abstract E get() throws 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
|
@Override
|
||||||
public Iterator<String> fingerprints() {
|
public Iterator<String> fingerprints() {
|
||||||
return null;
|
Iterator<Certificate> certificates = items();
|
||||||
|
return new Iterator<String>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return certificates.hasNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class WriteLock {
|
@Override
|
||||||
private final File lockFile;
|
public String next() {
|
||||||
private RandomAccessFile randomAccessFile;
|
return certificates.next().getFingerprint();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d;
|
package pgp.cert_d;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d.exception;
|
package pgp.cert_d.exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d.exception;
|
package pgp.cert_d.exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.cert_d.exception;
|
package pgp.cert_d.exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exceptions defined by the Shared PGP Certificate Directory.
|
||||||
|
*
|
||||||
|
* @see <a href="https://sequoia-pgp.gitlab.io/pgp-cert-d/#name-failure-modes">Failure Modes</a>
|
||||||
|
*/
|
||||||
|
package pgp.cert_d.exception;
|
10
pgp-cert-d-java/src/main/java/pgp/cert_d/package-info.java
Normal file
10
pgp-cert-d-java/src/main/java/pgp/cert_d/package-info.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the Shared PGP Certificate Directory for java.
|
||||||
|
*
|
||||||
|
* @see <a href="https://sequoia-pgp.gitlab.io/pgp-cert-d/">Shared PGP Certificate Directory</a>
|
||||||
|
*/
|
||||||
|
package pgp.cert_d;
|
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.certificate_store;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public abstract class Certificate {
|
||||||
|
/**
|
||||||
|
* Return the fingerprint of the certificate as 40 lowercase hex characters.
|
||||||
|
* TODO: Allow OpenPGP V5 fingerprints
|
||||||
|
*
|
||||||
|
* @return fingerprint
|
||||||
|
*/
|
||||||
|
public abstract String getFingerprint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an {@link InputStream} of the binary representation of the certificate.
|
||||||
|
*
|
||||||
|
* @return input stream
|
||||||
|
*/
|
||||||
|
public abstract InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a tag of the certificate.
|
||||||
|
* The tag is a checksum calculated over the binary representation of the certificate.
|
||||||
|
*
|
||||||
|
* @return tag
|
||||||
|
*/
|
||||||
|
public abstract String getTag() throws IOException;
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package pgp.certificate_store;
|
package pgp.certificate_store;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -6,19 +10,19 @@ import java.util.Iterator;
|
||||||
|
|
||||||
public interface CertificateStore {
|
public interface CertificateStore {
|
||||||
|
|
||||||
Item get(String identifier) throws IOException;
|
Certificate get(String identifier) throws IOException;
|
||||||
|
|
||||||
Item getIfChanged(String identifier, String tag) throws IOException;
|
Certificate getIfChanged(String identifier, String tag) throws IOException;
|
||||||
|
|
||||||
Item insert(InputStream data, MergeCallback merge) throws IOException;
|
Certificate insert(InputStream data, MergeCallback merge) throws IOException;
|
||||||
|
|
||||||
Item tryInsert(InputStream data, MergeCallback merge) throws IOException;
|
Certificate tryInsert(InputStream data, MergeCallback merge) throws IOException;
|
||||||
|
|
||||||
Item insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException;
|
Certificate insertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException;
|
||||||
|
|
||||||
Item tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException;
|
Certificate tryInsertSpecial(String specialName, InputStream data, MergeCallback merge) throws IOException;
|
||||||
|
|
||||||
Iterator<Item> items();
|
Iterator<Certificate> items();
|
||||||
|
|
||||||
Iterator<String> fingerprints();
|
Iterator<String> fingerprints();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
package pgp.certificate_store;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class Item {
|
|
||||||
|
|
||||||
private final String fingerprint;
|
|
||||||
private final String tag;
|
|
||||||
private final InputStream data;
|
|
||||||
|
|
||||||
public Item(String fingerprint, String tag, InputStream data) {
|
|
||||||
this.fingerprint = fingerprint;
|
|
||||||
this.tag = tag;
|
|
||||||
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,7 +1,8 @@
|
||||||
package pgp.certificate_store;
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import java.io.InputStream;
|
package pgp.certificate_store;
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge a given certificate (update) with an existing certificate.
|
* Merge a given certificate (update) with an existing certificate.
|
||||||
|
@ -11,12 +12,12 @@ public interface MergeCallback {
|
||||||
/**
|
/**
|
||||||
* Merge the given certificate data with the existing certificate and return the result.
|
* Merge the given certificate data with the existing certificate and return the result.
|
||||||
*
|
*
|
||||||
* If no existing certificate is found (i.e. existing is null), this method returns the binary representation of data.
|
* If no existing certificate is found (i.e. existing is null), this method returns the unmodified data.
|
||||||
*
|
*
|
||||||
* @param data input stream containing the certificate
|
* @param data certificate
|
||||||
* @param existing optional input stream containing an already existing copy of the certificate
|
* @param existing optional already existing copy of the certificate
|
||||||
* @return output stream containing the binary representation of the merged certificate
|
* @return merged certificate
|
||||||
*/
|
*/
|
||||||
OutputStream merge(InputStream data, InputStream existing);
|
Certificate merge(Certificate data, Certificate existing);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.certificate_store;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface ParserBackend {
|
||||||
|
|
||||||
|
Certificate readCertificate(InputStream inputStream) throws IOException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract definitions of an OpenPGP certificate store.
|
||||||
|
*/
|
||||||
|
package pgp.certificate_store;
|
28
pgpainless-cert-d/build.gradle
Normal file
28
pgpainless-cert-d/build.gradle
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// 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(":pgpainless-core")
|
||||||
|
api project(":pgp-cert-d-java")
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.cert_d;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
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.CertificateParser;
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
|
import pgp.cert_d.FileLockingMechanism;
|
||||||
|
import pgp.cert_d.LockingMechanism;
|
||||||
|
import pgp.cert_d.SharedPGPCertificateDirectory;
|
||||||
|
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
||||||
|
import pgp.cert_d.exception.BadDataException;
|
||||||
|
import pgp.cert_d.exception.BadNameException;
|
||||||
|
import pgp.cert_d.exception.NotAStoreException;
|
||||||
|
import pgp.certificate_store.Certificate;
|
||||||
|
import pgp.certificate_store.MergeCallback;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
public class SharedPGPCertificateDirectoryTest {
|
||||||
|
|
||||||
|
Logger logger = LoggerFactory.getLogger(SharedPGPCertificateDirectoryTest.class);
|
||||||
|
SharedPGPCertificateDirectory directory;
|
||||||
|
|
||||||
|
private static MergeCallback dummyMerge = new MergeCallback() {
|
||||||
|
@Override
|
||||||
|
public Certificate merge(Certificate data, Certificate existing) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() throws IOException, NotAStoreException {
|
||||||
|
File tempDir = Files.createTempDirectory("pgp.cert.d-").toFile();
|
||||||
|
tempDir.deleteOnExit();
|
||||||
|
directory = new SharedPGPCertificateDirectoryImpl(tempDir, new CertificateParser());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleInsertGet() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException, BadNameException {
|
||||||
|
logger.info(() -> "simpleInsertGet: " + ((SharedPGPCertificateDirectoryImpl) directory).getBaseDirectory().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()));
|
||||||
|
|
||||||
|
// 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"));
|
||||||
|
|
||||||
|
// tryInsert
|
||||||
|
certIn = new ByteArrayInputStream(cert.getEncoded());
|
||||||
|
assertNotNull(directory.tryInsert(certIn, dummyMerge));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tryInsertFailsWithLockedStore() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadDataException, InterruptedException {
|
||||||
|
SharedPGPCertificateDirectoryImpl fileDirectory = (SharedPGPCertificateDirectoryImpl) directory;
|
||||||
|
logger.info(() -> "tryInsertFailsWithLockedStore: " + fileDirectory.getBaseDirectory().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");
|
||||||
|
LockingMechanism lock = new FileLockingMechanism(lockFile);
|
||||||
|
lock.lockDirectory();
|
||||||
|
|
||||||
|
assertNull(directory.tryInsert(certIn, dummyMerge));
|
||||||
|
|
||||||
|
lock.releaseDirectory();
|
||||||
|
|
||||||
|
assertNotNull(directory.tryInsert(certIn, dummyMerge));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ dependencies {
|
||||||
|
|
||||||
api "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
|
api "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
|
||||||
api "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion"
|
api "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion"
|
||||||
|
api project(":pgp-certificate-store")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.certificate_store;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
|
import pgp.certificate_store.Certificate;
|
||||||
|
import pgp.certificate_store.ParserBackend;
|
||||||
|
|
||||||
|
public class CertificateParser implements ParserBackend {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate readCertificate(InputStream inputStream) throws IOException {
|
||||||
|
final PGPPublicKeyRing certificate = PGPainless.readKeyRing().publicKeyRing(inputStream);
|
||||||
|
return new Certificate() {
|
||||||
|
@Override
|
||||||
|
public String getFingerprint() {
|
||||||
|
return OpenPgpFingerprint.of(certificate).toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(certificate.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() throws IOException {
|
||||||
|
MessageDigest digest;
|
||||||
|
try {
|
||||||
|
digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError("No MessageDigest for SHA-256 instantiated, although BC is on the classpath: " + e.getMessage());
|
||||||
|
}
|
||||||
|
digest.update(certificate.getEncoded());
|
||||||
|
return Base64.toBase64String(digest.digest());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of the module pgp-certificate-store using pgpainless-core.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.certificate_store;
|
|
@ -7,6 +7,7 @@ rootProject.name = 'PGPainless'
|
||||||
include 'pgpainless-core',
|
include 'pgpainless-core',
|
||||||
'pgpainless-sop',
|
'pgpainless-sop',
|
||||||
'pgpainless-cli',
|
'pgpainless-cli',
|
||||||
|
'pgpainless-cert-d',
|
||||||
'pgp-certificate-store',
|
'pgp-certificate-store',
|
||||||
'pgp-cert-d-java'
|
'pgp-cert-d-java'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue