mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 12:27:58 +01:00
Port wot-dijkstra Java classes to Kotlin
This commit is contained in:
parent
31aeb18535
commit
9d3a9893f2
21 changed files with 395 additions and 824 deletions
|
@ -42,7 +42,7 @@ allprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
// For library modules, enable android api compatibility check
|
// For library modules, enable android api compatibility check
|
||||||
if (it.name != 'pgpainless-cli') {
|
if (it.name != 'pgpainless-cli' && it.name != 'wot-dijkstra') {
|
||||||
// animalsniffer
|
// animalsniffer
|
||||||
apply plugin: 'ru.vyarus.animalsniffer'
|
apply plugin: 'ru.vyarus.animalsniffer'
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
import org.pgpainless.wot.dijkstra.sq.CertSynopsis;
|
import org.pgpainless.wot.dijkstra.sq.CertSynopsis;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Certification;
|
import org.pgpainless.wot.dijkstra.sq.Certification;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Depth;
|
import org.pgpainless.wot.dijkstra.sq.Depth;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Optional;
|
|
||||||
import org.pgpainless.wot.dijkstra.sq.RegexSet;
|
import org.pgpainless.wot.dijkstra.sq.RegexSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +33,7 @@ public class CertificationFactory {
|
||||||
public static Certification fromDelegation(CertSynopsis issuer,
|
public static Certification fromDelegation(CertSynopsis issuer,
|
||||||
CertSynopsis target,
|
CertSynopsis target,
|
||||||
PGPSignature signature) {
|
PGPSignature signature) {
|
||||||
return fromSignature(issuer, Optional.empty(), target, signature);
|
return fromSignature(issuer, null, target, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +49,7 @@ public class CertificationFactory {
|
||||||
String targetUserId,
|
String targetUserId,
|
||||||
CertSynopsis target,
|
CertSynopsis target,
|
||||||
PGPSignature signature) {
|
PGPSignature signature) {
|
||||||
return fromSignature(issuer, Optional.just(targetUserId), target, signature);
|
return fromSignature(issuer, targetUserId, target, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +62,7 @@ public class CertificationFactory {
|
||||||
* @return certification
|
* @return certification
|
||||||
*/
|
*/
|
||||||
public static Certification fromSignature(CertSynopsis issuer,
|
public static Certification fromSignature(CertSynopsis issuer,
|
||||||
Optional<String> targetUserId,
|
String targetUserId,
|
||||||
CertSynopsis target,
|
CertSynopsis target,
|
||||||
PGPSignature signature) {
|
PGPSignature signature) {
|
||||||
return new Certification(
|
return new Certification(
|
||||||
|
@ -71,7 +70,7 @@ public class CertificationFactory {
|
||||||
target,
|
target,
|
||||||
targetUserId,
|
targetUserId,
|
||||||
SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime(),
|
SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime(),
|
||||||
Optional.maybe(SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature)),
|
SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature),
|
||||||
SignatureSubpacketsUtil.isExportable(signature),
|
SignatureSubpacketsUtil.isExportable(signature),
|
||||||
getTrustAmountFrom(signature),
|
getTrustAmountFrom(signature),
|
||||||
getTrustDepthFrom(signature),
|
getTrustDepthFrom(signature),
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.pgpainless.wot.dijkstra.sq.CertSynopsis;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Certification;
|
import org.pgpainless.wot.dijkstra.sq.Certification;
|
||||||
import org.pgpainless.wot.dijkstra.sq.CertificationSet;
|
import org.pgpainless.wot.dijkstra.sq.CertificationSet;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Network;
|
import org.pgpainless.wot.dijkstra.sq.Network;
|
||||||
import org.pgpainless.wot.dijkstra.sq.Optional;
|
|
||||||
import org.pgpainless.wot.dijkstra.sq.ReferenceTime;
|
import org.pgpainless.wot.dijkstra.sq.ReferenceTime;
|
||||||
import org.pgpainless.wot.sugar.IterableIterator;
|
import org.pgpainless.wot.sugar.IterableIterator;
|
||||||
import org.pgpainless.wot.sugar.PrefixedIterator;
|
import org.pgpainless.wot.sugar.PrefixedIterator;
|
||||||
|
@ -80,7 +79,7 @@ public class WebOfTrust implements CertificateAuthority {
|
||||||
Iterator<Certificate> certificates = certificateStore.items();
|
Iterator<Certificate> certificates = certificateStore.items();
|
||||||
Iterator<Certificate> withTrustRoot = new PrefixedIterator<>(trustRoot, certificates);
|
Iterator<Certificate> withTrustRoot = new PrefixedIterator<>(trustRoot, certificates);
|
||||||
IterableIterator<Certificate> iterable = new IterableIterator<>(withTrustRoot);
|
IterableIterator<Certificate> iterable = new IterableIterator<>(withTrustRoot);
|
||||||
network = fromCertificates(iterable, PGPainless.getPolicy(), Optional.just(ReferenceTime.now()));
|
network = fromCertificates(iterable, PGPainless.getPolicy(), ReferenceTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,9 +94,9 @@ public class WebOfTrust implements CertificateAuthority {
|
||||||
public static Network fromCertificates(
|
public static Network fromCertificates(
|
||||||
Iterable<Certificate> certificates,
|
Iterable<Certificate> certificates,
|
||||||
Policy policy,
|
Policy policy,
|
||||||
Optional<ReferenceTime> optReferenceTime) {
|
ReferenceTime optReferenceTime) {
|
||||||
|
|
||||||
ReferenceTime referenceTime = optReferenceTime.isPresent() ? optReferenceTime.get() : ReferenceTime.now();
|
ReferenceTime referenceTime = optReferenceTime == null ? ReferenceTime.now() : optReferenceTime;
|
||||||
List<KeyRingInfo> validCerts = parseValidCertificates(certificates, policy, referenceTime.getTimestamp());
|
List<KeyRingInfo> validCerts = parseValidCertificates(certificates, policy, referenceTime.getTimestamp());
|
||||||
|
|
||||||
LOGGER.debug("Successfully parsed " + validCerts.size() + " certificates.");
|
LOGGER.debug("Successfully parsed " + validCerts.size() + " certificates.");
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra;
|
|
||||||
|
|
||||||
public final class IntegerUtils {
|
|
||||||
|
|
||||||
private IntegerUtils() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backported method from Java 8.
|
|
||||||
*
|
|
||||||
* @param x x
|
|
||||||
* @param y y
|
|
||||||
* @return result of comparison
|
|
||||||
*/
|
|
||||||
public static int compare(int x, int y) {
|
|
||||||
// noinspection UseCompareMethod
|
|
||||||
return x < y ? -1 : (x == y ? 0 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(issuer.getFingerprint()).append((issuer.getUserIds().isEmpty() ? " " : " (" + issuer.getUserIds().keySet().iterator().next() + ") "));
|
|
||||||
sb.append(userId.isPresent() ? "certifies" : "delegates to").append(userId.isPresent() ? " [" + userId.get() + "] " : " ").append(target.getFingerprint())
|
|
||||||
.append(userId.isEmpty() && !target.getUserIds().isEmpty() ? " (" + target.getUserIds().keySet().iterator().next() + ")" : "");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link CertificationSet} is a set of {@link Certification Certifications} made by the same issuer, on the same
|
|
||||||
* target certificate.
|
|
||||||
* In some sense, a {@link CertificationSet} can be considered an edge in the web of trust.
|
|
||||||
*/
|
|
||||||
public final 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<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CertificationSet fromCertification(Certification certification) {
|
|
||||||
CertificationSet set = CertificationSet.empty(certification.getIssuer(), certification.getTarget());
|
|
||||||
set.add(certification);
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CertificationSet(CertSynopsis issuer,
|
|
||||||
CertSynopsis target,
|
|
||||||
Map<Optional<String>, List<Certification>> certifications) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.target = target;
|
|
||||||
this.certifications = new HashMap<>(certifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CertSynopsis getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CertSynopsis getTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Optional<String>, List<Certification>> getCertifications() {
|
|
||||||
// Copy to avoid side effects
|
|
||||||
Map<Optional<String>, List<Certification>> copy = new HashMap<>();
|
|
||||||
for (Optional<String> key : certifications.keySet()) {
|
|
||||||
copy.put(key, new ArrayList<>(certifications.get(key)));
|
|
||||||
}
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (Map.Entry<Optional<String>, List<Certification>> entry : certifications.entrySet()) {
|
|
||||||
for (Certification certification : entry.getValue()) {
|
|
||||||
sb.append(certification).append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq;
|
|
||||||
|
|
||||||
import org.pgpainless.wot.dijkstra.IntegerUtils;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
public final 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 IntegerUtils.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A network consists of nodes, and edges between them.
|
|
||||||
* For the Web of Trust, nodes consist of {@link CertSynopsis CertSynopses}, while the edges between the nodes are
|
|
||||||
* {@link CertificationSet CertificationSets}.
|
|
||||||
* Edges can hereby be accessed in two ways:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #getEdges()} returns a {@link Map} keyed by the {@link OpenPgpFingerprint fingerprint} of an issuer,
|
|
||||||
* whose values are {@link List Lists} containing all edges originating from the issuer.</li>
|
|
||||||
* <li>{@link #getReverseEdges()} on the other hand returns a {@link Map} keyed by the
|
|
||||||
* {@link OpenPgpFingerprint fingerprint} of a target, whose value are {@link List Lists} containing all edges
|
|
||||||
* pointing to the target.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@link Network} from a set of nodes, edges, reversed edges and a reference time.
|
|
||||||
*
|
|
||||||
* @param nodes map containing all nodes of the network, keyed by their fingerprints
|
|
||||||
* @param edges map containing all edges of the network, keyed by the fingerprint of the issuer
|
|
||||||
* @param reverseEdges map containing all reversed edges of the network, keyed by the fingerprint of the target
|
|
||||||
* @param referenceTime reference time
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all nodes ({@link CertSynopsis}) of the {@link Network}, indexed by their
|
|
||||||
* {@link OpenPgpFingerprint fingerprints}.
|
|
||||||
*
|
|
||||||
* @return nodes of the network
|
|
||||||
*/
|
|
||||||
public Map<OpenPgpFingerprint, CertSynopsis> getNodes() {
|
|
||||||
return new HashMap<>(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all edges of the {@link Network}, indexed by the {@link OpenPgpFingerprint fingerprint} of the issuer.
|
|
||||||
* An edge consists of a {@link CertificationSet} containing all signatures made by the issuer on the target.
|
|
||||||
* TODO: Do we care about immutability, or can we ignore that issue since Network is only used by
|
|
||||||
* WebOfTrust anyways?
|
|
||||||
*
|
|
||||||
* @return map of edges
|
|
||||||
*/
|
|
||||||
public Map<OpenPgpFingerprint, List<CertificationSet>> getEdges() {
|
|
||||||
return new HashMap<>(edges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all reversed edges of the {@link Network}, indexed by the {@link OpenPgpFingerprint fingerprint} of the target.
|
|
||||||
* TODO: Do we care about immutability, or can we ignore that issue since Network is only used by
|
|
||||||
* WebOfTrust anyways?
|
|
||||||
*
|
|
||||||
* @return map of reversed edges
|
|
||||||
*/
|
|
||||||
public Map<OpenPgpFingerprint, List<CertificationSet>> getReverseEdges() {
|
|
||||||
return new HashMap<>(reverseEdges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the total number of edges on the network.
|
|
||||||
*
|
|
||||||
* @return number of edges
|
|
||||||
*/
|
|
||||||
public int getNumberOfEdges() {
|
|
||||||
int num = 0;
|
|
||||||
for (List<CertificationSet> outEdges : edges.values()) {
|
|
||||||
num += outEdges.size();
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the total number of signatures the network comprises.
|
|
||||||
*
|
|
||||||
* @return number of signatures
|
|
||||||
*/
|
|
||||||
public int getNumberOfSignatures() {
|
|
||||||
int num = 0;
|
|
||||||
for (List<CertificationSet> edgesPerIssuer : edges.values()) {
|
|
||||||
for (CertificationSet edge : edgesPerIssuer) {
|
|
||||||
for (List<Certification> sigsPerDatum : edge.getCertifications().values()) {
|
|
||||||
num += sigsPerDatum.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link ReferenceTime} which was used when creating the {@link Network}.
|
|
||||||
*
|
|
||||||
* @return reference time
|
|
||||||
*/
|
|
||||||
public ReferenceTime getReferenceTime() {
|
|
||||||
return referenceTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
int edgeNum = 0;
|
|
||||||
for (List<CertificationSet> edgesFrom : edges.values()) {
|
|
||||||
edgeNum += edgesFrom.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder("Network with " + getNodes().size() + " nodes, " + edgeNum + " edges:\n");
|
|
||||||
for (OpenPgpFingerprint issuer : getNodes().keySet()) {
|
|
||||||
List<CertificationSet> edges = getEdges().get(issuer);
|
|
||||||
if (edges == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (CertificationSet edge : edges) {
|
|
||||||
sb.append(edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public final 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public final class RegexSet {
|
|
||||||
|
|
||||||
private final Set<String> regexStrings;
|
|
||||||
|
|
||||||
private RegexSet(Set<String> regexStrings) {
|
|
||||||
this.regexStrings = regexStrings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegexSet fromExpressionList(@Nonnull List<String> regexList) {
|
|
||||||
Set<String> regexStringSet = new HashSet<>(regexList);
|
|
||||||
return new RegexSet(regexStringSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegexSet fromExpression(@Nonnull String regex) {
|
|
||||||
return fromExpressionList(Collections.singletonList(regex));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegexSet wildcard() {
|
|
||||||
return fromExpressionList(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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package org.pgpainless.wot.dijkstra.sq
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
import org.pgpainless.algorithm.RevocationState
|
import org.pgpainless.algorithm.RevocationState
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class Certification(
|
||||||
|
val issuer: CertSynopsis,
|
||||||
|
val target: CertSynopsis,
|
||||||
|
val userId: String?,
|
||||||
|
val creationTime: Date,
|
||||||
|
val expirationTime: Date?,
|
||||||
|
val exportable: Boolean,
|
||||||
|
val trustAmount: Int,
|
||||||
|
val trustDepth: Depth,
|
||||||
|
val regex: RegexSet
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
issuer: CertSynopsis,
|
||||||
|
targetUserId: String?,
|
||||||
|
target: CertSynopsis,
|
||||||
|
creationTime: Date) :
|
||||||
|
this(issuer, target, targetUserId, creationTime, null, true, 120, Depth.limited(0), RegexSet.wildcard())
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val relation = if (userId != null) {
|
||||||
|
"certifies"
|
||||||
|
} else {
|
||||||
|
"delegates to"
|
||||||
|
}
|
||||||
|
val relationTarget = if (userId != null) {
|
||||||
|
"[$userId] ${target.fingerprint}"
|
||||||
|
} else {
|
||||||
|
"$target"
|
||||||
|
}
|
||||||
|
return "$issuer $relation $relationTarget"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
data class CertificationSet(
|
||||||
|
val issuer: CertSynopsis,
|
||||||
|
val target: CertSynopsis,
|
||||||
|
val certifications: MutableMap<String?, MutableList<Certification>>) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun empty(issuer: CertSynopsis, target: CertSynopsis): CertificationSet {
|
||||||
|
return CertificationSet(issuer, target, HashMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromCertification(certification: Certification) : CertificationSet {
|
||||||
|
val set = empty(certification.issuer, certification.target)
|
||||||
|
set.add(certification)
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun merge(other: CertificationSet) {
|
||||||
|
if (other == this) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require(issuer.fingerprint == other.issuer.fingerprint) { "Issuer fingerprint mismatch." }
|
||||||
|
require(target.fingerprint == other.target.fingerprint) { "Target fingerprint mismatch." }
|
||||||
|
|
||||||
|
for (userId in other.certifications.keys) {
|
||||||
|
for (certification in other.certifications[userId]!!) {
|
||||||
|
add(certification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(certification : Certification) {
|
||||||
|
require(issuer.fingerprint == certification.issuer.fingerprint) { "Issuer fingerprint mismatch." }
|
||||||
|
require(target.fingerprint == certification.target.fingerprint) { "Target fingerprint mismatch." }
|
||||||
|
|
||||||
|
var certificationsForUserId: MutableList<Certification>? = certifications[certification.userId]
|
||||||
|
if (certificationsForUserId == null) {
|
||||||
|
certificationsForUserId = ArrayList()
|
||||||
|
certifications[certification.userId] = certificationsForUserId
|
||||||
|
}
|
||||||
|
certificationsForUserId.add(certification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$certifications"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
class Depth(val limit: Int?) : Comparable<Depth> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun unconstrained() : Depth {
|
||||||
|
return Depth(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun limited(limit: Int): Depth {
|
||||||
|
require(limit in 0..255) {
|
||||||
|
"Trust depth MUST be a value between 0 and 255."
|
||||||
|
}
|
||||||
|
return Depth(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun auto(limit: Int): Depth {
|
||||||
|
return if (limit == 255) {
|
||||||
|
unconstrained()
|
||||||
|
} else {
|
||||||
|
limited(limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUnconstrained() : Boolean {
|
||||||
|
return limit == null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decrease(value : Int) : Depth {
|
||||||
|
return if (isUnconstrained()) {
|
||||||
|
unconstrained()
|
||||||
|
} else {
|
||||||
|
if (limit!! >= value) {
|
||||||
|
limited(limit - value)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Depth cannot be decreased.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun min(other: Depth) : Depth {
|
||||||
|
return if (compareTo(other) <= 0) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(o: Depth): Int {
|
||||||
|
return if (isUnconstrained()) {
|
||||||
|
if (o.isUnconstrained()) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (o.isUnconstrained()) {
|
||||||
|
-1
|
||||||
|
} else {
|
||||||
|
limit!!.compareTo(o.limit!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() : String {
|
||||||
|
return if (isUnconstrained()) { "unconstrained" } else { limit!!.toString() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A network consists of nodes, and edges between them.
|
||||||
|
* For the Web of Trust, nodes consist of [CertSynopses][CertSynopsis], while the edges between the nodes are
|
||||||
|
* [CertificationSets][CertificationSet].
|
||||||
|
*
|
||||||
|
* @constructor creates a new network
|
||||||
|
* @param nodes contains a [Map] of [CertSynopsis] keyed by their [OpenPgpFingerprint]
|
||||||
|
* @param edges [Map] keyed by the [fingerprint][OpenPgpFingerprint] of an issuer, whose values are [Lists][List] containing all edges originating from the issuer.
|
||||||
|
* @param reverseEdges [Map] keyed by the [fingerprint][OpenPgpFingerprint] of a target, whose values are [Lists][List] containing all edges pointing to the target
|
||||||
|
* @param referenceTime reference time at which the [Network] was built
|
||||||
|
*/
|
||||||
|
class Network(
|
||||||
|
val nodes: Map<OpenPgpFingerprint, CertSynopsis>,
|
||||||
|
val edges: Map<OpenPgpFingerprint, List<CertificationSet>>,
|
||||||
|
val reverseEdges: Map<OpenPgpFingerprint, List<CertificationSet>>,
|
||||||
|
val referenceTime: ReferenceTime) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun empty(referenceTime: ReferenceTime): Network {
|
||||||
|
return Network(HashMap(), HashMap(), HashMap(), referenceTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val numberOfEdges: Int
|
||||||
|
get() {
|
||||||
|
return edges.values.sumOf { it.size }
|
||||||
|
}
|
||||||
|
|
||||||
|
val numberOfSignatures: Int
|
||||||
|
get() {
|
||||||
|
return edges.values
|
||||||
|
.flatten()
|
||||||
|
.flatMap { it.certifications.values }
|
||||||
|
.sumOf { it.size }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append("Network with ${nodes.size} nodes, $numberOfEdges edges:\n")
|
||||||
|
for (issuer in nodes.keys) {
|
||||||
|
val outEdges = edges[issuer] ?: continue
|
||||||
|
for (edge in outEdges) {
|
||||||
|
sb.append(edge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class Path(
|
||||||
|
val root: CertSynopsis,
|
||||||
|
val edges: MutableList<Certification>,
|
||||||
|
var residualDepth: Depth
|
||||||
|
) {
|
||||||
|
constructor(root: CertSynopsis) : this(
|
||||||
|
root, mutableListOf<Certification>(), Depth.unconstrained())
|
||||||
|
|
||||||
|
val target: CertSynopsis
|
||||||
|
get() {
|
||||||
|
return if (edges.isEmpty()) {
|
||||||
|
root
|
||||||
|
} else {
|
||||||
|
edges[edges.size - 1].target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val certificates: List<CertSynopsis>
|
||||||
|
get() {
|
||||||
|
val certs: MutableList<CertSynopsis> = ArrayList()
|
||||||
|
certs.add(root)
|
||||||
|
for (certification in edges) {
|
||||||
|
certs.add(certification.target)
|
||||||
|
}
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
val length: Int
|
||||||
|
get() = edges.size + 1
|
||||||
|
|
||||||
|
val certifications: List<Certification>
|
||||||
|
get() = ArrayList(edges)
|
||||||
|
|
||||||
|
val amount: Int
|
||||||
|
get() = if (edges.isEmpty()) {
|
||||||
|
120
|
||||||
|
} else {
|
||||||
|
var min = 255
|
||||||
|
for (edge in edges) {
|
||||||
|
min = min(min, edge.trustAmount)
|
||||||
|
}
|
||||||
|
min
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(certification: Certification) {
|
||||||
|
require(target.fingerprint == certification.issuer.fingerprint) {
|
||||||
|
"Cannot append certification to path: Path's tail is not issuer of the certification."
|
||||||
|
}
|
||||||
|
require(residualDepth.isUnconstrained() || residualDepth.limit!! > 0) {
|
||||||
|
"Not enough depth."
|
||||||
|
}
|
||||||
|
|
||||||
|
var cyclic = root.fingerprint == certification.target.fingerprint
|
||||||
|
for (i in 0..edges.size) {
|
||||||
|
val edge = edges[i]
|
||||||
|
if (cyclic) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (edge.target.fingerprint == certification.target.fingerprint) {
|
||||||
|
cyclic = if (i == edges.size - 1) {
|
||||||
|
edge.userId == certification.userId
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(!cyclic) { "Adding the certification to the path would create a cycle." }
|
||||||
|
|
||||||
|
residualDepth = certification.trustDepth.min(residualDepth.decrease(1))
|
||||||
|
edges.add(certification)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
class Paths(val paths: MutableList<Item>) {
|
||||||
|
|
||||||
|
fun add(path: Path, amount: Int) {
|
||||||
|
require(amount <= path.amount) {
|
||||||
|
"Amount too small. TODO: Better error message"
|
||||||
|
}
|
||||||
|
paths.add(Item(path, amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
val amount: Int
|
||||||
|
get() {
|
||||||
|
return paths.sumOf { it.amount }
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Item(val path: Path, val amount: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra.sq
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
data class RegexSet(val regexStrings: Set<String>) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun fromExpressionList(regexList: List<String>): RegexSet {
|
||||||
|
return RegexSet(regexList.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromExpression(regex: String): RegexSet {
|
||||||
|
return fromExpressionList(listOf(regex))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun wildcard(): RegexSet {
|
||||||
|
return fromExpressionList(listOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matches(string: String): Boolean {
|
||||||
|
if (regexStrings.isEmpty()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (regex in regexStrings) {
|
||||||
|
val matcher = Pattern.compile(regex).matcher(string)
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ -17,15 +19,15 @@ public class DepthTest {
|
||||||
public void testUnlimitedItem() {
|
public void testUnlimitedItem() {
|
||||||
Depth depth = Depth.unconstrained();
|
Depth depth = Depth.unconstrained();
|
||||||
assertTrue(depth.isUnconstrained());
|
assertTrue(depth.isUnconstrained());
|
||||||
assertFalse(depth.getLimit().isPresent());
|
assertNull(depth.getLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLimitedItem() {
|
public void testLimitedItem() {
|
||||||
Depth limited = Depth.limited(2);
|
Depth limited = Depth.limited(2);
|
||||||
assertFalse(limited.isUnconstrained());
|
assertFalse(limited.isUnconstrained());
|
||||||
assertTrue(limited.getLimit().isPresent());
|
assertNotNull(limited.getLimit());
|
||||||
assertEquals(2, limited.getLimit().get());
|
assertEquals(2, limited.getLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -40,7 +42,7 @@ public class DepthTest {
|
||||||
Depth limited = Depth.limited(1);
|
Depth limited = Depth.limited(1);
|
||||||
Depth decreased = limited.decrease(1);
|
Depth decreased = limited.decrease(1);
|
||||||
assertFalse(decreased.isUnconstrained());
|
assertFalse(decreased.isUnconstrained());
|
||||||
assertEquals(0, decreased.getLimit().get());
|
assertEquals(0, decreased.getLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -76,7 +78,7 @@ public class DepthTest {
|
||||||
public void testAutoLimited() {
|
public void testAutoLimited() {
|
||||||
Depth depth = Depth.auto(120);
|
Depth depth = Depth.auto(120);
|
||||||
assertFalse(depth.isUnconstrained());
|
assertFalse(depth.isUnconstrained());
|
||||||
assertEquals(120, depth.getLimit().get());
|
assertEquals(120, depth.getLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue