mirror of
https://codeberg.org/PGPainless/cert-d-pgpainless.git
synced 2024-12-22 05:17:56 +01:00
Implement storing of trust-root key
This commit is contained in:
parent
fca9a8ef91
commit
ee1fd669ed
13 changed files with 357 additions and 104 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
# Shared PGP Certificate Directory for Java
|
# Shared PGP Certificate Directory for Java
|
||||||
|
|
||||||
This repository contains implementations of the [Shared PGP Certificate Directory](https://sequoia-pgp.gitlab.io/pgp-cert-d/) specification using [PGPainless](https://pgpainless.org) as backend.
|
This repository contains implementations of the [Shared PGP Certificate Directory](https://sequoia-pgp.gitlab.io/pgp-cert-d/) specification using [PGPainless](https://pgpainless.org) as backend.
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.cert_d.cli;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.certificate_store.CertificateFactory;
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
|
import pgp.certificate_store.Certificate;
|
||||||
|
import pgp.certificate_store.CertificateMerger;
|
||||||
|
import pgp.certificate_store.Key;
|
||||||
|
import pgp.certificate_store.KeyMerger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class MergeCallbacks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link CertificateMerger} that merges the two copies of the same certificate (same primary key) into one
|
||||||
|
* combined certificate.
|
||||||
|
*
|
||||||
|
* @return merging callback
|
||||||
|
*/
|
||||||
|
public static CertificateMerger mergeCertificates() {
|
||||||
|
return new CertificateMerger() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate merge(Certificate data, Certificate existing) throws IOException {
|
||||||
|
try {
|
||||||
|
PGPPublicKeyRing existingCert = PGPainless.readKeyRing().publicKeyRing(existing.getInputStream());
|
||||||
|
PGPPublicKeyRing updatedCert = PGPainless.readKeyRing().publicKeyRing(data.getInputStream());
|
||||||
|
PGPPublicKeyRing mergedCert = PGPPublicKeyRing.join(existingCert, updatedCert);
|
||||||
|
|
||||||
|
printOutDifferences(existingCert, mergedCert);
|
||||||
|
|
||||||
|
return CertificateFactory.certificateFromPublicKeyRing(mergedCert);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printOutDifferences(PGPPublicKeyRing existingCert, PGPPublicKeyRing mergedCert) {
|
||||||
|
int numSigsBefore = countSigs(existingCert);
|
||||||
|
int numSigsAfter = countSigs(mergedCert);
|
||||||
|
int newSigs = numSigsAfter - numSigsBefore;
|
||||||
|
int numUidsBefore = count(existingCert.getPublicKey().getUserIDs());
|
||||||
|
int numUidsAfter = count(mergedCert.getPublicKey().getUserIDs());
|
||||||
|
int newUids = numUidsAfter - numUidsBefore;
|
||||||
|
|
||||||
|
if (!existingCert.equals(mergedCert)) {
|
||||||
|
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(mergedCert);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(String.format("Certificate %s has", fingerprint));
|
||||||
|
if (newSigs != 0) {
|
||||||
|
sb.append(String.format(" %d new signatures", newSigs));
|
||||||
|
}
|
||||||
|
if (newUids != 0) {
|
||||||
|
if (newSigs != 0) {
|
||||||
|
sb.append(" and");
|
||||||
|
}
|
||||||
|
sb.append(String.format(" %d new UIDs", newUids));
|
||||||
|
}
|
||||||
|
if (newSigs == 0 && newUids == 0) {
|
||||||
|
sb.append(" changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this case it is okay to print to stdout, since we are a CLI app
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(sb);
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countSigs(PGPPublicKeyRing keys) {
|
||||||
|
int numSigs = 0;
|
||||||
|
for (PGPPublicKey key : keys) {
|
||||||
|
numSigs += count(key.getSignatures());
|
||||||
|
}
|
||||||
|
return numSigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use CollectionUtils.count() once available
|
||||||
|
private int count(Iterator<?> iterator) {
|
||||||
|
int num = 0;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next();
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an implementation of {@link CertificateMerger} that ignores the existing certificate and instead
|
||||||
|
* returns the first instance.
|
||||||
|
*
|
||||||
|
* @return overriding callback
|
||||||
|
*/
|
||||||
|
public static CertificateMerger overrideCertificate() {
|
||||||
|
// noinspection Convert2Lambda
|
||||||
|
return new CertificateMerger() {
|
||||||
|
@Override
|
||||||
|
public Certificate merge(Certificate data, Certificate existing) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyMerger overrideKey() {
|
||||||
|
// noinspection Convert2Lambda
|
||||||
|
return new KeyMerger() {
|
||||||
|
@Override
|
||||||
|
public Key merge(Key data, Key existing) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
package pgp.cert_d.cli;
|
package pgp.cert_d.cli;
|
||||||
|
|
||||||
import org.pgpainless.certificate_store.CertificateReader;
|
import org.pgpainless.certificate_store.CertificateReader;
|
||||||
|
import org.pgpainless.certificate_store.KeyReader;
|
||||||
import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter;
|
import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter;
|
||||||
import pgp.cert_d.BaseDirectoryProvider;
|
import pgp.cert_d.BaseDirectoryProvider;
|
||||||
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
||||||
|
@ -12,6 +13,7 @@ import pgp.cert_d.cli.commands.Export;
|
||||||
import pgp.cert_d.cli.commands.Get;
|
import pgp.cert_d.cli.commands.Get;
|
||||||
import pgp.cert_d.cli.commands.Insert;
|
import pgp.cert_d.cli.commands.Insert;
|
||||||
import pgp.cert_d.cli.commands.Import;
|
import pgp.cert_d.cli.commands.Import;
|
||||||
|
import pgp.cert_d.cli.commands.Setup;
|
||||||
import pgp.cert_d.jdbc.sqlite.DatabaseSubkeyLookup;
|
import pgp.cert_d.jdbc.sqlite.DatabaseSubkeyLookup;
|
||||||
import pgp.cert_d.jdbc.sqlite.SqliteSubkeyLookupDaoImpl;
|
import pgp.cert_d.jdbc.sqlite.SqliteSubkeyLookupDaoImpl;
|
||||||
import pgp.certificate_store.SubkeyLookup;
|
import pgp.certificate_store.SubkeyLookup;
|
||||||
|
@ -26,15 +28,19 @@ import java.sql.SQLException;
|
||||||
name = "certificate-store",
|
name = "certificate-store",
|
||||||
description = "Store and manage public OpenPGP certificates",
|
description = "Store and manage public OpenPGP certificates",
|
||||||
subcommands = {
|
subcommands = {
|
||||||
|
CommandLine.HelpCommand.class,
|
||||||
Export.class,
|
Export.class,
|
||||||
Insert.class,
|
Insert.class,
|
||||||
Import.class,
|
Import.class,
|
||||||
Get.class,
|
Get.class,
|
||||||
|
Setup.class
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public class PGPCertDCli {
|
public class PGPCertDCli {
|
||||||
|
|
||||||
@CommandLine.Option(names = "--base-directory", paramLabel = "DIRECTORY", description = "Overwrite the default certificate directory")
|
@CommandLine.Option(names = {"-s", "--store"}, paramLabel = "DIRECTORY",
|
||||||
|
description = "Overwrite the default certificate directory path",
|
||||||
|
scope = CommandLine.ScopeType.INHERIT)
|
||||||
File baseDirectory;
|
File baseDirectory;
|
||||||
|
|
||||||
private static CertificateDirectory certificateDirectory;
|
private static CertificateDirectory certificateDirectory;
|
||||||
|
@ -57,7 +63,8 @@ public class PGPCertDCli {
|
||||||
|
|
||||||
certificateDirectory = new SharedPGPCertificateDirectoryImpl(
|
certificateDirectory = new SharedPGPCertificateDirectoryImpl(
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
new CertificateReader());
|
new CertificateReader(),
|
||||||
|
new KeyReader());
|
||||||
subkeyLookup = new DatabaseSubkeyLookup(
|
subkeyLookup = new DatabaseSubkeyLookup(
|
||||||
SqliteSubkeyLookupDaoImpl.forDatabaseFile(new File(baseDirectory, "_pgpainless_subkey_map.db")));
|
SqliteSubkeyLookupDaoImpl.forDatabaseFile(new File(baseDirectory, "_pgpainless_subkey_map.db")));
|
||||||
|
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package pgp.cert_d.cli.commands;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.pgpainless.PGPainless;
|
|
||||||
import org.pgpainless.certificate_store.CertificateFactory;
|
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
|
||||||
import pgp.certificate_store.Certificate;
|
|
||||||
import pgp.certificate_store.MergeCallback;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
public class DefaultMergeCallback implements MergeCallback {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Certificate merge(Certificate data, Certificate existing) throws IOException {
|
|
||||||
try {
|
|
||||||
PGPPublicKeyRing existingCert = PGPainless.readKeyRing().publicKeyRing(existing.getInputStream());
|
|
||||||
PGPPublicKeyRing updatedCert = PGPainless.readKeyRing().publicKeyRing(data.getInputStream());
|
|
||||||
PGPPublicKeyRing mergedCert = PGPPublicKeyRing.join(existingCert, updatedCert);
|
|
||||||
|
|
||||||
printOutDifferences(existingCert, mergedCert);
|
|
||||||
|
|
||||||
return CertificateFactory.certificateFromPublicKeyRing(mergedCert);
|
|
||||||
} catch (PGPException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printOutDifferences(PGPPublicKeyRing existingCert, PGPPublicKeyRing mergedCert) {
|
|
||||||
int numSigsBefore = countSigs(existingCert);
|
|
||||||
int numSigsAfter = countSigs(mergedCert);
|
|
||||||
int newSigs = numSigsAfter - numSigsBefore;
|
|
||||||
int numUidsBefore = count(existingCert.getPublicKey().getUserIDs());
|
|
||||||
int numUidsAfter = count(mergedCert.getPublicKey().getUserIDs());
|
|
||||||
int newUids = numUidsAfter - numUidsBefore;
|
|
||||||
|
|
||||||
if (!existingCert.equals(mergedCert)) {
|
|
||||||
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(mergedCert);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(String.format("Certificate %s has", fingerprint));
|
|
||||||
if (newSigs != 0) {
|
|
||||||
sb.append(String.format(" %d new signatures", newSigs));
|
|
||||||
}
|
|
||||||
if (newUids != 0) {
|
|
||||||
if (newSigs != 0) {
|
|
||||||
sb.append(" and");
|
|
||||||
}
|
|
||||||
sb.append(String.format(" %d new UIDs", newUids));
|
|
||||||
}
|
|
||||||
if (newSigs == 0 && newUids == 0) {
|
|
||||||
sb.append(" changed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// In this case it is okay to print to stdout, since we are a CLI app
|
|
||||||
// CHECKSTYLE:OFF
|
|
||||||
System.out.println(sb);
|
|
||||||
// CHECKSTYLE:ON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int countSigs(PGPPublicKeyRing keys) {
|
|
||||||
int numSigs = 0;
|
|
||||||
for (PGPPublicKey key : keys) {
|
|
||||||
numSigs += count(key.getSignatures());
|
|
||||||
}
|
|
||||||
return numSigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use CollectionUtils.count() once available
|
|
||||||
private static int count(Iterator<?> iterator) {
|
|
||||||
int num = 0;
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
iterator.next();
|
|
||||||
num++;
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,9 +10,9 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import pgp.cert_d.cli.MergeCallbacks;
|
||||||
import pgp.cert_d.cli.PGPCertDCli;
|
import pgp.cert_d.cli.PGPCertDCli;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.MergeCallback;
|
|
||||||
import pgp.certificate_store.exception.BadDataException;
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import java.io.IOException;
|
||||||
public class Import implements Runnable {
|
public class Import implements Runnable {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Import.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(Import.class);
|
||||||
private final MergeCallback mergeCallback = new DefaultMergeCallback();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -33,7 +32,7 @@ public class Import implements Runnable {
|
||||||
for (PGPPublicKeyRing cert : certificates) {
|
for (PGPPublicKeyRing cert : certificates) {
|
||||||
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
|
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
|
||||||
Certificate certificate = PGPCertDCli.getCertificateDirectory()
|
Certificate certificate = PGPCertDCli.getCertificateDirectory()
|
||||||
.insertCertificate(certIn, mergeCallback);
|
.insertCertificate(certIn, MergeCallbacks.mergeCertificates());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("IO-Error.", e);
|
LOGGER.error("IO-Error.", e);
|
||||||
|
|
|
@ -6,9 +6,9 @@ package pgp.cert_d.cli.commands;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import pgp.cert_d.cli.MergeCallbacks;
|
||||||
import pgp.cert_d.cli.PGPCertDCli;
|
import pgp.cert_d.cli.PGPCertDCli;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.MergeCallback;
|
|
||||||
import pgp.certificate_store.exception.BadDataException;
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ import java.io.IOException;
|
||||||
public class Insert implements Runnable {
|
public class Insert implements Runnable {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Insert.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(Insert.class);
|
||||||
private final MergeCallback mergeCallback = new DefaultMergeCallback();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Certificate certificate = PGPCertDCli.getCertificateDirectory().insertCertificate(System.in, mergeCallback);
|
Certificate certificate = PGPCertDCli.getCertificateDirectory()
|
||||||
|
.insertCertificate(System.in, MergeCallbacks.mergeCertificates());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("IO-Error.", e);
|
LOGGER.error("IO-Error.", e);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package pgp.cert_d.cli.commands;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.key.generation.KeyRingBuilder;
|
||||||
|
import org.pgpainless.key.generation.KeySpec;
|
||||||
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import pgp.cert_d.cli.MergeCallbacks;
|
||||||
|
import pgp.cert_d.cli.PGPCertDCli;
|
||||||
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
@CommandLine.Command(name = "setup",
|
||||||
|
description = "Setup a new certificate directory")
|
||||||
|
public class Setup implements Runnable {
|
||||||
|
|
||||||
|
public static final Logger LOGGER = LoggerFactory.getLogger(Setup.class);
|
||||||
|
|
||||||
|
@CommandLine.ArgGroup()
|
||||||
|
Exclusive exclusive;
|
||||||
|
|
||||||
|
static class Exclusive {
|
||||||
|
@CommandLine.Option(names = "--with-password",
|
||||||
|
paramLabel = "PASSWORD",
|
||||||
|
description = "Ask for a password for the trust-root key")
|
||||||
|
String password;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = "--import-from-stdin",
|
||||||
|
description = "Import trust-root from stdin")
|
||||||
|
boolean importFromStdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PGPSecretKeyRing trustRoot;
|
||||||
|
if (exclusive == null) {
|
||||||
|
trustRoot = generateTrustRoot(Passphrase.emptyPassphrase());
|
||||||
|
} else {
|
||||||
|
if (exclusive.importFromStdin) {
|
||||||
|
trustRoot = readTrustRoot(System.in);
|
||||||
|
} else {
|
||||||
|
trustRoot = generateTrustRoot(Passphrase.fromPassword(exclusive.password.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(trustRoot.getEncoded());
|
||||||
|
PGPCertDCli.getCertificateDirectory().insertTrustRoot(inputStream, MergeCallbacks.overrideKey());
|
||||||
|
|
||||||
|
} catch (BadDataException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("IO error.", e);
|
||||||
|
System.exit(-1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.error("Thread interrupted.", e);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGPSecretKeyRing generateTrustRoot(Passphrase passphrase) {
|
||||||
|
PGPSecretKeyRing trustRoot;
|
||||||
|
KeyRingBuilder builder = PGPainless.buildKeyRing()
|
||||||
|
.addUserId("trust-root");
|
||||||
|
if (passphrase != null) {
|
||||||
|
builder.setPassphrase(passphrase);
|
||||||
|
}
|
||||||
|
builder.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER));
|
||||||
|
try {
|
||||||
|
trustRoot = builder.build();
|
||||||
|
} catch (NoSuchAlgorithmException | PGPException | InvalidAlgorithmParameterException e) {
|
||||||
|
throw new RuntimeException("Cannot generate trust-root OpenPGP key", e);
|
||||||
|
}
|
||||||
|
return trustRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGPSecretKeyRing readTrustRoot(InputStream inputStream) {
|
||||||
|
try {
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(inputStream);
|
||||||
|
if (secretKeys == null) {
|
||||||
|
throw new BadDataException();
|
||||||
|
}
|
||||||
|
return secretKeys;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read trust-root OpenPGP key", e);
|
||||||
|
} catch (BadDataException e) {
|
||||||
|
throw new RuntimeException("trust-root does not contain OpenPGP key", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.certificate_store;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import pgp.certificate_store.Certificate;
|
||||||
|
import pgp.certificate_store.Key;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class KeyFactory {
|
||||||
|
|
||||||
|
public static Key keyFromSecretKeyRing(PGPSecretKeyRing secretKeyRing) {
|
||||||
|
|
||||||
|
return new Key() {
|
||||||
|
@Override
|
||||||
|
public Certificate getCertificate() {
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.extractCertificate(secretKeyRing);
|
||||||
|
return CertificateFactory.certificateFromPublicKeyRing(publicKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(secretKeyRing.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(secretKeyRing.getEncoded());
|
||||||
|
return Base64.toBase64String(digest.digest());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.certificate_store;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import pgp.certificate_store.Key;
|
||||||
|
import pgp.certificate_store.KeyReaderBackend;
|
||||||
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class KeyReader implements KeyReaderBackend {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key readKey(InputStream data) throws IOException, BadDataException {
|
||||||
|
final PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(data);
|
||||||
|
return KeyFactory.keyFromSecretKeyRing(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,10 @@ import pgp.cert_d.SharedPGPCertificateDirectory;
|
||||||
import pgp.cert_d.SpecialNames;
|
import pgp.cert_d.SpecialNames;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.CertificateDirectory;
|
import pgp.certificate_store.CertificateDirectory;
|
||||||
|
import pgp.certificate_store.CertificateMerger;
|
||||||
import pgp.certificate_store.CertificateStore;
|
import pgp.certificate_store.CertificateStore;
|
||||||
import pgp.certificate_store.MergeCallback;
|
import pgp.certificate_store.Key;
|
||||||
|
import pgp.certificate_store.KeyMerger;
|
||||||
import pgp.certificate_store.SubkeyLookup;
|
import pgp.certificate_store.SubkeyLookup;
|
||||||
import pgp.certificate_store.exception.BadDataException;
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
import pgp.certificate_store.exception.BadNameException;
|
import pgp.certificate_store.exception.BadNameException;
|
||||||
|
@ -66,7 +68,7 @@ public class SharedPGPCertificateDirectoryAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate insertCertificate(InputStream data, MergeCallback merge)
|
public Certificate insertCertificate(InputStream data, CertificateMerger merge)
|
||||||
throws IOException, InterruptedException, BadDataException {
|
throws IOException, InterruptedException, BadDataException {
|
||||||
Certificate certificate = directory.insert(data, merge);
|
Certificate certificate = directory.insert(data, merge);
|
||||||
storeIdentifierForSubkeys(certificate);
|
storeIdentifierForSubkeys(certificate);
|
||||||
|
@ -74,7 +76,7 @@ public class SharedPGPCertificateDirectoryAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate tryInsertCertificate(InputStream data, MergeCallback merge)
|
public Certificate tryInsertCertificate(InputStream data, CertificateMerger merge)
|
||||||
throws IOException, BadDataException {
|
throws IOException, BadDataException {
|
||||||
Certificate certificate = directory.tryInsert(data, merge);
|
Certificate certificate = directory.tryInsert(data, merge);
|
||||||
storeIdentifierForSubkeys(certificate);
|
storeIdentifierForSubkeys(certificate);
|
||||||
|
@ -82,13 +84,13 @@ public class SharedPGPCertificateDirectoryAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
|
public Certificate insertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge)
|
||||||
throws IOException, InterruptedException, BadDataException, BadNameException {
|
throws IOException, InterruptedException, BadDataException, BadNameException {
|
||||||
return directory.insertWithSpecialName(specialName, data, merge);
|
return directory.insertWithSpecialName(specialName, data, merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
|
public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, CertificateMerger merge)
|
||||||
throws IOException, BadDataException, BadNameException {
|
throws IOException, BadDataException, BadNameException {
|
||||||
return directory.tryInsertWithSpecialName(specialName, data, merge);
|
return directory.tryInsertWithSpecialName(specialName, data, merge);
|
||||||
}
|
}
|
||||||
|
@ -120,4 +122,24 @@ public class SharedPGPCertificateDirectoryAdapter
|
||||||
public void storeCertificateSubkeyIds(String certificate, List<Long> subkeyIds) throws IOException {
|
public void storeCertificateSubkeyIds(String certificate, List<Long> subkeyIds) throws IOException {
|
||||||
subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds);
|
subkeyLookup.storeCertificateSubkeyIds(certificate, subkeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getTrustRoot() throws IOException, BadDataException {
|
||||||
|
return directory.getTrustRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getTrustRootIfChanged(String tag) throws IOException, BadDataException {
|
||||||
|
return directory.getTrustRootIfChanged(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key insertTrustRoot(InputStream data, KeyMerger keyMerger) throws IOException, InterruptedException, BadDataException {
|
||||||
|
return directory.insertTrustRoot(data, keyMerger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key tryInsertTrustRoot(InputStream data, KeyMerger keyMerger) throws IOException, BadDataException {
|
||||||
|
return directory.tryInsertTrustRoot(data, keyMerger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.certificate_store.CertificateReader;
|
import org.pgpainless.certificate_store.CertificateReader;
|
||||||
|
import org.pgpainless.certificate_store.KeyReader;
|
||||||
import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter;
|
import org.pgpainless.certificate_store.SharedPGPCertificateDirectoryAdapter;
|
||||||
import pgp.cert_d.InMemorySubkeyLookup;
|
import pgp.cert_d.InMemorySubkeyLookup;
|
||||||
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
||||||
|
@ -50,7 +51,7 @@ public class SharedPGPCertificateDirectoryAdapterTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setupInstance() throws IOException, NotAStoreException {
|
public void setupInstance() throws IOException, NotAStoreException {
|
||||||
adapter = new SharedPGPCertificateDirectoryAdapter(
|
adapter = new SharedPGPCertificateDirectoryAdapter(
|
||||||
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()),
|
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader(), new KeyReader()),
|
||||||
new InMemorySubkeyLookup());
|
new InMemorySubkeyLookup());
|
||||||
store = adapter;
|
store = adapter;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.certificate_store.CertificateReader;
|
import org.pgpainless.certificate_store.CertificateReader;
|
||||||
|
import org.pgpainless.certificate_store.KeyReader;
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
import org.pgpainless.key.generation.KeySpec;
|
import org.pgpainless.key.generation.KeySpec;
|
||||||
import org.pgpainless.key.generation.type.KeyType;
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
@ -41,15 +42,15 @@ import pgp.cert_d.CachingSharedPGPCertificateDirectoryWrapper;
|
||||||
import pgp.cert_d.FileLockingMechanism;
|
import pgp.cert_d.FileLockingMechanism;
|
||||||
import pgp.cert_d.SharedPGPCertificateDirectory;
|
import pgp.cert_d.SharedPGPCertificateDirectory;
|
||||||
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
import pgp.cert_d.SharedPGPCertificateDirectoryImpl;
|
||||||
|
import pgp.certificate_store.CertificateMerger;
|
||||||
import pgp.certificate_store.exception.BadDataException;
|
import pgp.certificate_store.exception.BadDataException;
|
||||||
import pgp.certificate_store.exception.BadNameException;
|
import pgp.certificate_store.exception.BadNameException;
|
||||||
import pgp.certificate_store.exception.NotAStoreException;
|
import pgp.certificate_store.exception.NotAStoreException;
|
||||||
import pgp.certificate_store.Certificate;
|
import pgp.certificate_store.Certificate;
|
||||||
import pgp.certificate_store.MergeCallback;
|
|
||||||
|
|
||||||
public class SharedPGPCertificateDirectoryTest {
|
public class SharedPGPCertificateDirectoryTest {
|
||||||
|
|
||||||
private static MergeCallback dummyMerge = new MergeCallback() {
|
private static CertificateMerger dummyMerge = new CertificateMerger() {
|
||||||
@Override
|
@Override
|
||||||
public Certificate merge(Certificate data, Certificate existing) {
|
public Certificate merge(Certificate data, Certificate existing) {
|
||||||
return data;
|
return data;
|
||||||
|
@ -58,9 +59,9 @@ public class SharedPGPCertificateDirectoryTest {
|
||||||
|
|
||||||
private static Stream<SharedPGPCertificateDirectory> provideTestSubjects() throws IOException, NotAStoreException {
|
private static Stream<SharedPGPCertificateDirectory> provideTestSubjects() throws IOException, NotAStoreException {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()),
|
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader(), new KeyReader()),
|
||||||
new CachingSharedPGPCertificateDirectoryWrapper(
|
new CachingSharedPGPCertificateDirectoryWrapper(
|
||||||
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader()))
|
new SharedPGPCertificateDirectoryImpl(tempDir(), new CertificateReader(), new KeyReader()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ allprojects {
|
||||||
junitVersion = '5.8.2'
|
junitVersion = '5.8.2'
|
||||||
mockitoVersion = '4.5.1'
|
mockitoVersion = '4.5.1'
|
||||||
pgpainlessVersion = '1.2.1'
|
pgpainlessVersion = '1.2.1'
|
||||||
pgpCertDJavaVersion = '0.1.1'
|
pgpCertDJavaVersion = '0.1.2-SNAPSHOT'
|
||||||
picocliVersion = '4.6.3'
|
picocliVersion = '4.6.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue