1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-12-25 04:17:59 +01:00

Checkout wot-dijkstra and pgpainless-wot from wot branch

This commit is contained in:
Paul Schaub 2023-06-17 15:32:57 +02:00
parent f46790be00
commit 90e87ccab4
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
51 changed files with 2580 additions and 0 deletions

View file

@ -0,0 +1,30 @@
// 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"
implementation(project(":pgpainless-core"))
// Certificate store
api "org.pgpainless:pgp-certificate-store:$certDJavaVersion"
}
test {
useJUnitPlatform()
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.wot;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
public interface CertificateAuthority {
/**
* Returns <pre>true</pre>, if the given binding (certificate and user-id) is correct.
* Correct means, that the binding is trustworthy.
*
* @param certificate OpenPGP certificate
* @param userId user-id
* @return binding correctness
*/
boolean isAuthorized(PGPPublicKeyRing certificate, String userId);
}

View file

@ -0,0 +1,29 @@
package org.pgpainless.wot;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import pgp.certificate_store.Certificate;
import pgp.certificate_store.exception.BadDataException;
import java.io.IOException;
public class WebOfTrust implements CertificateAuthority {
private final WebOfTrustCertificateStore certificateStore;
public WebOfTrust(WebOfTrustCertificateStore certificateStore) {
this.certificateStore = certificateStore;
}
/**
* Do the heavy lifting of calculating the web of trust.
*/
public void initialize() throws BadDataException, IOException {
Certificate trustRoot = certificateStore.getTrustRoot();
}
@Override
public boolean isAuthorized(PGPPublicKeyRing certificate, String userId) {
return false;
}
}

View file

@ -0,0 +1,74 @@
package org.pgpainless.wot;
import pgp.certificate_store.Certificate;
import pgp.certificate_store.CertificateDirectory;
import pgp.certificate_store.MergeCallback;
import pgp.certificate_store.exception.BadDataException;
import pgp.certificate_store.exception.BadNameException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
public class WebOfTrustCertificateStore implements CertificateDirectory {
private final CertificateDirectory certificateDirectory;
public WebOfTrustCertificateStore(CertificateDirectory certificateDirectory) {
this.certificateDirectory = certificateDirectory;
}
public Certificate getTrustRoot() throws BadDataException, IOException {
try {
return getCertificate("trust-root");
} catch (BadNameException e) {
throw new AssertionError("The underlying certificate directory MUST support getting a trust-root certificate.");
}
}
@Override
public Certificate getCertificate(String identifier)
throws IOException, BadNameException, BadDataException {
return certificateDirectory.getCertificate(identifier);
}
@Override
public Certificate getCertificateIfChanged(String identifier, String tag)
throws IOException, BadNameException, BadDataException {
return certificateDirectory.getCertificateIfChanged(identifier, tag);
}
@Override
public Certificate insertCertificate(InputStream data, MergeCallback merge)
throws IOException, InterruptedException, BadDataException {
return certificateDirectory.insertCertificate(data, merge);
}
@Override
public Certificate tryInsertCertificate(InputStream data, MergeCallback merge)
throws IOException, BadDataException {
return certificateDirectory.tryInsertCertificate(data, merge);
}
@Override
public Certificate insertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
throws IOException, InterruptedException, BadDataException, BadNameException {
return certificateDirectory.insertCertificateBySpecialName(specialName, data, merge);
}
@Override
public Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, MergeCallback merge)
throws IOException, BadDataException, BadNameException {
return certificateDirectory.tryInsertCertificateBySpecialName(specialName, data, merge);
}
@Override
public Iterator<Certificate> getCertificates() {
return certificateDirectory.getCertificates();
}
@Override
public Iterator<String> getFingerprints() {
return certificateDirectory.getFingerprints();
}
}

19
wot-dijkstra/QUESTIONS.md Normal file
View file

@ -0,0 +1,19 @@
# Questions
## Graph of Certificates or Graph of Subkeys?
## Persistable Graph or Dynamic Recalculation?
https://de.wikipedia.org/wiki/Dijkstra-Algorithmus
Dijkstra: Outgoing edges "live" in origin, WoT: Incoming edges "live" in target
When processing incoming edges on certificate (node): origin of incoming nodes (signing key) might not be known -> cannot verify.
multi-step process:
Firstly create intermediate graph with unverified edges, invert edge such that origin owns edges to targets
Secondly for edges where origin and target exist, verify signatures
What information from signatures to cache? Creation date, expiration? Regex, depth, amount!
What are then nodes? Certs? Bindings?

30
wot-dijkstra/build.gradle Normal file
View file

@ -0,0 +1,30 @@
// 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"
// @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation(project(":pgpainless-core"))
}
test {
useJUnitPlatform()
}

View file

@ -0,0 +1,72 @@
package org.pgpainless.wot.dijkstra;
public class Cost {
public static class SimpleCost extends Cost {
private final double weight;
public SimpleCost(double weight) {
this.weight = weight;
}
public double getWeight() {
return weight;
}
@Override
public String toString() {
return Double.toString(getWeight());
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof SimpleCost)) {
return false;
}
SimpleCost other = (SimpleCost) obj;
return getWeight() == other.getWeight();
}
@Override
public int hashCode() {
return (int) getWeight();
}
}
public static class TrustCost extends Cost {
private final int depth;
private final int amount;
private final String regex;
public TrustCost(int depth, int amount, String regex) {
this.depth = depth;
this.amount = amount;
this.regex = regex;
}
public int getDepth() {
return depth;
}
public int getAmount() {
return amount;
}
public String getRegex() {
return regex;
}
@Override
public String toString() {
return "d=" + getDepth() + ",a=" + getAmount() + (regex == null ? "" : ",r=" + getRegex());
}
}
}

View file

@ -0,0 +1,8 @@
package org.pgpainless.wot.dijkstra;
import javax.annotation.Nullable;
public abstract class Dijkstra<T, E extends Edge<T, C>, C extends Cost> {
@Nullable
public abstract Path<T, Node<T>, C, E> findPath(Node<T> to);
}

View file

@ -0,0 +1,26 @@
package org.pgpainless.wot.dijkstra;
public abstract class Edge<T, C extends Cost> implements Comparable<C> {
protected final Node<T> from;
protected final Node<T> to;
protected final C cost;
public Edge(Node<T> from, Node<T> to, C cost) {
this.from = from;
this.to = to;
this.cost = cost;
}
public Node<T> getFrom() {
return from;
}
public Node<T> getTo() {
return to;
}
public C getCost() {
return cost;
}
}

View file

@ -0,0 +1,22 @@
package org.pgpainless.wot.dijkstra;
import java.util.Collection;
public class Graph<T, N extends Node<T>, E extends Edge<T, C>, C extends Cost> {
private final Collection<N> nodes;
private final Collection<E> edges;
public Graph(Collection<N> nodes, Collection<E> edges) {
this.nodes = nodes;
this.edges = edges;
}
public Collection<N> getNodes() {
return nodes;
}
public Collection<E> getEdges() {
return edges;
}
}

View file

@ -0,0 +1,40 @@
package org.pgpainless.wot.dijkstra;
public class Node<T> {
private final T item;
public Node(T item) {
this.item = item;
}
private T getItem() {
return item;
}
@Override
public String toString() {
return "(" + getItem().toString() + ")";
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof Node)) {
return false;
}
Node<?> other = (Node<?>) obj;
return getItem().equals(other.getItem());
}
@Override
public int hashCode() {
return getItem().hashCode();
}
}

View file

@ -0,0 +1,59 @@
package org.pgpainless.wot.dijkstra;
import java.util.Arrays;
import java.util.List;
public class Path<T, N extends Node<T>, C extends Cost, E extends Edge<T, C>> {
private final N from;
private final N to;
private final List<E> edges;
public Path(N from, N to, List<E> edges) {
this.from = from;
this.to = to;
this.edges = edges;
}
public Node<T> getFrom() {
return from;
}
public Node<T> getTo() {
return to;
}
public List<E> getEdges() {
return edges;
}
@Override
public String toString() {
return Arrays.toString(getEdges().toArray());
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof Path)) {
return false;
}
Path<?, ?, ?, ?> other = (Path<?, ?, ?, ?>) obj;
return getFrom().equals(other.getFrom())
&& getTo().equals(other.getTo())
&& getEdges().equals(other.getEdges());
}
@Override
public int hashCode() {
return getFrom().hashCode()
+ 13 * getTo().hashCode()
+ 31 * getEdges().hashCode();
}
}

View file

@ -0,0 +1,110 @@
package org.pgpainless.wot.dijkstra;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ShortestPathDijkstra<T> extends Dijkstra<T, SimpleEdge<T>, Cost.SimpleCost> {
private final Node<T> root;
private final Graph<T, Node<T>, SimpleEdge<T>, Cost.SimpleCost> graph;
private final List<Node<T>> queue = new ArrayList<>();
private final Map<Node<T>, Double> distances = new HashMap<>();
private final Map<Node<T>, SimpleEdge<T>> precursors = new HashMap<>();
public ShortestPathDijkstra(Graph<T, Node<T>, SimpleEdge<T>, Cost.SimpleCost> graph, Node<T> root) {
// INITIALIZE
this.graph = graph;
this.root = root;
for (Node<T> node : graph.getNodes()) {
// dist[v] := infinity
distances.put(node, Double.MAX_VALUE);
// precursor[v] := null
precursors.put(node, null);
}
// dist[root] := 0
distances.put(root, 0d);
// Q := set of all nodes in graph
queue.addAll(graph.getNodes());
while (!queue.isEmpty()) {
Node<T> closest = closest();
queue.remove(closest);
for (SimpleEdge<T> edge : graph.getEdges()) {
if (!closest.equals(edge.getFrom())) {
// Skip non-neighbors
continue;
}
if (queue.contains(edge.getTo())) {
distUpdate(closest, edge.getTo());
}
}
}
}
private Node<T> closest() {
Double minDist = Double.MAX_VALUE;
int index = 0;
Double dist;
for (int i = 0; i < queue.size(); i++) {
if ((dist = distances.get(queue.get(i))) <= minDist) {
index = i;
minDist = dist;
}
}
return queue.get(index);
}
private void distUpdate(Node<T> from, Node<T> to) {
SimpleEdge<T> edge = getEdgeBetween(from, to);
if (edge == null) {
// No direct path
return;
}
Double distance = distances.get(from) + edge.getCost().getWeight();
if (distance < distances.get(to)) {
distances.put(to, distance);
precursors.put(to, edge);
}
}
private SimpleEdge<T> getEdgeBetween(Node<T> from, Node<T> to) {
for (SimpleEdge<T> edge : graph.getEdges()) {
if (!from.equals(edge.getFrom())) {
continue;
}
if (to.equals(edge.getTo())) {
return edge;
}
}
return null;
}
@Override
@Nullable
public Path<T, Node<T>, Cost.SimpleCost, SimpleEdge<T>> findPath(Node<T> to) {
List<SimpleEdge<T>> pathEdges = new ArrayList<>();
Node<T> waypoint = to;
SimpleEdge<T> edge;
while ((edge = precursors.get(waypoint)) != null) {
waypoint = precursors.get(waypoint).getFrom();
pathEdges.add(0, edge);
}
if (pathEdges.isEmpty()) {
return null;
}
return new Path<>(root, to, pathEdges);
}
}

View file

@ -0,0 +1,41 @@
package org.pgpainless.wot.dijkstra;
public class SimpleEdge<T> extends Edge<T, Cost.SimpleCost> {
public SimpleEdge(Node<T> from, Node<T> to, Double edgeWeight) {
super(from, to, new Cost.SimpleCost(edgeWeight));
}
@Override
public String toString() {
return getFrom().toString() + " " + getCost() + "> " + getTo().toString();
}
@Override
public int compareTo(Cost.SimpleCost o) {
return Double.compare(getCost().getWeight(), o.getWeight());
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof SimpleEdge)) {
return false;
}
SimpleEdge<?> other = (SimpleEdge<?>) obj;
return getFrom().equals(other.getFrom())
&& getTo().equals(other.getTo())
&& getCost().equals(other.getCost());
}
@Override
public int hashCode() {
return getFrom().hashCode() + 13 * getTo().hashCode() + 17 * getCost().hashCode();
}
}

View file

@ -0,0 +1,19 @@
package org.pgpainless.wot.dijkstra;
import javax.annotation.Nonnull;
public class TrustEdge<T> extends Edge<T, Cost.TrustCost> {
public TrustEdge(Node<T> from, Node<T> to, Cost.TrustCost cost) {
super(from, to, cost);
}
@Override
public int compareTo(@Nonnull Cost.TrustCost o) {
int depthCompare = Double.compare(cost.getDepth(), o.getDepth());
if (depthCompare != 0) {
return - depthCompare;
}
return Double.compare(cost.getAmount(), o.getAmount());
}
}

View file

@ -0,0 +1,13 @@
package org.pgpainless.wot.dijkstra;
import javax.annotation.Nullable;
public class WotDijkstra<T> extends Dijkstra<T, TrustEdge<T>, Cost.TrustCost> {
@Override
@Nullable
public Path<T, Node<T>, Cost.TrustCost, TrustEdge<T>> findPath(Node<T> to) {
return null;
}
}

View file

@ -0,0 +1,70 @@
package org.pgpainless.wot.dijkstra.sq;
import org.pgpainless.algorithm.RevocationState;
import org.pgpainless.key.OpenPgpFingerprint;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class CertSynopsis {
private final OpenPgpFingerprint fingerprint;
private final Date expirationTime;
private final RevocationState revocationState;
private final Set<String> userIds;
/**
* Create a new {@link CertSynopsis}.
*
* @param fingerprint fingerprint of the certificate
* @param expirationTime expiration time
* @param revocationState revocation state of the certificate
* @param userIds set of user-ids
*/
public CertSynopsis(OpenPgpFingerprint fingerprint,
Date expirationTime,
RevocationState revocationState,
Set<String> userIds) {
this.fingerprint = fingerprint;
this.expirationTime = expirationTime;
this.revocationState = revocationState;
this.userIds = userIds;
}
/**
* Return the fingerprint of the certificate.
*
* @return fingerprint
*/
public OpenPgpFingerprint getFingerprint() {
return fingerprint;
}
/**
* Get the certificates expiration time.
*
* @return expiration time
*/
public Date getExpirationTime() {
return expirationTime;
}
/**
* Get the revocation status of the certificate.
*
* @return revocation state
*/
public RevocationState getRevocationState() {
return revocationState;
}
/**
* Get a {@link Set} containing all user-ids of the certificate.
*
* @return user-ids
*/
public Set<String> userIds() {
return new HashSet<>(userIds);
}
}

View file

@ -0,0 +1,165 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.Date;
import java.util.List;
import org.bouncycastle.bcpg.sig.Exportable;
import org.bouncycastle.bcpg.sig.RegularExpression;
import org.bouncycastle.bcpg.sig.TrustSignature;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
public class Certification {
private final CertSynopsis issuer;
private final CertSynopsis target;
private final Optional<String> userId;
private final Date creationTime;
private final Optional<Date> expirationTime;
private final boolean exportable;
private final int trustAmount;
private final Depth trustDepth;
private final RegexSet regex;
public Certification(
CertSynopsis issuer,
CertSynopsis target,
Optional<String> userId,
Date creationTime,
Optional<Date> expirationTime,
boolean exportable,
int trustAmount,
Depth trustDepth,
RegexSet regex) {
this.issuer = issuer;
this.target = target;
this.userId = userId;
this.creationTime = creationTime;
this.expirationTime = expirationTime;
this.exportable = exportable;
this.trustAmount= trustAmount;
this.trustDepth = trustDepth;
this.regex = regex;
}
public Certification(CertSynopsis issuer,
Optional<String> targetUserId,
CertSynopsis target,
Date creationTime) {
this.issuer = issuer;
this.target = target;
this.userId = targetUserId;
this.creationTime = creationTime;
this.expirationTime = Optional.empty();
this.exportable = true;
this.trustDepth = Depth.limited(0);
this.trustAmount = 120;
this.regex = RegexSet.wildcard();
}
public Certification(CertSynopsis issuer,
Optional<String> targetUserId,
CertSynopsis target,
PGPSignature signature) {
this.issuer = issuer;
this.target = target;
this.userId = targetUserId;
this.creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime();
this.expirationTime = Optional.maybe(SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature));
Exportable exportablePacket = SignatureSubpacketsUtil.getExportableCertification(signature);
this.exportable = exportablePacket == null || exportablePacket.isExportable();
TrustSignature trustSignaturePacket = SignatureSubpacketsUtil.getTrustSignature(signature);
if (trustSignaturePacket == null) {
this.trustDepth = Depth.limited(0);
this.trustAmount = 120;
} else {
this.trustDepth = Depth.auto(trustSignaturePacket.getDepth());
this.trustAmount = trustSignaturePacket.getTrustAmount();
}
List<RegularExpression> regularExpressionList = SignatureSubpacketsUtil.getRegularExpressions(signature);
this.regex = RegexSet.fromList(regularExpressionList);
}
/**
* Get the issuer of the certification.
*
* @return issuer
*/
public CertSynopsis getIssuer() {
return issuer;
}
/**
* Get the target of the certification.
*
* @return target
*/
public CertSynopsis getTarget() {
return target;
}
/**
* Get the target user-id.
*
* @return user-id
*/
public Optional<String> getUserId() {
return userId;
}
/**
* Get the creation time of the certification,
*
* @return creation time
*/
public Date getCreationTime() {
return creationTime;
}
/**
* Get the (optional) expiration time of the certification.
*
* @return optional expiration time
*/
public Optional<Date> getExpirationTime() {
return expirationTime;
}
/**
* Return true if the certification is marked as exportable.
*
* @return exportable
*/
public boolean isExportable() {
return exportable;
}
/**
* Get the trust amount of the certification.
*
* @return trust amount
*/
public int getTrustAmount() {
return trustAmount;
}
/**
* Get the trust depth of the certification.
*
* @return trust depth
*/
public Depth getTrustDepth() {
return trustDepth;
}
/**
* Return the set of regular expressions.
*
* @return regex set
*/
public RegexSet getRegexes() {
return regex;
}
}

View file

@ -0,0 +1,109 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bouncycastle.openpgp.PGPSignature;
import javax.annotation.Nonnull;
public class CertificationSet {
private final CertSynopsis issuer;
private final CertSynopsis target;
private final Map<Optional<String>, List<Certification>> certifications;
/**
* Create an empty {@link CertificationSet}.
*
* @param issuer issuer
* @param target target
* @return empty set
*/
public static CertificationSet empty(CertSynopsis issuer, CertSynopsis target) {
return new CertificationSet(issuer, target, new HashMap<>());
}
/**
* Create a {@link CertificationSet} from a single certification.
*
* @param issuer issuer
* @param target target
* @param userId user-id
* @param certification certification
* @return singleton set
*/
public static CertificationSet fromCertification(
CertSynopsis issuer,
CertSynopsis target,
Optional<String> userId,
PGPSignature certification) {
Map<Optional<String>, List<Certification>> certificationMap = new HashMap<>();
List<Certification> certificationList = new ArrayList<>();
certificationList.add(new Certification(issuer, userId, target, certification));
certificationMap.put(userId, certificationList);
return new CertificationSet(issuer, target, certificationMap);
}
private CertificationSet(CertSynopsis issuer,
CertSynopsis target,
Map<Optional<String>, List<Certification>> certifications) {
this.issuer = issuer;
this.target = target;
this.certifications = new HashMap<>(certifications);
}
/**
* Merge this {@link CertificationSet} with another instance.
* After the operation, this will contain {@link Certification Certifications} from both sets.
*
* @param other other {@link CertificationSet}
*/
public void merge(@Nonnull CertificationSet other) {
if (other == this) {
return;
}
if (!issuer.getFingerprint().equals(other.issuer.getFingerprint())) {
throw new IllegalArgumentException("Issuer fingerprint mismatch.");
}
if (!target.getFingerprint().equals(other.target.getFingerprint())) {
throw new IllegalArgumentException("Target fingerprint mismatch.");
}
for (Map.Entry<Optional<String>, List<Certification>> entry : other.certifications.entrySet()) {
for (Certification certification : entry.getValue()) {
add(certification);
}
}
}
/**
* Add a {@link Certification} into this {@link CertificationSet}.
*
* @param certification certification
*/
public void add(@Nonnull Certification certification) {
if (!issuer.getFingerprint().equals(certification.getIssuer().getFingerprint())) {
throw new IllegalArgumentException("Issuer fingerprint mismatch.");
}
if (!target.getFingerprint().equals(certification.getTarget().getFingerprint())) {
throw new IllegalArgumentException("Target fingerprint mismatch.");
}
List<Certification> certificationsForUserId = certifications.get(certification.getUserId());
//noinspection Java8MapApi
if (certificationsForUserId == null) {
certificationsForUserId = new ArrayList<>();
certifications.put(certification.getUserId(), certificationsForUserId);
}
// TODO: Prevent duplicates, only keep newest timestamped sig?
certificationsForUserId.add(certification);
}
}

View file

@ -0,0 +1,76 @@
package org.pgpainless.wot.dijkstra.sq;
import javax.annotation.Nonnull;
public class Depth implements Comparable<Depth> {
private final Optional<Integer> depth;
private Depth(Optional<Integer> depth) {
this.depth = depth;
if (!isUnconstrained() && (getLimit().get() < 0 || getLimit().get() > 255)) {
throw new IllegalArgumentException("Depth must be a value between 0 and 255");
}
}
public static Depth unconstrained() {
return new Depth(Optional.empty());
}
public static Depth limited(int depth) {
return new Depth(Optional.just(depth));
}
public static Depth auto(int depth) {
return depth == 255 ? unconstrained() : limited(depth);
}
public Optional<Integer> getLimit() {
return depth;
}
public boolean isUnconstrained() {
return getLimit().isEmpty();
}
public Depth decrease(int value) {
if (isUnconstrained()) {
return unconstrained();
}
if (getLimit().get() >= value) {
return limited(getLimit().get() - value);
}
throw new IllegalArgumentException("Depth cannot be decreased.");
}
@Override
public int compareTo(@Nonnull Depth o) {
if (isUnconstrained()) {
if (o.isUnconstrained()) {
return 0;
} else {
return 1;
}
} else {
if (o.isUnconstrained()) {
return -1;
} else {
return Integer.compare(getLimit().get(), o.getLimit().get());
}
}
}
@Override
public String toString() {
return isUnconstrained() ? "unconstrained" : getLimit().get().toString();
}
public Depth min(Depth other) {
if (compareTo(other) <= 0) {
return this;
} else {
return other;
}
}
}

View file

@ -0,0 +1,154 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.bouncycastle.bcpg.sig.RevocationReason;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.RevocationState;
import org.pgpainless.algorithm.RevocationStateType;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.util.RevocationAttributes;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
public class Network {
private final Map<OpenPgpFingerprint, CertSynopsis> nodes;
private final Map<OpenPgpFingerprint, List<CertificationSet>> edges;
private final Map<OpenPgpFingerprint, List<CertificationSet>> reverseEdges;
private final ReferenceTime referenceTime;
public Network(Map<OpenPgpFingerprint, CertSynopsis> nodes,
Map<OpenPgpFingerprint, List<CertificationSet>> edges,
Map<OpenPgpFingerprint, List<CertificationSet>> reverseEdges,
ReferenceTime referenceTime) {
this.nodes = nodes;
this.edges = edges;
this.reverseEdges = reverseEdges;
this.referenceTime = referenceTime;
}
/**
* Create an empty {@link Network}.
*
* @param referenceTime reference time for evaluation
* @return network
*/
public static Network empty(@Nonnull ReferenceTime referenceTime) {
return new Network(
new HashMap<>(),
new HashMap<>(),
new HashMap<>(),
referenceTime);
}
/**
* Create a {@link Network} from a set of certificates.
*
* @param certificates set of certificates
* @param policy evaluation policy
* @param optReferenceTime reference time for evaluation
* @return network
*/
public static Network fromCertificates(
Iterable<PGPPublicKeyRing> certificates,
Policy policy,
Optional<ReferenceTime> optReferenceTime) {
ReferenceTime referenceTime = optReferenceTime.isPresent() ? optReferenceTime.get() : ReferenceTime.now();
List<KeyRingInfo> validCerts = new ArrayList<>();
for (PGPPublicKeyRing cert : certificates) {
KeyRingInfo info = new KeyRingInfo(cert, policy, referenceTime.getTimestamp());
if (info.getValidUserIds().isEmpty()) {
// Ignore invalid cert
} else {
validCerts.add(info);
}
}
return fromValidCertificates(
validCerts,
referenceTime
);
}
/**
* Create a {@link Network} from a set of validated certificates.
*
* @param validatedCertificates set of validated certificates
* @param referenceTime reference time
* @return network
*/
public static Network fromValidCertificates(
Iterable<KeyRingInfo> validatedCertificates,
ReferenceTime referenceTime) {
Map<OpenPgpFingerprint, KeyRingInfo> byFingerprint = new HashMap<>();
Map<Long, List<KeyRingInfo>> byKeyId = new HashMap<>();
Map<OpenPgpFingerprint, CertSynopsis> certSynopsisMap = new HashMap<>();
for (KeyRingInfo cert : validatedCertificates) {
//noinspection Java8MapApi
if (byFingerprint.get(cert.getFingerprint()) == null) {
byFingerprint.put(cert.getFingerprint(), cert);
}
List<KeyRingInfo> byKeyIdEntry = byKeyId.get(cert.getKeyId());
//noinspection Java8MapApi
if (byKeyIdEntry == null) {
byKeyIdEntry = new ArrayList<>();
byKeyId.put(cert.getKeyId(), byKeyIdEntry);
}
byKeyIdEntry.add(cert);
certSynopsisMap.put(cert.getFingerprint(),
new CertSynopsis(cert.getFingerprint(),
cert.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER),
revocationStateFromSignature(cert.getRevocationSelfSignature()),
new HashSet<>(cert.getValidUserIds())));
}
Map<OpenPgpFingerprint, List<CertificationSet>> edges = new HashMap<>();
Map<OpenPgpFingerprint, List<CertificationSet>> reverseEdges = new HashMap<>();
return new Network(certSynopsisMap, edges, reverseEdges, referenceTime);
}
public Map<OpenPgpFingerprint, CertSynopsis> getNodes() {
return new HashMap<>(nodes);
}
public Map<OpenPgpFingerprint, List<CertificationSet>> getEdges() {
return new HashMap<>(edges);
}
public Map<OpenPgpFingerprint, List<CertificationSet>> getReverseEdges() {
return new HashMap<>(reverseEdges);
}
public ReferenceTime getReferenceTime() {
return referenceTime;
}
private static RevocationState revocationStateFromSignature(PGPSignature revocation) {
if (revocation == null) {
return RevocationState.notRevoked();
}
RevocationReason revocationReason = SignatureSubpacketsUtil.getRevocationReason(revocation);
if (revocationReason == null) {
return RevocationState.hardRevoked();
}
return RevocationAttributes.Reason.isHardRevocation(revocationReason.getRevocationReason()) ?
RevocationState.hardRevoked() : RevocationState.softRevoked(revocation.getCreationTime());
}
}

View file

@ -0,0 +1,74 @@
package org.pgpainless.wot.dijkstra.sq;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class Optional<T> {
private final T item;
public static <T> Optional<T> empty() {
return new Optional<>();
}
public static <T> Optional<T> just(@Nonnull T item) {
return new Optional<>(item);
}
public static <T> Optional<T> maybe(@Nullable T item) {
return item == null ? empty() : just(item);
}
private Optional() {
this.item = null;
}
private Optional(@Nonnull T item) {
this.item = item;
}
public boolean isEmpty() {
return item == null;
}
public boolean isPresent() {
return item != null;
}
public @Nonnull T get() {
if (item == null) {
throw new NullPointerException("Item is null.");
}
return item;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional other = (Optional) obj;
if (isEmpty() && other.isEmpty()) {
return true;
}
if (isPresent() && isPresent()) {
return get().equals(other.get());
}
return false;
}
}

View file

@ -0,0 +1,90 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.ArrayList;
import java.util.List;
public class Path {
private CertSynopsis root;
private List<Certification> edges;
private Depth residualDepth;
public Path(CertSynopsis root) {
this.root = root;
this.edges = new ArrayList<>();
this.residualDepth = Depth.unconstrained();
}
public CertSynopsis getRoot() {
return root;
}
public CertSynopsis getTarget() {
if (edges.isEmpty()) {
return getRoot();
} else {
return edges.get(edges.size() - 1).getTarget();
}
}
public List<CertSynopsis> getCertificates() {
List<CertSynopsis> certs = new ArrayList<>();
certs.add(getRoot());
for (Certification edge : edges) {
certs.add(edge.getTarget());
}
return certs;
}
public int getLength() {
return edges.size() + 1;
}
public List<Certification> getCertifications() {
return new ArrayList<>(edges);
}
public Depth getResidualDepth() {
return residualDepth;
}
public int getAmount() {
if (edges.isEmpty()) {
return 120;
}
int min = 255;
for (Certification edge : edges) {
min = Math.min(min, edge.getTrustAmount());
}
return min;
}
public void append(Certification certification) {
if (!getTarget().getFingerprint().equals(certification.getIssuer().getFingerprint())) {
throw new IllegalArgumentException("Cannot append certification to path: Path's tail is not issuer of the certification.");
}
if (!residualDepth.isUnconstrained() && residualDepth.getLimit().get() == 0) {
throw new IllegalArgumentException("Not enough depth.");
}
boolean cyclic = getRoot().getFingerprint().equals(certification.getTarget().getFingerprint());
for (int i = 0; i < edges.size() && !cyclic; i++) {
Certification edge = edges.get(i);
if (edge.getTarget().getFingerprint().equals(certification.getTarget().getFingerprint())) {
if (i == edges.size() - 1) {
cyclic = edge.getUserId().equals(certification.getUserId());
} else {
cyclic = true;
}
}
}
if (cyclic) {
throw new IllegalArgumentException("Adding the certification to the path would create a cycle.");
}
residualDepth = certification.getTrustDepth().min(residualDepth.decrease(1));
edges.add(certification);
}
}

View file

@ -0,0 +1,46 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.ArrayList;
import java.util.List;
public class Paths {
private final List<Item> paths = new ArrayList<>();
public List<Item> getPaths() {
return new ArrayList<>(paths);
}
public void add(Path path, int amount) {
if (amount <= path.getAmount()) {
throw new AssertionError();
}
this.paths.add(new Item(path, amount));
}
public int getAmount() {
int sum = 0;
for (Item item : paths) {
sum += item.amount;
}
return sum;
}
public static class Item {
private final Path path;
private final int amount;
public Item(Path path, int amount) {
this.path = path;
this.amount = amount;
}
public Path getPath() {
return path;
}
public int getAmount() {
return amount;
}
}
}

View file

@ -0,0 +1,30 @@
package org.pgpainless.wot.dijkstra.sq;
import javax.annotation.Nonnull;
import java.util.Date;
public interface ReferenceTime {
@Nonnull Date getTimestamp();
static ReferenceTime now() {
final Date now = new Date();
return new ReferenceTime() {
@Override
@Nonnull
public Date getTimestamp() {
return now;
}
};
}
static ReferenceTime timestamp(@Nonnull Date timestamp) {
return new ReferenceTime() {
@Override
@Nonnull
public Date getTimestamp() {
return timestamp;
}
};
}
}

View file

@ -0,0 +1,51 @@
package org.pgpainless.wot.dijkstra.sq;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.bouncycastle.bcpg.sig.RegularExpression;
public class RegexSet {
private final Set<String> regexStrings;
private RegexSet(Set<String> regexStrings) {
this.regexStrings = regexStrings;
}
public static RegexSet fromList(@Nonnull List<RegularExpression> regexList) {
Set<String> regexStringSet = new HashSet<>();
for (RegularExpression regex : regexList) {
regexStringSet.add(regex.getRegex());
}
return new RegexSet(regexStringSet);
}
public static RegexSet fromRegex(@Nonnull RegularExpression regex) {
return fromList(Collections.singletonList(regex));
}
public static RegexSet wildcard() {
return fromList(Collections.emptyList());
}
public boolean matches(String string) {
if (regexStrings.isEmpty()) {
return true;
}
for (String regex : regexStrings) {
Matcher matcher = Pattern.compile(regex).matcher(string);
if (matcher.matches()) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,152 @@
package org.pgpainless.wot.dijkstra;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
public class BasicShortestPathDijkstraTest {
/**
* Generate a test graph from a string definition.
* The definition might look like this:
* <pre>
* Alice
* Bob -1> Charlie
* Charlie -4> Dieter -1> Alice
* Dieter -2> Charlie
* </pre>
* @param definition definition
* @return graph
*/
private Graph<String, Node<String>, SimpleEdge<String>, Cost.SimpleCost> generate(String definition) {
Set<Node<String>> nodes = new HashSet<>();
Set<SimpleEdge<String>> edges = new HashSet<>();
String[] lines = definition.split("\n");
for (String line : lines) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
String[] fromTo = line.split(" -\\d+> ");
if (fromTo.length == 1) {
// Unconnected node
nodes.add(new Node<>(fromTo[0]));
continue;
}
int searchOffset = 0;
for (int i = 0; i < fromTo.length - 1; i++) {
Node<String> from = new Node<>(fromTo[i]);
nodes.add(from);
searchOffset += fromTo[i].length() + " -".length();
int costStop = line.indexOf("> ", searchOffset);
String costString = line.substring(searchOffset, costStop);
Double cost = Double.parseDouble(costString);
searchOffset += costString.length() + "> ".length();
Node<String> to = new Node<>(fromTo[i + 1]);
nodes.add(to);
edges.add(new SimpleEdge<>(from, to, cost));
}
}
return new Graph<>(nodes, edges);
}
private Path<String, Node<String>, Cost.SimpleCost, SimpleEdge<String>> path(String definition) {
definition = definition.trim();
String[] fromTo = definition.split(" -\\d+> ");
if (fromTo.length == 1) {
// Unconnected node
Node<String> node = new Node<>(fromTo[0]);
return new Path<>(node, node, Collections.singletonList(new SimpleEdge<>(node, node, 0d)));
}
Node<String> start = null;
Node<String> end = null;
List<SimpleEdge<String>> edges = new ArrayList<>();
int searchOffset = 0;
for (int i = 0; i < fromTo.length - 1; i++) {
Node<String> from = new Node<>(fromTo[i]);
if (start == null) {
start = from;
}
searchOffset += fromTo[i].length() + " -".length();
int costStop = definition.indexOf("> ", searchOffset);
String costString = definition.substring(searchOffset, costStop);
Double cost = Double.parseDouble(costString);
searchOffset += costString.length() + "> ".length();
Node<String> to = new Node<>(fromTo[i + 1]);
edges.add(new SimpleEdge<>(from, to, cost));
end = to;
}
Path<String, Node<String>, Cost.SimpleCost, SimpleEdge<String>> path = new Path<>(start, end, edges);
return path;
}
@Test
public void exampleGraphTest() {
Graph<String, Node<String>, SimpleEdge<String>, Cost.SimpleCost> g = generate(
"Alice\n" +
"Bob -1> Charlie\n" +
"Bob -3> Dieter -1> Marlene\n" +
"Dieter -1> Alice\n" +
"Mallory\n");
Set<Node<String>> expectedNodes = new HashSet<>();
expectedNodes.add(new Node<>("Alice"));
expectedNodes.add(new Node<>("Bob"));
expectedNodes.add(new Node<>("Charlie"));
expectedNodes.add(new Node<>("Dieter"));
expectedNodes.add(new Node<>("Marlene"));
expectedNodes.add(new Node<>("Mallory"));
assertEquals(expectedNodes, g.getNodes());
Set<SimpleEdge<String>> expectedEdges = new HashSet<>();
expectedEdges.add(new SimpleEdge<>(new Node<>("Bob"), new Node<>("Charlie"), 1d));
expectedEdges.add(new SimpleEdge<>(new Node<>("Bob"), new Node<>("Dieter"), 3d));
expectedEdges.add(new SimpleEdge<>(new Node<>("Dieter"), new Node<>("Marlene"), 1d));
expectedEdges.add(new SimpleEdge<>(new Node<>("Dieter"), new Node<>("Alice"), 1d));
assertEquals(g.getEdges(), expectedEdges);
}
@Test
public void emptyNetworkTest() {
Graph<String, Node<String>, SimpleEdge<String>, Cost.SimpleCost> graph = generate("");
Node<String> root = new Node<>("root");
Node<String> target = new Node<>("target");
ShortestPathDijkstra<String> dijkstra = new ShortestPathDijkstra<>(graph, root);
Path<String, Node<String>, Cost.SimpleCost, SimpleEdge<String>> path = dijkstra.findPath(target);
assertNull(path);
}
@Test
public void pathFindingTest() {
Graph<String, Node<String>, SimpleEdge<String>, Cost.SimpleCost> graph = generate(
"Pablo\n" +
"Root -2> Alice -3> Alexandra\n" +
"Root -1> Karlos -1> Alexandra\n" +
"Karlos -2> Malte -4> Sven");
ShortestPathDijkstra<String> dijkstra = new ShortestPathDijkstra<>(graph, new Node<>("Root"));
assertEquals(path("Root -1> Karlos -2> Malte -4> Sven"), dijkstra.findPath(new Node<>("Sven")));
assertEquals(path("Root -1> Karlos"), dijkstra.findPath(new Node<>("Karlos")));
assertEquals(path("Root -1> Karlos -1> Alexandra"), dijkstra.findPath(new Node<>("Alexandra")));
dijkstra = new ShortestPathDijkstra<>(graph, new Node<>("Karlos"));
assertEquals(path("Karlos -2> Malte -4> Sven"), dijkstra.findPath(new Node<>("Sven")));
}
}

View file

@ -0,0 +1,31 @@
package org.pgpainless.wot.dijkstra;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class NodeTest {
@Test
public void equalsTest() {
Node<String> n1 = new Node<>("foo");
Node<String> n1_ = new Node<>("foo");
Node<String> n2 = new Node<>("bar");
assertEquals(n1, n1_);
assertEquals(n1, n1);
assertNotEquals(n1, n2);
Map<Node<String>, String> map = new HashMap<>();
map.put(n1, "foo");
map.put(n2, "bar");
assertEquals("foo", map.get(n1));
assertEquals("bar", map.get(n2));
assertEquals("foo", map.get(n1_));
}
}

View file

@ -0,0 +1,95 @@
package org.pgpainless.wot.dijkstra.sq;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DepthTest {
@Test
public void testUnlimitedItem() {
Depth depth = Depth.unconstrained();
assertTrue(depth.isUnconstrained());
assertFalse(depth.getLimit().isPresent());
}
@Test
public void testLimitedItem() {
Depth limited = Depth.limited(2);
assertFalse(limited.isUnconstrained());
assertTrue(limited.getLimit().isPresent());
assertEquals(2, limited.getLimit().get());
}
@Test
public void testDecreaseUnconstrainedYieldsUnconstrained() {
Depth unconstrained = Depth.unconstrained();
Depth decreased = unconstrained.decrease(20);
assertTrue(decreased.isUnconstrained());
}
@Test
public void testDecreaseLimitedYieldsDecreasedLimited() {
Depth limited = Depth.limited(1);
Depth decreased = limited.decrease(1);
assertFalse(decreased.isUnconstrained());
assertEquals(0, decreased.getLimit().get());
}
@Test
public void testDecreaseLimitedTooMuchYieldsException() {
Depth limited = Depth.limited(0);
assertThrows(IllegalArgumentException.class, () -> limited.decrease(1));
}
@Test
public void testCompareTo() {
Depth unlimited = Depth.unconstrained();
Depth unlimited2 = Depth.unconstrained();
Depth depth2 = Depth.limited(2);
Depth depth2_ = Depth.limited(2);
Depth depth5 = Depth.limited(5);
assertEquals(0, unlimited.compareTo(unlimited2));
assertTrue(unlimited.compareTo(depth2) > 0);
assertTrue(unlimited.compareTo(depth5) > 0);
assertTrue(depth2.compareTo(unlimited) < 0);
assertTrue(depth2.compareTo(depth5) < 0);
assertTrue(depth5.compareTo(depth2) > 0);
assertEquals(0, depth2.compareTo(depth2_));
}
@Test
public void testAutoUnconstrained() {
Depth depth = Depth.auto(255);
assertTrue(depth.isUnconstrained());
}
@Test
public void testAutoLimited() {
Depth depth = Depth.auto(120);
assertFalse(depth.isUnconstrained());
assertEquals(120, depth.getLimit().get());
}
@Test
public void testOutOfBounds() {
assertThrows(IllegalArgumentException.class, () -> Depth.limited(-1));
assertThrows(IllegalArgumentException.class, () -> Depth.limited(256));
assertThrows(IllegalArgumentException.class, () -> Depth.auto(-1));
assertThrows(IllegalArgumentException.class, () -> Depth.auto(256));
}
@Test
public void testToStringUnconstrained() {
assertEquals("unconstrained", Depth.unconstrained().toString());
}
@Test
public void testToStringLimited() {
assertEquals("1", Depth.limited(1).toString());
}
}

View file

@ -0,0 +1,35 @@
package org.pgpainless.wot.dijkstra.sq;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
public class NetworkTest {
@Test
public void testEmptyNetworkIsEmpty() {
ReferenceTime referenceTime = ReferenceTime.now();
Network network = Network.empty(referenceTime);
assertTrue(network.getNodes().isEmpty());
assertTrue(network.getEdges().isEmpty());
assertTrue(network.getReverseEdges().isEmpty());
assertEquals(referenceTime, network.getReferenceTime());
}
@Test
public void testNetworkFromCertificates() {
ReferenceTime referenceTime = ReferenceTime.now();
}
}

View file

@ -0,0 +1,275 @@
package org.pgpainless.wot.dijkstra.sq;
import java.io.IOException;
import java.io.InputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.Trustworthiness;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.signature.subpackets.CertificationSubpackets;
import org.pgpainless.util.Passphrase;
public class WotTestVectors {
private static WotTestVectors INSTANCE = null;
public static WotTestVectors getTestVectors() {
if (INSTANCE == null) {
INSTANCE = new WotTestVectors();
}
return INSTANCE;
}
public PGPSecretKeyRing getFreshFooBankCaKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankCaKey.asc"));
}
public PGPPublicKeyRing getFreshFooBankCaCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankCaCert.asc"));
}
public String getFooBankCaPassphrase() {
return "superS3cureP4ssphrase";
}
public SecretKeyRingProtector getFooBankCaProtector() {
return SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(getFooBankCaPassphrase()));
}
public PGPSecretKeyRing getFreshFooBankEmployeeKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankEmployeeKey.asc"));
}
public PGPPublicKeyRing getFreshFooBankEmployeeCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankEmployeeCert.asc"));
}
public String getFooBankEmployeePassphrase() {
return "iLoveWorking@FooBank";
}
public SecretKeyRingProtector getFooBankEmployeeProtector() {
return SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(getFooBankEmployeePassphrase()));
}
public PGPSecretKeyRing getFreshFooBankAdminKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankAdminKey.asc"));
}
public PGPPublicKeyRing getFreshFooBankAdminCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankAdminCert.asc"));
}
public String getFooBankAdminPassphrase() {
return "keepFooBankSecure";
}
public SecretKeyRingProtector getFooBankAdminProtector() {
return SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(getFooBankAdminPassphrase()));
}
public PGPSecretKeyRing getFreshFooBankCustomerKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankCustomerKey.asc"));
}
public PGPPublicKeyRing getFreshFooBankCustomerCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/foobankCustomerCert.asc"));
}
public PGPSecretKeyRing getFreshBarBankCaKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/barbankCaKey.asc"));
}
public PGPPublicKeyRing getFreshBarBankCaCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/barbankCaCert.asc"));
}
public PGPSecretKeyRing getFreshBarBankEmployeeKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/barbankEmployeeKey.asc"));
}
public PGPPublicKeyRing getFreshBarBankEmployeeCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/barbankEmployeeCert.asc"));
}
public PGPSecretKeyRing getFreshFakeFooBankEmployeeKey() throws IOException {
return PGPainless.readKeyRing().secretKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/fakeFoobankEmployeeKey.asc"));
}
public PGPPublicKeyRing getFreshFakeFooBankEmployeeCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("test_vectors/freshly_generated/fakeFoobankEmployeeCert.asc"));
}
public PGPPublicKeyRing getCrossSignedBarBankCaCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("cross_signed/barbankCaCert.asc"));
}
public PGPPublicKeyRing getCrossSignedBarBankEmployeeCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("cross_signed/barbankEmployeeCert.asc"));
}
public PGPPublicKeyRing getCrossSignedFooBankAdminCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("cross_signed/foobankAdminCert.asc"));
}
public PGPPublicKeyRing getCrossSignedFooBankCaCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("cross_signed/foobankCaCert.asc"));
}
public PGPPublicKeyRing getCrossSignedFooBankEmployeeCert() throws IOException {
return PGPainless.readKeyRing().publicKeyRing(getTestResourceInputStream("cross_signed/foobankEmployeeCert.asc"));
}
// Generate cross signed test vectors from freshly generated
public void crossSign() throws IOException, PGPException {
PGPSecretKeyRing freshFooBankCaKey = getFreshFooBankCaKey();
PGPPublicKeyRing freshFooBankCaCert = getFreshFooBankCaCert();
PGPSecretKeyRing freshFooBankEmployeeKey = getFreshFooBankEmployeeKey();
PGPPublicKeyRing freshFooBankEmployeeCert = getFreshFooBankEmployeeCert();
PGPSecretKeyRing freshFooBankAdminKey = getFreshFooBankAdminKey();
PGPPublicKeyRing freshFooBankAdminCert = getFreshFooBankAdminCert();
PGPSecretKeyRing freshFooBankCustomerKey = getFreshFooBankCustomerKey();
PGPPublicKeyRing freshFooBankCustomerCert = getFreshFooBankCustomerCert();
PGPSecretKeyRing freshBarBankCaKey = getFreshBarBankCaKey();
PGPPublicKeyRing freshBarBankCaCert = getFreshBarBankCaCert();
PGPSecretKeyRing freshBarBankEmployeeKey = getFreshBarBankEmployeeKey();
PGPPublicKeyRing freshBarBankEmployeeCert = getFreshBarBankEmployeeCert();
PGPSecretKeyRing freshFakeFooBankEmployeeKey = getFreshFakeFooBankEmployeeKey();
PGPPublicKeyRing freshFakeFooBankEmployeeCert = getFreshFakeFooBankEmployeeCert();
final String fooBankRegex = "<[^>]+[@.]foobank\\.com>$";
final String barBankRegex = "<[^>]+[@.]barbank\\.com>$";
// Foo CA signs Foo Employee
PGPPublicKeyRing caCertifiedFooBankEmployeeCert = PGPainless.certify()
.userIdOnCertificate("Foo Bank Employee <employee@foobank.com>", freshFooBankEmployeeCert)
.withKey(freshFooBankCaKey, getFooBankCaProtector())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.addNotationData(false, "affiliation@foobank.com", "employee");
}
})
.getCertifiedCertificate();
// Foo CA signs Foo Admin
PGPPublicKeyRing caCertifiedFooBankAdminCert = PGPainless.certify()
.userIdOnCertificate("Foo Bank Admin <admin@foobank.com>", freshFooBankAdminCert)
.withKey(freshFooBankCaKey, getFooBankCaProtector())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.addNotationData(false, "affiliation@foobank.com", "administrator");
}
})
.getCertifiedCertificate();
// Foo Employee delegates trust to Foo CA
PGPPublicKeyRing employeeDelegatedCaCert = PGPainless.certify()
.certificate(freshFooBankCaCert, Trustworthiness.fullyTrusted().introducer())
.withKey(freshFooBankEmployeeKey, getFooBankEmployeeProtector())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.setRegularExpression(fooBankRegex);
}
})
.getCertifiedCertificate();
// Foo Admin delegates trust to Foo CA
PGPPublicKeyRing adminDelegatedCaCert = PGPainless.certify()
.certificate(freshFooBankCaCert, Trustworthiness.fullyTrusted().introducer())
.withKey(freshFooBankAdminKey, getFooBankAdminProtector())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.setRegularExpression(fooBankRegex);
}
})
.getCertifiedCertificate();
// Customer delegates trust to Foo CA
PGPPublicKeyRing customerDelegatedCaCert = PGPainless.certify()
.certificate(freshFooBankCaCert, Trustworthiness.fullyTrusted().introducer())
.withKey(freshFooBankCustomerKey, SecretKeyRingProtector.unprotectedKeys())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.setRegularExpression(fooBankRegex);
}
})
.getCertifiedCertificate();
PGPPublicKeyRing mergedFooCa = PGPPublicKeyRing.join(employeeDelegatedCaCert, adminDelegatedCaCert);
mergedFooCa = PGPPublicKeyRing.join(mergedFooCa, customerDelegatedCaCert);
// Foo Admin delegates trust to Bar CA
PGPPublicKeyRing fooAdminDelegatedBarCa = PGPainless.certify()
.certificate(freshBarBankCaCert, Trustworthiness.fullyTrusted().introducer())
.withKey(freshFooBankAdminKey, getFooBankAdminProtector())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.setRegularExpression("<[^>]+[@.]barbank\\.com>$");
}
}).getCertifiedCertificate();
// Bar Employee delegates Bar CA
PGPPublicKeyRing barEmployeeDelegatesBarCa = PGPainless.certify()
.certificate(freshBarBankCaCert, Trustworthiness.fullyTrusted().introducer())
.withKey(freshBarBankEmployeeKey, SecretKeyRingProtector.unprotectedKeys())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.setRegularExpression(barBankRegex);
}
})
.getCertifiedCertificate();
PGPPublicKeyRing mergedBarCa = PGPPublicKeyRing.join(fooAdminDelegatedBarCa, barEmployeeDelegatesBarCa);
// Bar CA signs Bar Employee
PGPPublicKeyRing barCaCertifiedEmployeeCert = PGPainless.certify()
.userIdOnCertificate("Bar Bank Employee <employee@barbank.com>", freshBarBankEmployeeCert)
.withKey(freshBarBankCaKey, SecretKeyRingProtector.unprotectedKeys())
.buildWithSubpackets(new CertificationSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(CertificationSubpackets hashedSubpackets) {
hashedSubpackets.addNotationData(false, "affiliation@barbank.com", "employee");
}
})
.getCertifiedCertificate();
System.out.println("Foo Employee");
System.out.println(PGPainless.asciiArmor(caCertifiedFooBankEmployeeCert));
System.out.println("Foo Admin");
System.out.println(PGPainless.asciiArmor(caCertifiedFooBankAdminCert));
System.out.println("Foo CA");
System.out.println(PGPainless.asciiArmor(mergedFooCa));
System.out.println("Bar CA");
System.out.println(PGPainless.asciiArmor(mergedBarCa));
System.out.println("Bar Employee");
System.out.println(PGPainless.asciiArmor(barCaCertifiedEmployeeCert));
}
private static InputStream getTestResourceInputStream(String resource) {
InputStream inputStream = WotTestVectors.class.getClassLoader().getResourceAsStream(resource);
if (inputStream == null) {
throw new IllegalArgumentException(String.format("Unknown resource %s", resource));
}
return inputStream;
}
}

View file

@ -0,0 +1,47 @@
<!--
SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
SPDX-License-Identifier: Apache-2.0
-->
# Test Vectors
## Freshly Generated Vectors
The `freshly_generated/` directory contains freshly generated test vectors.
Those are keys and certificates without any third-party signatures.
```mermaid
graph LR;
a[Foo Bank CA &ltca&#64foobank.com&gt];
b[Foo Bank Employee &ltemployee&#64foobank.com&gt];
c[Foo Bank Admin &ltadmin&#64foobank.com&gt];
d[Customer &ltcustomer&#64example.com&gt];
e[Bar Bank CA &ltca&#64barbank.com&gt];
f[Bar Bank Employee &ltemployee&#64barbank.com&gt];
g[Foo Bank Employee &#40Attacker&#41 &ltemployee&#64foobank.com&gt];
```
## Cross Signed Vectors
The `cross_signed/` directory contains test vectors that model the following interconnectivity:
```mermaid
graph TD;
a[Foo Bank CA &ltca&#64foobank.com&gt];
b[Foo Bank Employee &ltemployee&#64foobank.com&gt];
c[Foo Bank Admin &ltadmin&#64foobank.com&gt];
d[Customer &ltcustomer&#64example.com&gt];
e[Bar Bank CA &ltca&#64barbank.com&gt];
f[Bar Bank Employee &ltemployee&#64barbank.com&gt];
g[Foo Bank Employee &#40Attacker&#41 &ltemployee&#64foobank.com&gt];
a -- generic certification --> b & c;
b & c & d == 1:120:&quot&lt&#91^&gt&#93&#43&#91&#64.&#93foobank\.com>$&quot ==> a;
e -- generic certification --> f;
c == 1:120:&quot&lt&#91^&gt&#93&#43&#91&#64.&#93barbank\.com>$&quot ==> e;
```
## Useful Resources:
* https://mateam.net/html-escape-characters/
* https://docs.sequoia-pgp.org/sequoia_openpgp/regex/index.html#caveat-emptor

View file

@ -0,0 +1,28 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: CEE5 FB5D C524 84A6 9B73 9FAD 7DA0 2993 D86B 0355
Comment: Bar Bank CA <ca@barbank.com>
mDMEYs1BgxYJKwYBBAHaRw8BAQdA3hOlkHAVyvTwePiifKnwLhkcJnswNidJ7dWo
UKEQlZWIlAQfFgoARgUCYs1MVwkQ9fHjtTaNbKcWIQSmiOyyabOBYdpNc4/18eO1
No1spxoGPFtePl0rW0AuXWJhcmJhbmtcLmNvbT4kAAOFAXgAAOfiAQCJIhMid48+
8FS9IO76ELTYMsy0q/kBd4z3Gs+OYqM3ZQEAjyqoEKT8OHNC3zPkneE4bhsB21CH
PMBe+pYcUE3tZAmIlAQfFgoARgUCYs1MVwkQitIzV+t4uwgWIQREeoKqA/fXpLHa
TXyK0jNX63i7CBoGPFtePl0rW0AuXWJhcmJhbmtcLmNvbT4kAAOFAXgAAHb5AP99
3BHUJhkpc6S0NiTsT1OKifmAuI9bRT++TIqEB6p7uwEA09k6Y59pjZhnISG4+KiK
Y+ZhGKx6bbyGIXirwlssMQK0HEJhciBCYW5rIENBIDxjYUBiYXJiYW5rLmNvbT6I
jwQTFgoAQQUCYs1BgwkQfaApk9hrA1UWIQTO5ftdxSSEpptzn619oCmT2GsDVQKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAABltgEA4Ob86bhjLa6Xg4wnzYIGH21W
EUlc37BBaqwr4qvfsA4A/0BzOdTy3Wty5DkdMiA2yPOxIVuHbTD8pgqpne2yoegN
uDgEYs1BgxIKKwYBBAGXVQEFAQEHQDVHLNRE4Gtg9/BzwSUMW3psBD7OUm4Df0gg
ilvLZhMcAwEIB4h1BBgWCgAdBQJizUGDAp4BApsMBRYCAwEABAsJCAcFFQoJCAsA
CgkQfaApk9hrA1WrYAEAsZsEoUfAPIG6d9gtK7Zmm3zt+xiTELW08E/pwjmFTmEB
AODICUhsEelkdXrbIkL2oWAKoxlsbkyA6mNXBt5UXQcJuDMEYs1BgxYJKwYBBAHa
Rw8BAQdARHF/sgSTB38osF0+XA9N2htsMIrb322j3LQdrk0M77SI1QQYFgoAfQUC
Ys1BgwKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmLNQYMACgkQtEK9
w/vjcduqJwD+KW23+689GXtu4k3PW60GPsiue22lMGwZY7hsWvZB1hkBAPER8PMs
0MJAQzcs2u6bYPMDI921umqX8Yvsob1kzs0FAAoJEH2gKZPYawNVdKwBAKz5zfwE
KZ2dGPCsUJzAXfPxe0LmATcvS5g/LvAreD0KAQDfpfMFO2EqQu6wDt+e5MH3YCjG
saYwvjB1elj1zhblAg==
=O9uk
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,25 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 447A 82AA 03F7 D7A4 B1DA 4D7C 8AD2 3357 EB78 BB08
Comment: Bar Bank Employee <employee@barbank.com>
mDMEYs1CXRYJKwYBBAHaRw8BAQdAeMjyfqInb8aZppFsKHdndoeL3j08Z5CtagKz
KPb6YGO0KEJhciBCYW5rIEVtcGxveWVlIDxlbXBsb3llZUBiYXJiYW5rLmNvbT6I
jwQTFgoAQQUCYs1CXQkQitIzV+t4uwgWIQREeoKqA/fXpLHaTXyK0jNX63i7CAKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAACgcgD7Bk2Hu+Lu3aA+IyzFyTQOw8zQ
RUeA/X8neEIezYDTC+MA/jGFyiKW7b+6W3zgdIgiHMeWhG3Wx2oOC1izW2+tP2cJ
iJ4EEBYKAFAFAmLNTFcJEH2gKZPYawNVFiEEzuX7XcUkhKabc5+tfaApk9hrA1Uo
FIAAAAAAFwAIYWZmaWxpYXRpb25AYmFyYmFuay5jb21lbXBsb3llZQAAG4ABAMF2
yTTckIjHNIyv7M8mcUx/zW++oJMxnhztT73cQeNxAQDVoyHqRgLpWR4lKw2b5IwE
gw9KT9zIA9jveWufwgIvD7g4BGLNQl0SCisGAQQBl1UBBQEBB0Bq216nP4pw9r1F
8OMcUWisYUvOePUeULPLYV+jX8UdBgMBCAeIdQQYFgoAHQUCYs1CXQKeAQKbDAUW
AgMBAAQLCQgHBRUKCQgLAAoJEIrSM1freLsIo5QA/A9zDz8XrjtbJIK60J3kmwvY
jynF9IkzyFTj4sNeHhJTAP92dTWvbogEwzPhBbofwzOT7PXg7laQ8v9eTqr50lWf
B7gzBGLNQl0WCSsGAQQB2kcPAQEHQEV9SpoogW2HrVIaU953W31ceDvDmBWMFBQQ
a3Qsz6IBiNUEGBYKAH0FAmLNQl0CngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkW
CgAGBQJizUJdAAoJENG3C+ldSVbaPsAA/2giZHcGT0nyAojPds1IfIE97dyAlVgj
fbTH3H4QNVsAAP9dpakDZya/q8M/wnZOyOBf+dHiUrvDbxiBo4tj2ANJBgAKCRCK
0jNX63i7CMPgAP4ofxoiit/7nqvndk50NkiM9Ab0yTcLnjY+b28YMDLacwEApoEl
I15NDk/uTXQRKtXrHKoORwSK3XRPSSGYgMOhkwQ=
=LiUn
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,25 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: A688 ECB2 69B3 8161 DA4D 738F F5F1 E3B5 368D 6CA7
Comment: Foo Bank Admin <admin@foobank.com>
mDMEYrcZeRYJKwYBBAHaRw8BAQdABkxvWXwHDD6B3hXYQehQkil3e3Gcg5mNbOdS
9DSeny20IkZvbyBCYW5rIEFkbWluIDxhZG1pbkBmb29iYW5rLmNvbT6IjwQTFgoA
QQUCYrcZeQkQ9fHjtTaNbKcWIQSmiOyyabOBYdpNc4/18eO1No1spwKeAQKbAQUW
AgMBAAQLCQgHBRUKCQgLApkBAABPGwEAv0TWDMkJV6QiIgm8V4b9uIEzMpQku4UM
FBHvMKXh0W8A/1pocJBG+K9srn8NJlPBul7duoNbkzzHANkytFVEBOgEiKMEEBYK
AFUFAmLNTFYJEKFJBrdXMI7wFiEEMUVo/QiegSvLRR4roUkGt1cwjvAtFIAAAAAA
FwANYWZmaWxpYXRpb25AZm9vYmFuay5jb21hZG1pbmlzdHJhdG9yAADddwEAkcJU
aQfRbAkUvOp1zPUddQq6+a5C1y33d5s2n9gLqucBALy/kkbN5moNxEtcqTD5K1uD
OFfEVEIOEAGE7ic1dWcMuDgEYrcZeRIKKwYBBAGXVQEFAQEHQDd1fqNPF27j+Z8B
PtbyoU6sp63UQfBhJrUKNFjpWNZrAwEIB4h1BBgWCgAdBQJitxl5Ap4BApsMBRYC
AwEABAsJCAcFFQoJCAsACgkQ9fHjtTaNbKdmNwEAzTQAJ/ML5puyGHR9zCbuAonD
B0lKoXlJeWX7YpsLSugA/3M4YgzpBfoRNbehCbc2hcd/hbW9gKQs2k2iCUYDoPIN
uDMEYrcZeRYJKwYBBAHaRw8BAQdAIF2e85PlS4PWquE3NLE59A1tr9KPxEznQ0Ej
G/DfefiI1QQYFgoAfQUCYrcZeQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYK
AAYFAmK3GXkACgkQUqp0e41/iO+PswD+I+dgdOrj/jflr3D0Th8cugj9zFzwFYn9
aaGmiee5wv8A/RvvJoiwHMTi3QPvR4zGdF5pibOccbWr/dggFjKXTmoFAAoJEPXx
47U2jWynVbEBANEjxzV8KnujGor79Mc1ag9wxyDgihXRowsoKEnSkyHQAQD6n0Pf
1q8fl7BxbreVeY+0HN2swKlO9TvO303a3X6/BQ==
=WtDJ
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,31 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 3145 68FD 089E 812B CB45 1E2B A149 06B7 5730 8EF0
Comment: Foo Bank CA <ca@foobank.com>
mDMEYrcXbxYJKwYBBAHaRw8BAQdAPIIGHwbBzEIExd70jtw7uDvOymt0nU83uLqR
4Og+cxCIlAQfFgoARgUCYs1MVgkQIa3PO58zSf4WIQQ7JP05RzLWqc9uBx4hrc87
nzNJ/hoGPFtePl0rW0AuXWZvb2JhbmtcLmNvbT4kAAOFAXgAAD4CAPwIMuVG2f+b
UfDsrtbQx8UO8TUnoRVHhwW/ogsp9Mop6AD/SoMIK1aNxzha56NOgCmAHo3hvdVp
gflXu5SlMpDcBgyIlAQfFgoARgUCYs1MVwkQ9fHjtTaNbKcWIQSmiOyyabOBYdpN
c4/18eO1No1spxoGPFtePl0rW0AuXWZvb2JhbmtcLmNvbT4kAAOFAXgAADS+AQD5
L1q6IO6xnG7pz4iammO7yM5jseDKsnd06q1qXTOxRQEAsYz2dx0owhU+4qZwfEME
kLIewdsBcjJurlXYDZJSkAuIlAQfFgoARgUCYs1MVwkQ+LtlEBaP0r8WIQQsTMpM
iRd/0ZHuo+D4u2UQFo/SvxoGPFtePl0rW0AuXWZvb2JhbmtcLmNvbT4kAAOFAXgA
AKlYAQDO0czTk2j+CRpYLwH24UT0NZQGcImx/xW+XHUz40y/mgEAmvkBkS0yaDET
94Nq1FpCL0oaheEybrXPzVT5+67Q2QW0HEZvbyBCYW5rIENBIDxjYUBmb29iYW5r
LmNvbT6IjwQTFgoAQQUCYrcXbwkQoUkGt1cwjvAWIQQxRWj9CJ6BK8tFHiuhSQa3
VzCO8AKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAAD+HwEAzElDjL9DRUnWd4yK
jNoB+yT6RAQ9kD6BgRs/vKyQtqcBANfIysKFneE0B4lExhh6hB2cGoMR/YMhJHSd
9zorxH4KuDgEYrcXbxIKKwYBBAGXVQEFAQEHQMmhBx59elwIbvvv3UOCLZn2VJDE
gkOGq1DOpYRNXe8FAwEIB4h1BBgWCgAdBQJitxdvAp4BApsMBRYCAwEABAsJCAcF
FQoJCAsACgkQoUkGt1cwjvDeBgEA9rsAaBuE5QWoZSH6FxHGpq5XY1rFpmvFKn7g
hZ33p6ABAJZJx0RQZGDaI5f46Pjph2U7pvLm18mXNd5cYPQtcFoFuDMEYrcXcBYJ
KwYBBAHaRw8BAQdAwdROT7LazWiJsEWF/bXjcAybl7TE3avv7GTVUT00MK2I1QQY
FgoAfQUCYrcXcAKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3F3AA
CgkQKvTwayMtR7nA+wEAzKlKeGvOSUtp5R07BTmOchkP0T3sZ0L4MvBBtpAuU5YA
/jNm1162I/ceEEtKgyKp0wC5pqYR0b+AWgB0tPaMfaoJAAoJEKFJBrdXMI7w608A
/0nV1NXH/U0pRzmNNeRJWAMlFXUdxhhYSlqrqYf2GQRlAP9NR9iMwpA0gFP+Uey3
bCNC8MxtZLKhPkz2Rz4pnnfaDA==
=y1eO
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,25 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 3B24 FD39 4732 D6A9 CF6E 071E 21AD CF3B 9F33 49FE
Comment: Foo Bank Employee <employee@foobank.com>
mDMEYrcZeRYJKwYBBAHaRw8BAQdAl9j10kv/7NRAZe+yATf7kkXOoHwkxaKkHzM3
wMihWlu0KEZvbyBCYW5rIEVtcGxveWVlIDxlbXBsb3llZUBmb29iYW5rLmNvbT6I
jwQTFgoAQQUCYrcZeQkQIa3PO58zSf4WIQQ7JP05RzLWqc9uBx4hrc87nzNJ/gKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAAA1aAEAthiiVUcYiKVyzfzbrZLkQlTW
NR6fu85fR5rPns2NUdwA/2vkrneDmkE04osRwUGLMh30iQzduQ3aFW0hEe2OmDUO
iJ4EEBYKAFAFAmLNTFYJEKFJBrdXMI7wFiEEMUVo/QiegSvLRR4roUkGt1cwjvAo
FIAAAAAAFwAIYWZmaWxpYXRpb25AZm9vYmFuay5jb21lbXBsb3llZQAAVjoBAOqF
r5tscY9sx4sxEjgDJ+v4+Jd4KxU8bNljzde4BG+AAP9Dj2IhrpNe8CJJlncV2TC1
55IXAr/DLYcKwzqEJrVxBLg4BGK3GXkSCisGAQQBl1UBBQEBB0ArMHy+cSuGxSdZ
ScGncoxzB9ZLioxTRlR5Mqd/M2rNGAMBCAeIdQQYFgoAHQUCYrcZeQKeAQKbDAUW
AgMBAAQLCQgHBRUKCQgLAAoJECGtzzufM0n+vDoA/A3p3LMYSUcvELY9Af3nC6/4
71r5B3MDnURShddOtcRyAP96J1i5ExKnC+Ltw36/QtyxfyHp9Pr9Fw6BX/zq4EFo
CrgzBGK3GXkWCSsGAQQB2kcPAQEHQPlVJ9tosjkDY81zIKll52Gh00rhhXY1jFEx
x248kkrjiNUEGBYKAH0FAmK3GXkCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkW
CgAGBQJitxl5AAoJEJxjnlk3tCvcxesBAIf5xQYgRFfDeQlr63uglNCsvOXMa8BN
LzgObT2nn8LrAQC+sqoWycLRFdFqg29E4OjhZd3iRvFWDoIfs67d9ywBDgAKCRAh
rc87nzNJ/jWHAP4zs2k1MtV2eOcjUzdt8cfMsiHcD29uJsv6O628gqVdqgD/dvb5
LPKxFPnfhSGOip0q2USrbBZLgupBjsNuVVu2IgM=
=DqW1
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: CEE5 FB5D C524 84A6 9B73 9FAD 7DA0 2993 D86B 0355
Comment: Bar Bank CA <ca@barbank.com>
mDMEYs1BgxYJKwYBBAHaRw8BAQdA3hOlkHAVyvTwePiifKnwLhkcJnswNidJ7dWo
UKEQlZW0HEJhciBCYW5rIENBIDxjYUBiYXJiYW5rLmNvbT6IjwQTFgoAQQUCYs1B
gwkQfaApk9hrA1UWIQTO5ftdxSSEpptzn619oCmT2GsDVQKeAQKbAQUWAgMBAAQL
CQgHBRUKCQgLApkBAABltgEA4Ob86bhjLa6Xg4wnzYIGH21WEUlc37BBaqwr4qvf
sA4A/0BzOdTy3Wty5DkdMiA2yPOxIVuHbTD8pgqpne2yoegNuDgEYs1BgxIKKwYB
BAGXVQEFAQEHQDVHLNRE4Gtg9/BzwSUMW3psBD7OUm4Df0ggilvLZhMcAwEIB4h1
BBgWCgAdBQJizUGDAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQfaApk9hrA1Wr
YAEAsZsEoUfAPIG6d9gtK7Zmm3zt+xiTELW08E/pwjmFTmEBAODICUhsEelkdXrb
IkL2oWAKoxlsbkyA6mNXBt5UXQcJuDMEYs1BgxYJKwYBBAHaRw8BAQdARHF/sgST
B38osF0+XA9N2htsMIrb322j3LQdrk0M77SI1QQYFgoAfQUCYs1BgwKeAQKbAgUW
AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmLNQYMACgkQtEK9w/vjcduqJwD+KW23
+689GXtu4k3PW60GPsiue22lMGwZY7hsWvZB1hkBAPER8PMs0MJAQzcs2u6bYPMD
I921umqX8Yvsob1kzs0FAAoJEH2gKZPYawNVdKwBAKz5zfwEKZ2dGPCsUJzAXfPx
e0LmATcvS5g/LvAreD0KAQDfpfMFO2EqQu6wDt+e5MH3YCjGsaYwvjB1elj1zhbl
Ag==
=Xlkn
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,24 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: CEE5 FB5D C524 84A6 9B73 9FAD 7DA0 2993 D86B 0355
Comment: Bar Bank CA <ca@barbank.com>
lFgEYs1BgxYJKwYBBAHaRw8BAQdA3hOlkHAVyvTwePiifKnwLhkcJnswNidJ7dWo
UKEQlZUAAQD94AUX6NT0z3kvWb1CXxgZacF3w/r2IWV0lSc6WZDcQRBTtBxCYXIg
QmFuayBDQSA8Y2FAYmFyYmFuay5jb20+iI8EExYKAEEFAmLNQYMJEH2gKZPYawNV
FiEEzuX7XcUkhKabc5+tfaApk9hrA1UCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ
AQAAZbYBAODm/Om4Yy2ul4OMJ82CBh9tVhFJXN+wQWqsK+Kr37AOAP9AcznU8t1r
cuQ5HTIgNsjzsSFbh20w/KYKqZ3tsqHoDZxdBGLNQYMSCisGAQQBl1UBBQEBB0A1
RyzUROBrYPfwc8ElDFt6bAQ+zlJuA39IIIpby2YTHAMBCAcAAP9FU6uepd97vJ4y
VTcvtzPXG9jvpgNw50pDfV90z6VAwBEaiHUEGBYKAB0FAmLNQYMCngECmwwFFgID
AQAECwkIBwUVCgkICwAKCRB9oCmT2GsDVatgAQCxmwShR8A8gbp32C0rtmabfO37
GJMQtbTwT+nCOYVOYQEA4MgJSGwR6WR1etsiQvahYAqjGWxuTIDqY1cG3lRdBwmc
WARizUGDFgkrBgEEAdpHDwEBB0BEcX+yBJMHfyiwXT5cD03aG2wwitvfbaPctB2u
TQzvtAABAJJiLD7fXGdr/liZ//EOhJ4YicrcxXahud6q3OYA1QUJEYSI1QQYFgoA
fQUCYs1BgwKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmLNQYMACgkQ
tEK9w/vjcduqJwD+KW23+689GXtu4k3PW60GPsiue22lMGwZY7hsWvZB1hkBAPER
8PMs0MJAQzcs2u6bYPMDI921umqX8Yvsob1kzs0FAAoJEH2gKZPYawNVdKwBAKz5
zfwEKZ2dGPCsUJzAXfPxe0LmATcvS5g/LvAreD0KAQDfpfMFO2EqQu6wDt+e5MH3
YCjGsaYwvjB1elj1zhblAg==
=owuy
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 447A 82AA 03F7 D7A4 B1DA 4D7C 8AD2 3357 EB78 BB08
Comment: Bar Bank Employee <employee@barbank.com>
mDMEYs1CXRYJKwYBBAHaRw8BAQdAeMjyfqInb8aZppFsKHdndoeL3j08Z5CtagKz
KPb6YGO0KEJhciBCYW5rIEVtcGxveWVlIDxlbXBsb3llZUBiYXJiYW5rLmNvbT6I
jwQTFgoAQQUCYs1CXQkQitIzV+t4uwgWIQREeoKqA/fXpLHaTXyK0jNX63i7CAKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAACgcgD7Bk2Hu+Lu3aA+IyzFyTQOw8zQ
RUeA/X8neEIezYDTC+MA/jGFyiKW7b+6W3zgdIgiHMeWhG3Wx2oOC1izW2+tP2cJ
uDgEYs1CXRIKKwYBBAGXVQEFAQEHQGrbXqc/inD2vUXw4xxRaKxhS8549R5Qs8th
X6NfxR0GAwEIB4h1BBgWCgAdBQJizUJdAp4BApsMBRYCAwEABAsJCAcFFQoJCAsA
CgkQitIzV+t4uwijlAD8D3MPPxeuO1skgrrQneSbC9iPKcX0iTPIVOPiw14eElMA
/3Z1Na9uiATDM+EFuh/DM5Ps9eDuVpDy/15OqvnSVZ8HuDMEYs1CXRYJKwYBBAHa
Rw8BAQdARX1KmiiBbYetUhpT3ndbfVx4O8OYFYwUFBBrdCzPogGI1QQYFgoAfQUC
Ys1CXQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmLNQl0ACgkQ0bcL
6V1JVto+wAD/aCJkdwZPSfICiM92zUh8gT3t3ICVWCN9tMfcfhA1WwAA/12lqQNn
Jr+rwz/Cdk7I4F/50eJSu8NvGIGji2PYA0kGAAoJEIrSM1freLsIw+AA/ih/GiKK
3/ueq+d2TnQ2SIz0BvTJNwueNj5vbxgwMtpzAQCmgSUjXk0OT+5NdBEq1escqg5H
BIrddE9JIZiAw6GTBA==
=F0kp
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,24 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: 447A 82AA 03F7 D7A4 B1DA 4D7C 8AD2 3357 EB78 BB08
Comment: Bar Bank Employee <employee@barbank.com>
lFgEYs1CXRYJKwYBBAHaRw8BAQdAeMjyfqInb8aZppFsKHdndoeL3j08Z5CtagKz
KPb6YGMAAP0WnOME3m2zmC6ujA1r5s1RLCtIsrWnkX62ICDCJzIWLA8ftChCYXIg
QmFuayBFbXBsb3llZSA8ZW1wbG95ZWVAYmFyYmFuay5jb20+iI8EExYKAEEFAmLN
Ql0JEIrSM1freLsIFiEERHqCqgP316Sx2k18itIzV+t4uwgCngECmwEFFgIDAQAE
CwkIBwUVCgkICwKZAQAAoHIA+wZNh7vi7t2gPiMsxck0DsPM0EVHgP1/J3hCHs2A
0wvjAP4xhcoilu2/ult84HSIIhzHloRt1sdqDgtYs1tvrT9nCZxdBGLNQl0SCisG
AQQBl1UBBQEBB0Bq216nP4pw9r1F8OMcUWisYUvOePUeULPLYV+jX8UdBgMBCAcA
AP9AYnLs0zkVSsWMxZDMTCoW1fjrgjzOMGeZRtsvrcUA2BF2iHUEGBYKAB0FAmLN
Ql0CngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCK0jNX63i7CKOUAPwPcw8/F647
WySCutCd5JsL2I8pxfSJM8hU4+LDXh4SUwD/dnU1r26IBMMz4QW6H8Mzk+z14O5W
kPL/Xk6q+dJVnwecWARizUJdFgkrBgEEAdpHDwEBB0BFfUqaKIFth61SGlPed1t9
XHg7w5gVjBQUEGt0LM+iAQAA/3fH0f3QPCOEpByws3f4Ovqjc+IzwFmA6mmwlxN2
9wNpEs6I1QQYFgoAfQUCYs1CXQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYK
AAYFAmLNQl0ACgkQ0bcL6V1JVto+wAD/aCJkdwZPSfICiM92zUh8gT3t3ICVWCN9
tMfcfhA1WwAA/12lqQNnJr+rwz/Cdk7I4F/50eJSu8NvGIGji2PYA0kGAAoJEIrS
M1freLsIw+AA/ih/GiKK3/ueq+d2TnQ2SIz0BvTJNwueNj5vbxgwMtpzAQCmgSUj
Xk0OT+5NdBEq1escqg5HBIrddE9JIZiAw6GTBA==
=7A42
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: E5C1 A41B E6BF 8E3D 595B 7214 2284 3AF5 6677 E9B0
Comment: Foo Bank Employee (Attacker) <employee@foobank.com>
mDMEYs1CoBYJKwYBBAHaRw8BAQdAKjGuG4PPeUX1utitGGofxfcD5Sqf7c9CtyFA
qXA/gd60M0ZvbyBCYW5rIEVtcGxveWVlIChBdHRhY2tlcikgPGVtcGxveWVlQGZv
b2JhbmsuY29tPoiPBBMWCgBBBQJizUKgCRAihDr1ZnfpsBYhBOXBpBvmv449WVty
FCKEOvVmd+mwAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAAorAP9E0cnmX26P
zIcvmYXqCH4gfkH/18efgKAtLxFwz3yxuwD+KFj4xDxWy/KTmFRhJmFDBhJsNUiH
tNEbVpBV1GiVLQ24OARizUKgEgorBgEEAZdVAQUBAQdAU05+Xcun+77TCwhuwW/Y
zdKexCD+6u4EPUXyDKo0rjMDAQgHiHUEGBYKAB0FAmLNQqACngECmwwFFgIDAQAE
CwkIBwUVCgkICwAKCRAihDr1ZnfpsHH/AQDsxQe/sYVqwgtSFbOxw/hmGMEmMOwx
E5B+Pk7lLGcC3gEAuaFwRb4UgOeadQQ7eerxE0djYjL+wpqYLkYZrWI1dAy4MwRi
zUKgFgkrBgEEAdpHDwEBB0Dc1odFo5f84ciLYjkWoH0jAMlZxc/TLMS1yi5lrohX
M4jVBBgWCgB9BQJizUKgAp4BApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUC
Ys1CoAAKCRDcHi36XF9/A/pLAQD7tDyLsbP+s6jV4ZloujljLdxIlhg+g/87ruhc
ISPRKQD/bTubvAzC8ixoYAWGPZOEut03b+zJ4g1l8SHXVCb+HwkACgkQIoQ69WZ3
6bAdwgEAhZb7c0u07FlgxXkEIPIt45DwZbML37hxaVjF08NlwssBAJInWO4Gcj8T
L0GLRaWEF+8dpPp+WtgklVkFel7f2XoK
=UN4g
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,24 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: E5C1 A41B E6BF 8E3D 595B 7214 2284 3AF5 6677 E9B0
Comment: Foo Bank Employee (Attacker) <employee@foobank.com>
lFgEYs1CoBYJKwYBBAHaRw8BAQdAKjGuG4PPeUX1utitGGofxfcD5Sqf7c9CtyFA
qXA/gd4AAP4vJXkVBVsASvS7pwIJXVuqKDb7yeCaR6Bjh9ihE1tZQw49tDNGb28g
QmFuayBFbXBsb3llZSAoQXR0YWNrZXIpIDxlbXBsb3llZUBmb29iYW5rLmNvbT6I
jwQTFgoAQQUCYs1CoAkQIoQ69WZ36bAWIQTlwaQb5r+OPVlbchQihDr1ZnfpsAKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAAAKKwD/RNHJ5l9uj8yHL5mF6gh+IH5B
/9fHn4CgLS8RcM98sbsA/ihY+MQ8Vsvyk5hUYSZhQwYSbDVIh7TRG1aQVdRolS0N
nF0EYs1CoBIKKwYBBAGXVQEFAQEHQFNOfl3Lp/u+0wsIbsFv2M3SnsQg/uruBD1F
8gyqNK4zAwEIBwAA/2grdpHBu9eI82YNrx+/ljb69FIYK2ttBVmL5awfh9rgEWiI
dQQYFgoAHQUCYs1CoAKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJECKEOvVmd+mw
cf8BAOzFB7+xhWrCC1IVs7HD+GYYwSYw7DETkH4+TuUsZwLeAQC5oXBFvhSA55p1
BDt56vETR2NiMv7CmpguRhmtYjV0DJxYBGLNQqAWCSsGAQQB2kcPAQEHQNzWh0Wj
l/zhyItiORagfSMAyVnFz9MsxLXKLmWuiFczAAEA+xMd+XrMwFJisctVMfZaaPDR
QZ7EAVeCZU2UAe0u1ssQ2ojVBBgWCgB9BQJizUKgAp4BApsCBRYCAwEABAsJCAcF
FQoJCAtfIAQZFgoABgUCYs1CoAAKCRDcHi36XF9/A/pLAQD7tDyLsbP+s6jV4Zlo
ujljLdxIlhg+g/87ruhcISPRKQD/bTubvAzC8ixoYAWGPZOEut03b+zJ4g1l8SHX
VCb+HwkACgkQIoQ69WZ36bAdwgEAhZb7c0u07FlgxXkEIPIt45DwZbML37hxaVjF
08NlwssBAJInWO4Gcj8TL0GLRaWEF+8dpPp+WtgklVkFel7f2XoK
=saRS
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: A688 ECB2 69B3 8161 DA4D 738F F5F1 E3B5 368D 6CA7
Comment: Foo Bank Admin <admin@foobank.com>
mDMEYrcZeRYJKwYBBAHaRw8BAQdABkxvWXwHDD6B3hXYQehQkil3e3Gcg5mNbOdS
9DSeny20IkZvbyBCYW5rIEFkbWluIDxhZG1pbkBmb29iYW5rLmNvbT6IjwQTFgoA
QQUCYrcZeQkQ9fHjtTaNbKcWIQSmiOyyabOBYdpNc4/18eO1No1spwKeAQKbAQUW
AgMBAAQLCQgHBRUKCQgLApkBAABPGwEAv0TWDMkJV6QiIgm8V4b9uIEzMpQku4UM
FBHvMKXh0W8A/1pocJBG+K9srn8NJlPBul7duoNbkzzHANkytFVEBOgEuDgEYrcZ
eRIKKwYBBAGXVQEFAQEHQDd1fqNPF27j+Z8BPtbyoU6sp63UQfBhJrUKNFjpWNZr
AwEIB4h1BBgWCgAdBQJitxl5Ap4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQ9fHj
tTaNbKdmNwEAzTQAJ/ML5puyGHR9zCbuAonDB0lKoXlJeWX7YpsLSugA/3M4Ygzp
BfoRNbehCbc2hcd/hbW9gKQs2k2iCUYDoPINuDMEYrcZeRYJKwYBBAHaRw8BAQdA
IF2e85PlS4PWquE3NLE59A1tr9KPxEznQ0EjG/DfefiI1QQYFgoAfQUCYrcZeQKe
AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3GXkACgkQUqp0e41/iO+P
swD+I+dgdOrj/jflr3D0Th8cugj9zFzwFYn9aaGmiee5wv8A/RvvJoiwHMTi3QPv
R4zGdF5pibOccbWr/dggFjKXTmoFAAoJEPXx47U2jWynVbEBANEjxzV8KnujGor7
9Mc1ag9wxyDgihXRowsoKEnSkyHQAQD6n0Pf1q8fl7BxbreVeY+0HN2swKlO9TvO
303a3X6/BQ==
=CESR
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,27 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: A688 ECB2 69B3 8161 DA4D 738F F5F1 E3B5 368D 6CA7
Comment: Foo Bank Admin <admin@foobank.com>
lIYEYrcZeRYJKwYBBAHaRw8BAQdABkxvWXwHDD6B3hXYQehQkil3e3Gcg5mNbOdS
9DSeny3+CQMCmWVHhM7xa9pgkX1Z8T7FcGWpMD91IgrF+7Bh97UtTFw8a71ixXww
pH/86zqEE9mry/95KvxsiCSPYwKhS0j4S20mZfQriWvpM3gCZSbhMrQiRm9vIEJh
bmsgQWRtaW4gPGFkbWluQGZvb2JhbmsuY29tPoiPBBMWCgBBBQJitxl5CRD18eO1
No1spxYhBKaI7LJps4Fh2k1zj/Xx47U2jWynAp4BApsBBRYCAwEABAsJCAcFFQoJ
CAsCmQEAAE8bAQC/RNYMyQlXpCIiCbxXhv24gTMylCS7hQwUEe8wpeHRbwD/Wmhw
kEb4r2yufw0mU8G6Xt26g1uTPMcA2TK0VUQE6ASciwRitxl5EgorBgEEAZdVAQUB
AQdAN3V+o08XbuP5nwE+1vKhTqynrdRB8GEmtQo0WOlY1msDAQgH/gkDApllR4TO
8WvaYNfxPfMWsBQLze2hTDxhCds/kO7WSwJw0cN3IzGlCrv+KSId5n6Rfz4NtlWN
yBOIecPZbzK4H0RSIc7jqiNRMqznpclwNYGIdQQYFgoAHQUCYrcZeQKeAQKbDAUW
AgMBAAQLCQgHBRUKCQgLAAoJEPXx47U2jWynZjcBAM00ACfzC+abshh0fcwm7gKJ
wwdJSqF5SXll+2KbC0roAP9zOGIM6QX6ETW3oQm3NoXHf4W1vYCkLNpNoglGA6Dy
DZyGBGK3GXkWCSsGAQQB2kcPAQEHQCBdnvOT5UuD1qrhNzSxOfQNba/Sj8RM50NB
Ixvw33n4/gkDApllR4TO8WvaYFyjXSJ54rtghVxVYEUd4OFWaGmKO5+KMEgqCqv4
TWhfaqT+wV5hKJbx/IkgZFS9viFTK7BEEhaApslO/TuE6nFSoi7gVw+I1QQYFgoA
fQUCYrcZeQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3GXkACgkQ
Uqp0e41/iO+PswD+I+dgdOrj/jflr3D0Th8cugj9zFzwFYn9aaGmiee5wv8A/Rvv
JoiwHMTi3QPvR4zGdF5pibOccbWr/dggFjKXTmoFAAoJEPXx47U2jWynVbEBANEj
xzV8KnujGor79Mc1ag9wxyDgihXRowsoKEnSkyHQAQD6n0Pf1q8fl7BxbreVeY+0
HN2swKlO9TvO303a3X6/BQ==
=4xhi
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,23 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 3145 68FD 089E 812B CB45 1E2B A149 06B7 5730 8EF0
Comment: Foo Bank CA <ca@foobank.com>
Comment: Freshly generated cert
mDMEYrcXbxYJKwYBBAHaRw8BAQdAPIIGHwbBzEIExd70jtw7uDvOymt0nU83uLqR
4Og+cxC0HEZvbyBCYW5rIENBIDxjYUBmb29iYW5rLmNvbT6IjwQTFgoAQQUCYrcX
bwkQoUkGt1cwjvAWIQQxRWj9CJ6BK8tFHiuhSQa3VzCO8AKeAQKbAQUWAgMBAAQL
CQgHBRUKCQgLApkBAAD+HwEAzElDjL9DRUnWd4yKjNoB+yT6RAQ9kD6BgRs/vKyQ
tqcBANfIysKFneE0B4lExhh6hB2cGoMR/YMhJHSd9zorxH4KuDgEYrcXbxIKKwYB
BAGXVQEFAQEHQMmhBx59elwIbvvv3UOCLZn2VJDEgkOGq1DOpYRNXe8FAwEIB4h1
BBgWCgAdBQJitxdvAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQoUkGt1cwjvDe
BgEA9rsAaBuE5QWoZSH6FxHGpq5XY1rFpmvFKn7ghZ33p6ABAJZJx0RQZGDaI5f4
6Pjph2U7pvLm18mXNd5cYPQtcFoFuDMEYrcXcBYJKwYBBAHaRw8BAQdAwdROT7La
zWiJsEWF/bXjcAybl7TE3avv7GTVUT00MK2I1QQYFgoAfQUCYrcXcAKeAQKbAgUW
AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3F3AACgkQKvTwayMtR7nA+wEAzKlK
eGvOSUtp5R07BTmOchkP0T3sZ0L4MvBBtpAuU5YA/jNm1162I/ceEEtKgyKp0wC5
pqYR0b+AWgB0tPaMfaoJAAoJEKFJBrdXMI7w608A/0nV1NXH/U0pRzmNNeRJWAMl
FXUdxhhYSlqrqYf2GQRlAP9NR9iMwpA0gFP+Uey3bCNC8MxtZLKhPkz2Rz4pnnfa
DA==
=5bcR
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,28 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: 3145 68FD 089E 812B CB45 1E2B A149 06B7 5730 8EF0
Comment: Foo Bank CA <ca@foobank.com>
Comment: Freshly generated key
lIYEYrcXbxYJKwYBBAHaRw8BAQdAPIIGHwbBzEIExd70jtw7uDvOymt0nU83uLqR
4Og+cxD+CQMCgCIAcSyeuXJgGMgKUCcU6tDwfGXgIVy0wCoDsSX2qWTKkyOYf7fu
Kw0BmQ4bFhQtC9hpPDhCKuR0cLUV8f0TEU0/Vq4n5hNy2erDW60KFLQcRm9vIEJh
bmsgQ0EgPGNhQGZvb2JhbmsuY29tPoiPBBMWCgBBBQJitxdvCRChSQa3VzCO8BYh
BDFFaP0InoEry0UeK6FJBrdXMI7wAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEA
AP4fAQDMSUOMv0NFSdZ3jIqM2gH7JPpEBD2QPoGBGz+8rJC2pwEA18jKwoWd4TQH
iUTGGHqEHZwagxH9gyEkdJ33OivEfgqciwRitxdvEgorBgEEAZdVAQUBAQdAyaEH
Hn16XAhu++/dQ4ItmfZUkMSCQ4arUM6lhE1d7wUDAQgH/gkDAoAiAHEsnrlyYPCE
d+xi8TsvxtcWq7lO+wwMxg/et4nFijmCDwK5/1duRutt/UYj1PU1cicfJ9choM6l
5qui74PBr5yTkUvNhAOjWfH60heIdQQYFgoAHQUCYrcXbwKeAQKbDAUWAgMBAAQL
CQgHBRUKCQgLAAoJEKFJBrdXMI7w3gYBAPa7AGgbhOUFqGUh+hcRxqauV2NaxaZr
xSp+4IWd96egAQCWScdEUGRg2iOX+Oj46YdlO6by5tfJlzXeXGD0LXBaBZyGBGK3
F3AWCSsGAQQB2kcPAQEHQMHUTk+y2s1oibBFhf2143AMm5e0xN2r7+xk1VE9NDCt
/gkDAoAiAHEsnrlyYFpx33LyuL4N/uccem+lZPCwjSum+Turs6vZQNY3toahx0fm
i68BGm1Eu716NN/5AV3nXXa9jmf6PsesiEvfWWITo8A7/fuI1QQYFgoAfQUCYrcX
cAKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3F3AACgkQKvTwayMt
R7nA+wEAzKlKeGvOSUtp5R07BTmOchkP0T3sZ0L4MvBBtpAuU5YA/jNm1162I/ce
EEtKgyKp0wC5pqYR0b+AWgB0tPaMfaoJAAoJEKFJBrdXMI7w608A/0nV1NXH/U0p
RzmNNeRJWAMlFXUdxhhYSlqrqYf2GQRlAP9NR9iMwpA0gFP+Uey3bCNC8MxtZLKh
Pkz2Rz4pnnfaDA==
=roYK
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 2C4C CA4C 8917 7FD1 91EE A3E0 F8BB 6510 168F D2BF
Comment: Customer <customer@example.com>
mDMEYrcaYxYJKwYBBAHaRw8BAQdAIt6xhlPPq2wTnLO2CqMvoP2yk68NBSPJWkse
85lCWee0H0N1c3RvbWVyIDxjdXN0b21lckBleGFtcGxlLmNvbT6IjwQTFgoAQQUC
YrcaYwkQ+LtlEBaP0r8WIQQsTMpMiRd/0ZHuo+D4u2UQFo/SvwKeAQKbAQUWAgMB
AAQLCQgHBRUKCQgLApkBAAB3agEAmnfM6RToSpLfBFXOERCAz0v9FYfskTz7bsGM
CPSEgYcBAK85o67OTq5y8JhHHFfHvHeIIaE/IAzJLQHRNT3pvJ8KuDgEYrcaYxIK
KwYBBAGXVQEFAQEHQGZVHqBfmrVSMbIFQUSE9bnj9JU/W/JUFuPHbd5csPpdAwEI
B4h1BBgWCgAdBQJitxpjAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQ+LtlEBaP
0r/XhAD+LHlbWcUil8HmnWpBSwcv3wBP1vnt4zPOlm1pkfmgq/MA/3hb+I0kQNMR
7ux3bYORuLiHulapQtYQG5GGRfbvJqMPuDMEYrcaYxYJKwYBBAHaRw8BAQdAy3Jc
mVMbgm6VT88y2xNCFrePTBXaroz90pI9mWBjvMGI1QQYFgoAfQUCYrcaYwKeAQKb
AgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3GmMACgkQZTy2QngzKnRNbwEA
pyuSMaZgI+kOaiFiIxCGXoR/zhYKKMyoq5JlF1W+cwQBALwz2ixIlP3jzT7wJnDf
hcK7n/SMlXD4MIqR2CQkxRYHAAoJEPi7ZRAWj9K/Cb0A/24XLgqc5mj+MT0gyXRU
GBOpoTlpZ86bpb4LcLycRU+0AQDuAq/i1n6e/noMRsbOh0WYZbqAbOgiv9DVJcRQ
4kfpAg==
=r4Bk
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,24 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: 2C4C CA4C 8917 7FD1 91EE A3E0 F8BB 6510 168F D2BF
Comment: Customer <customer@example.com>
lFgEYrcaYxYJKwYBBAHaRw8BAQdAIt6xhlPPq2wTnLO2CqMvoP2yk68NBSPJWkse
85lCWecAAP9xVDxOuRZ5pDo5cSKIJf50dMNp/I/xHkNDHPNcI+ManQ9HtB9DdXN0
b21lciA8Y3VzdG9tZXJAZXhhbXBsZS5jb20+iI8EExYKAEEFAmK3GmMJEPi7ZRAW
j9K/FiEELEzKTIkXf9GR7qPg+LtlEBaP0r8CngECmwEFFgIDAQAECwkIBwUVCgkI
CwKZAQAAd2oBAJp3zOkU6EqS3wRVzhEQgM9L/RWH7JE8+27BjAj0hIGHAQCvOaOu
zk6ucvCYRxxXx7x3iCGhPyAMyS0B0TU96byfCpxdBGK3GmMSCisGAQQBl1UBBQEB
B0BmVR6gX5q1UjGyBUFEhPW54/SVP1vyVBbjx23eXLD6XQMBCAcAAP9iOym80xxC
El9TTF/k7mfRBjNocGBMMUP5nnkPwdag2A+KiHUEGBYKAB0FAmK3GmMCngECmwwF
FgIDAQAECwkIBwUVCgkICwAKCRD4u2UQFo/Sv9eEAP4seVtZxSKXweadakFLBy/f
AE/W+e3jM86WbWmR+aCr8wD/eFv4jSRA0xHu7Hdtg5G4uIe6VqlC1hAbkYZF9u8m
ow+cWARitxpjFgkrBgEEAdpHDwEBB0DLclyZUxuCbpVPzzLbE0IWt49MFdqujP3S
kj2ZYGO8wQABAPY7EMCZEq/sSDkldwv/43GUe4KuDay7Q6jHugO+YkkqD3iI1QQY
FgoAfQUCYrcaYwKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3GmMA
CgkQZTy2QngzKnRNbwEApyuSMaZgI+kOaiFiIxCGXoR/zhYKKMyoq5JlF1W+cwQB
ALwz2ixIlP3jzT7wJnDfhcK7n/SMlXD4MIqR2CQkxRYHAAoJEPi7ZRAWj9K/Cb0A
/24XLgqc5mj+MT0gyXRUGBOpoTlpZ86bpb4LcLycRU+0AQDuAq/i1n6e/noMRsbO
h0WYZbqAbOgiv9DVJcRQ4kfpAg==
=th8Q
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,22 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: PGPainless
Comment: 3B24 FD39 4732 D6A9 CF6E 071E 21AD CF3B 9F33 49FE
Comment: Foo Bank Employee <employee@foobank.com>
mDMEYrcZeRYJKwYBBAHaRw8BAQdAl9j10kv/7NRAZe+yATf7kkXOoHwkxaKkHzM3
wMihWlu0KEZvbyBCYW5rIEVtcGxveWVlIDxlbXBsb3llZUBmb29iYW5rLmNvbT6I
jwQTFgoAQQUCYrcZeQkQIa3PO58zSf4WIQQ7JP05RzLWqc9uBx4hrc87nzNJ/gKe
AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAAA1aAEAthiiVUcYiKVyzfzbrZLkQlTW
NR6fu85fR5rPns2NUdwA/2vkrneDmkE04osRwUGLMh30iQzduQ3aFW0hEe2OmDUO
uDgEYrcZeRIKKwYBBAGXVQEFAQEHQCswfL5xK4bFJ1lJwadyjHMH1kuKjFNGVHky
p38zas0YAwEIB4h1BBgWCgAdBQJitxl5Ap4BApsMBRYCAwEABAsJCAcFFQoJCAsA
CgkQIa3PO58zSf68OgD8DencsxhJRy8Qtj0B/ecLr/jvWvkHcwOdRFKF1061xHIA
/3onWLkTEqcL4u3Dfr9C3LF/Ien0+v0XDoFf/OrgQWgKuDMEYrcZeRYJKwYBBAHa
Rw8BAQdA+VUn22iyOQNjzXMgqWXnYaHTSuGFdjWMUTHHbjySSuOI1QQYFgoAfQUC
YrcZeQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3GXkACgkQnGOe
WTe0K9zF6wEAh/nFBiBEV8N5CWvre6CU0Ky85cxrwE0vOA5tPaefwusBAL6yqhbJ
wtEV0WqDb0Tg6OFl3eJG8VYOgh+zrt33LAEOAAoJECGtzzufM0n+NYcA/jOzaTUy
1XZ45yNTN23xx8yyIdwPb24my/o7rbyCpV2qAP929vks8rEU+d+FIY6KnSrZRKts
FkuC6kGOw25VW7YiAw==
=/hVO
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,27 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: PGPainless
Comment: 3B24 FD39 4732 D6A9 CF6E 071E 21AD CF3B 9F33 49FE
Comment: Foo Bank Employee <employee@foobank.com>
lIYEYrcZeRYJKwYBBAHaRw8BAQdAl9j10kv/7NRAZe+yATf7kkXOoHwkxaKkHzM3
wMihWlv+CQMCMzeQucBpShVg5GroEDGhC62uvl+Rjki+P6aR5rCKbaoff1Y3DEGQ
LEz0InfGo4I2lLUhox1E+th6BHT+fwg7E/Qk+9j040TvEXAqHIJhXbQoRm9vIEJh
bmsgRW1wbG95ZWUgPGVtcGxveWVlQGZvb2JhbmsuY29tPoiPBBMWCgBBBQJitxl5
CRAhrc87nzNJ/hYhBDsk/TlHMtapz24HHiGtzzufM0n+Ap4BApsBBRYCAwEABAsJ
CAcFFQoJCAsCmQEAADVoAQC2GKJVRxiIpXLN/NutkuRCVNY1Hp+7zl9Hms+ezY1R
3AD/a+Sud4OaQTTiixHBQYsyHfSJDN25DdoVbSER7Y6YNQ6ciwRitxl5EgorBgEE
AZdVAQUBAQdAKzB8vnErhsUnWUnBp3KMcwfWS4qMU0ZUeTKnfzNqzRgDAQgH/gkD
AjM3kLnAaUoVYI0D8gPzG1t3dPpkNgc411bts2T9BgrZPkV7C3u338TXsFSHxTdo
vK7/l/PhikN0qKjKVG5PXnxHhu56RUYiF+XAhkjcI9qIdQQYFgoAHQUCYrcZeQKe
AQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJECGtzzufM0n+vDoA/A3p3LMYSUcvELY9
Af3nC6/471r5B3MDnURShddOtcRyAP96J1i5ExKnC+Ltw36/QtyxfyHp9Pr9Fw6B
X/zq4EFoCpyGBGK3GXkWCSsGAQQB2kcPAQEHQPlVJ9tosjkDY81zIKll52Gh00rh
hXY1jFExx248kkrj/gkDAjM3kLnAaUoVYFXrAVkSWsFeXVYLYtq0a49sc5q7Kz1K
KJmDBZKnvwgipPqf1eAkcZDSNanBZ2BO2K77Ix5FDFp67qc3XIZ6HUZqrP1siQmI
1QQYFgoAfQUCYrcZeQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmK3
GXkACgkQnGOeWTe0K9zF6wEAh/nFBiBEV8N5CWvre6CU0Ky85cxrwE0vOA5tPaef
wusBAL6yqhbJwtEV0WqDb0Tg6OFl3eJG8VYOgh+zrt33LAEOAAoJECGtzzufM0n+
NYcA/jOzaTUy1XZ45yNTN23xx8yyIdwPb24my/o7rbyCpV2qAP929vks8rEU+d+F
IY6KnSrZRKtsFkuC6kGOw25VW7YiAw==
=kvmL
-----END PGP PRIVATE KEY BLOCK-----