mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 04:17:59 +01:00
Algorithm port from Rust
This commit is contained in:
parent
e42e570911
commit
5b18a1b465
1 changed files with 639 additions and 0 deletions
|
@ -0,0 +1,639 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Heiko Schaefer <heiko@schaefer@name>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.wot.dijkstra
|
||||||
|
|
||||||
|
import org.pgpainless.wot.dijkstra.filter.*
|
||||||
|
import org.pgpainless.wot.network.*
|
||||||
|
import org.pgpainless.wot.query.Path
|
||||||
|
import org.pgpainless.wot.query.Paths
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
// The amount of trust needed for a binding to be fully trusted.
|
||||||
|
private const val FULLY_TRUSTED = 120
|
||||||
|
|
||||||
|
// The usual amount of trust assigned to a partially trusted
|
||||||
|
// introducer.
|
||||||
|
//
|
||||||
|
// Normally, three partially trusted introducers are needed to
|
||||||
|
// authenticate a binding. Thus, this is a third of `FULLY_TRUSTED`.
|
||||||
|
private const val PARTIALLY_TRUSTED = 40
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A path's cost.
|
||||||
|
*
|
||||||
|
* This is needed to do a Dijkstra.
|
||||||
|
*/
|
||||||
|
internal class Cost(
|
||||||
|
// The path's length (i.e., the number of hops to the target).
|
||||||
|
// *Less* is better (we prefer short paths).
|
||||||
|
val length: Int,
|
||||||
|
|
||||||
|
// The trust amount along this path.
|
||||||
|
// More is better (we prefer paths with a high trust amount).
|
||||||
|
val amount: Int,
|
||||||
|
) : Comparable<Cost> {
|
||||||
|
|
||||||
|
// "Greater than" means: the path is preferable, that is:
|
||||||
|
// - It requires a small number of hops (length)
|
||||||
|
// - It has a high "trust amount"
|
||||||
|
override fun compareTo(other: Cost) =
|
||||||
|
compareValuesBy(this, other, { -it.length }, { it.amount })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We perform a Dijkstra in reserve from the target towards the roots.
|
||||||
|
internal data class ForwardPointer(
|
||||||
|
// If null, then the target.
|
||||||
|
val next: EdgeComponent?
|
||||||
|
)
|
||||||
|
|
||||||
|
class Query(
|
||||||
|
private val network: Network,
|
||||||
|
private val roots: Roots,
|
||||||
|
private val certificationNetwork: Boolean) {
|
||||||
|
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger(Query::class.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates the specified binding.
|
||||||
|
*
|
||||||
|
* Enough independent paths are gotten to satisfy `target_trust_amount`.
|
||||||
|
*
|
||||||
|
* A fully trusted authentication is 120. If you require that a binding
|
||||||
|
* be double authenticated, you can specify 240.
|
||||||
|
*/
|
||||||
|
fun authenticate(targetFpr: Fingerprint,
|
||||||
|
targetUserid: String,
|
||||||
|
targetTrustAmount: Int): Paths {
|
||||||
|
|
||||||
|
logger.debug("Query.authenticate")
|
||||||
|
logger.debug("Authenticating <{}, '{}'>", targetFpr, targetUserid)
|
||||||
|
logger.debug("Roots ({}):", roots.size())
|
||||||
|
logger.debug(roots.roots().withIndex()
|
||||||
|
.joinToString("\n") { (i, r) -> " $i: $r" })
|
||||||
|
|
||||||
|
val paths = Paths()
|
||||||
|
|
||||||
|
// This ChainFilter collects modifiers to the network over the course
|
||||||
|
// of the calculation of this authentication.
|
||||||
|
val filters = ChainFilter()
|
||||||
|
|
||||||
|
if (certificationNetwork) {
|
||||||
|
// We're building a certification network.
|
||||||
|
// (Treat all certifications like delegations with infinite depth
|
||||||
|
// and no regular expressions.)
|
||||||
|
filters.add(TrustedIntroducerFilter())
|
||||||
|
} else {
|
||||||
|
// We're building a regular authentication network.
|
||||||
|
|
||||||
|
// Model trust amounts of roots as a CapCertificateFilter
|
||||||
|
// for roots that are not "FULLY_TRUSTED"
|
||||||
|
if (roots.roots().any { it.amount != FULLY_TRUSTED }) {
|
||||||
|
val caps = CapCertificateFilter()
|
||||||
|
|
||||||
|
roots.roots().forEach {
|
||||||
|
if (it.amount != FULLY_TRUSTED) {
|
||||||
|
caps.cap(it.fingerprint, it.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filters.add(caps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a (partial) run of the Ford Fulkerson algorithm.
|
||||||
|
//
|
||||||
|
// (The Ford Fulkerson algorithm finds a path, computes a residual
|
||||||
|
// network by subtracting that path, and then loops until no paths
|
||||||
|
// remain)
|
||||||
|
|
||||||
|
|
||||||
|
var progress = true
|
||||||
|
|
||||||
|
// On this iteration approach
|
||||||
|
// [https://gitlab.com/sequoia-pgp/sequoia-wot/-/commit/ff006688155aaa3ee0c14b88bef1a143b0ecae23]
|
||||||
|
//
|
||||||
|
// "Better mimic GnuPG's trust root semantics
|
||||||
|
//
|
||||||
|
// If Alice considers Bob and Carol to be fully trusted, Alice has
|
||||||
|
// certified Bob, and Bob has certified Carol, then Carol should be
|
||||||
|
// considered a trust root, because she is certified by Bob, who is
|
||||||
|
// considered a trust root, because he is certified by Alice.
|
||||||
|
//
|
||||||
|
// In other words, we need to iterate."
|
||||||
|
nextPath@ while (progress && paths.amount < targetTrustAmount) {
|
||||||
|
progress = false
|
||||||
|
|
||||||
|
for (selfSigned in listOf(true, false)) {
|
||||||
|
val authPaths = backwardPropagate(targetFpr, targetUserid, selfSigned, filters)
|
||||||
|
|
||||||
|
// The paths returned by backward_propagate may overlap.
|
||||||
|
// So we only use one (picking one of the best, by trust and length).
|
||||||
|
//
|
||||||
|
// Then we subtract the path from the network and run backward_propagate
|
||||||
|
// again, if we haven't yet reached 'targetTrustAmount'.
|
||||||
|
val bestPath = roots.fingerprints()
|
||||||
|
.mapNotNull { authPaths[it] } // Only consider paths that start at a root.
|
||||||
|
.maxWithOrNull(compareBy(
|
||||||
|
// We want the *most* amount of trust,
|
||||||
|
{ it.second }, // path amount
|
||||||
|
// but the *shortest* path.
|
||||||
|
{ -it.first.length }, // -path.len
|
||||||
|
// Be predictable. Break ties based on the fingerprint of the root.
|
||||||
|
{ it.first.root.fingerprint })
|
||||||
|
)
|
||||||
|
|
||||||
|
if (bestPath != null) {
|
||||||
|
val (path, amount) = bestPath
|
||||||
|
|
||||||
|
if (path.length == 1) {
|
||||||
|
// This path is a root.
|
||||||
|
//
|
||||||
|
// We've used 'amount' of trust from this root, so we'll detract that amount
|
||||||
|
// from that root, with a filter.
|
||||||
|
val suppress = SuppressIssuerFilter()
|
||||||
|
suppress.suppressIssuer(path.root.fingerprint, amount)
|
||||||
|
filters.add(suppress)
|
||||||
|
} else {
|
||||||
|
// Add the path to the filter to create a residual
|
||||||
|
// network without this path.
|
||||||
|
val suppress = SuppressCertificationFilter()
|
||||||
|
|
||||||
|
suppress.suppressPath(path, amount)
|
||||||
|
filters.add(suppress)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.add(path, amount)
|
||||||
|
progress = true
|
||||||
|
|
||||||
|
// Prefer paths where the target User ID is self-signed as long as possible.
|
||||||
|
continue@nextPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a path in the network from one or multiple `roots` that
|
||||||
|
* authenticates the target binding.
|
||||||
|
*
|
||||||
|
* If `roots` is empty, authenticated paths starting from any node
|
||||||
|
* are returned.
|
||||||
|
*
|
||||||
|
* Implements the algorithm outlined in:
|
||||||
|
* https://gitlab.com/sequoia-pgp/sequoia-wot/-/blob/main/spec/sequoia-wot.md#implementation-strategy
|
||||||
|
*
|
||||||
|
* Note: the algorithm prefers shorter paths to longer paths. So the
|
||||||
|
* returned path(s) may not be optimal in terms of the amount of trust.
|
||||||
|
* To compensate for this, the caller should run the algorithm again on
|
||||||
|
* a residual network.
|
||||||
|
*
|
||||||
|
* `selfSigned` picks between two variants of this algorithm. Each of the
|
||||||
|
* modes finds a distinct subset of authenticated paths:
|
||||||
|
*
|
||||||
|
* - If `true`, this function only finds paths that end in a
|
||||||
|
* self-certification, and only if the target node is
|
||||||
|
* a trusted introducer.
|
||||||
|
*
|
||||||
|
* - If `false`, this function only finds paths that don't use
|
||||||
|
* a self-certification as the last edge.
|
||||||
|
*/
|
||||||
|
private fun backwardPropagate(targetFpr: Fingerprint,
|
||||||
|
targetUserid: String,
|
||||||
|
selfSigned: Boolean,
|
||||||
|
filter: CertificationFilter)
|
||||||
|
: HashMap<Fingerprint, Pair<Path, Int>> {
|
||||||
|
|
||||||
|
logger.debug("Query.backward_propagate")
|
||||||
|
|
||||||
|
logger.debug("Roots (${roots.size()}):\n{}",
|
||||||
|
roots.roots().withIndex().joinToString("\n") { (i, r) ->
|
||||||
|
val fpr = r.fingerprint
|
||||||
|
network.nodes[fpr]?.let { " {$i}. {$it}" } ?: " {$i}. {$fpr} (not found)"
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.debug("target: {}, {}", targetFpr, targetUserid)
|
||||||
|
logger.debug("self signed: {}", selfSigned)
|
||||||
|
|
||||||
|
// If the node is not in the network, we're done.
|
||||||
|
val target = network.nodes[targetFpr] ?: return hashMapOf()
|
||||||
|
|
||||||
|
// Make sure the target is valid (not expired and not revoked
|
||||||
|
// at the reference time).
|
||||||
|
if ((target.expirationTime != null) &&
|
||||||
|
(target.expirationTime <= network.referenceTime.timestamp)) {
|
||||||
|
logger.debug("{}: Target certificate is expired at reference time.", targetFpr)
|
||||||
|
return hashMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.revocationState.isEffective(network.referenceTime)) {
|
||||||
|
logger.debug("{}: Target certificate is revoked at reference time.", targetFpr)
|
||||||
|
return hashMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recall: the target doesn't need to have self-signed the
|
||||||
|
// User ID to authenticate the User ID. But if the target has
|
||||||
|
// revoked it, then it can't be authenticated.
|
||||||
|
val targetUa: RevocationState? = target.userIds[targetUserid]
|
||||||
|
targetUa?.let {
|
||||||
|
if (it.isEffective(network.referenceTime)) {
|
||||||
|
logger.debug("{}: Target user id is revoked at reference time.", targetFpr)
|
||||||
|
return hashMapOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dijkstra.
|
||||||
|
val bestNextNode: HashMap<Fingerprint, ForwardPointer> = HashMap()
|
||||||
|
val queue: PairPriorityQueue<Fingerprint, Cost> = PairPriorityQueue()
|
||||||
|
|
||||||
|
fun fpCost(fp0: ForwardPointer): Cost {
|
||||||
|
var fp = fp0
|
||||||
|
|
||||||
|
var amount = 120
|
||||||
|
var length: Int = if (selfSigned) 1 else 0
|
||||||
|
|
||||||
|
while (fp.next != null) {
|
||||||
|
val ec: EdgeComponent = fp.next!! // FIXME
|
||||||
|
|
||||||
|
val a = ec.trustAmount
|
||||||
|
val d = ec.trustDepth
|
||||||
|
|
||||||
|
val value = FilterValues(d, a, null)
|
||||||
|
|
||||||
|
val r = filter.cost(ec, value, true)
|
||||||
|
assert(r) { "cost function returned different result, but must be constant!" }
|
||||||
|
|
||||||
|
amount = min(value.amount, amount)
|
||||||
|
length += 1
|
||||||
|
fp = bestNextNode[ec.target.fingerprint]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cost(length, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfSigned) {
|
||||||
|
// If the target is a trusted introducer and has self-signed
|
||||||
|
// the User ID, then also consider that path.
|
||||||
|
if (targetUa != null) {
|
||||||
|
logger.debug("Target User ID is self signed.")
|
||||||
|
|
||||||
|
val cost = Cost(1, 120)
|
||||||
|
queue.insert(targetFpr, cost)
|
||||||
|
bestNextNode[targetFpr] = ForwardPointer(null)
|
||||||
|
} else {
|
||||||
|
logger.debug("Target User ID is not self-signed, but that is required.")
|
||||||
|
return hashMapOf()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val cost = Cost(0, 120)
|
||||||
|
queue.insert(targetFpr, cost)
|
||||||
|
bestNextNode[targetFpr] = ForwardPointer(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Iterate over each node in the priority queue.
|
||||||
|
while (true) {
|
||||||
|
val signeeFpr = queue.pop()?.first ?: break
|
||||||
|
|
||||||
|
val it = roots.get(signeeFpr)
|
||||||
|
if ((it != null) && (it.amount >= FULLY_TRUSTED)) {
|
||||||
|
// XXX: Technically, we could stop if the root's trust
|
||||||
|
// amount is at least the required trust amount.
|
||||||
|
// Since we don't know it, and the maximum is
|
||||||
|
// `FULLY_TRUSTED`, we use that.
|
||||||
|
logger.debug("Skipping fully trust root: {}.", it.fingerprint)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val signee = network.nodes[signeeFpr]!! // already looked up
|
||||||
|
|
||||||
|
// Get the signee's current forward pointer.
|
||||||
|
//
|
||||||
|
// We need to clone this, because we want to manipulate
|
||||||
|
// 'distance' and we can't do that if there is a reference
|
||||||
|
// to something in it.
|
||||||
|
val signeeFp: ForwardPointer = bestNextNode[signeeFpr]!!
|
||||||
|
val signeeFpCost = fpCost(signeeFp)
|
||||||
|
|
||||||
|
logger.debug("{}'s forward pointer: {}", signeeFpr, signeeFp.next?.target)
|
||||||
|
|
||||||
|
// Get signeeFp
|
||||||
|
|
||||||
|
// Not limiting by required_depth, because 'network' doesn't expose an interface for this
|
||||||
|
val certificationSets: List<Edge> =
|
||||||
|
network.reverseEdges[signeeFpr].orEmpty() // "certifications_of"
|
||||||
|
|
||||||
|
if (certificationSets.isEmpty()) {
|
||||||
|
// Nothing certified it. The path is a dead end.
|
||||||
|
logger.debug("{} was not certified, dead end", signeeFpr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Visiting {} ({}), certified {} times",
|
||||||
|
signee.fingerprint,
|
||||||
|
signee.toString(),
|
||||||
|
certificationSets.size)
|
||||||
|
|
||||||
|
for (certification in certificationSets
|
||||||
|
.map { cs ->
|
||||||
|
cs.components
|
||||||
|
.map { it.value }.flatten()
|
||||||
|
}.flatten()) {
|
||||||
|
|
||||||
|
val issuerFpr = certification.issuer.fingerprint
|
||||||
|
|
||||||
|
val fv = FilterValues(certification.trustDepth,
|
||||||
|
certification.trustAmount,
|
||||||
|
certification.regexes)
|
||||||
|
|
||||||
|
if (!filter.cost(certification, fv,
|
||||||
|
false)) {
|
||||||
|
logger.debug(" Cost function says to skip certification by {}", certification.issuer)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(" Considering certification by: {}, depth: {} (of {}), amount: {} (of {}), regexes: {}",
|
||||||
|
certification.issuer,
|
||||||
|
fv.depth,
|
||||||
|
certification.trustDepth,
|
||||||
|
fv.amount,
|
||||||
|
certification.trustAmount,
|
||||||
|
fv.regexps)
|
||||||
|
|
||||||
|
|
||||||
|
if (fv.amount == 0) {
|
||||||
|
logger.debug(" Certification amount is 0, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selfSigned
|
||||||
|
&& signeeFpr == targetFpr
|
||||||
|
&& certification.userId != targetUserid) {
|
||||||
|
assert(signeeFp.next == null)
|
||||||
|
|
||||||
|
logger.debug(" Certification certifies target, but for the wrong user id (want: {}, got: {})",
|
||||||
|
targetUserid, certification.userId)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fv.depth < Depth.auto(signeeFpCost.length)) {
|
||||||
|
logger.debug(" Certification does not have enough depth ({}, needed: {}), skipping", fv.depth, signeeFpCost.length)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val re = fv.regexps
|
||||||
|
if ((re != null) && !re.matches(targetUserid)) {
|
||||||
|
logger.debug(" Certification's re does not match target User ID, skipping.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val proposedFp: ForwardPointer = ForwardPointer(certification)
|
||||||
|
|
||||||
|
val proposedFpCost = Cost(signeeFpCost.length + 1,
|
||||||
|
min(fv.amount, signeeFpCost.amount))
|
||||||
|
|
||||||
|
logger.debug(" Forward pointer for {}:", certification.issuer)
|
||||||
|
|
||||||
|
val pn = proposedFp.next // cache value for debug output
|
||||||
|
logger.debug(" Proposed: {}, amount: {}, depth: {}",
|
||||||
|
pn?.target ?: "target", proposedFpCost.amount, proposedFpCost.length)
|
||||||
|
|
||||||
|
// distance.entry takes a mutable ref, so we can't
|
||||||
|
// compute the current fp's cost in the next block.
|
||||||
|
val currentFpCost: Cost? = bestNextNode[issuerFpr]?.let { fpCost(it) }
|
||||||
|
|
||||||
|
when (val currentFp = bestNextNode[issuerFpr]) {
|
||||||
|
null -> {
|
||||||
|
// We haven't seen this node before.
|
||||||
|
|
||||||
|
logger.debug(" Current: None")
|
||||||
|
logger.debug(" Setting {}'s forward pointer to {}", certification.issuer, signee)
|
||||||
|
logger.debug(" Queuing {}", certification.issuer)
|
||||||
|
|
||||||
|
queue.insert(issuerFpr, proposedFpCost)
|
||||||
|
bestNextNode[issuerFpr] = proposedFp
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// We've visited this node in the past. Now
|
||||||
|
// we need to determine whether using
|
||||||
|
// certification and following the proposed
|
||||||
|
// path is better than the current path.
|
||||||
|
|
||||||
|
val currentFpCost = currentFpCost!! // shadow the variable
|
||||||
|
|
||||||
|
val cn = currentFp.next // cache value for debug output
|
||||||
|
logger.debug(" Current: {}, amount: {}, depth: {}",
|
||||||
|
cn?.target ?: "target", currentFpCost.amount, currentFpCost.length)
|
||||||
|
|
||||||
|
// We prefer a shorter path (in terms of
|
||||||
|
// edges) as this allows us to reach more of
|
||||||
|
// the graph.
|
||||||
|
//
|
||||||
|
// If the path length is equal, we prefer the
|
||||||
|
// larger amount of trust.
|
||||||
|
|
||||||
|
if (proposedFpCost.length < currentFpCost.length) {
|
||||||
|
if (proposedFpCost.amount < currentFpCost.amount) {
|
||||||
|
// We have two local optima: one has a shorter path, the other a
|
||||||
|
// higher trust amount. We prefer the shorter path.
|
||||||
|
|
||||||
|
logger.debug(" Preferring proposed: current has a shorter path ({} < {}), but worse amount of trust ({} < {})",
|
||||||
|
proposedFpCost.length, currentFpCost.length,
|
||||||
|
proposedFpCost.amount, currentFpCost.amount)
|
||||||
|
|
||||||
|
bestNextNode[issuerFpr] = proposedFp
|
||||||
|
} else {
|
||||||
|
// Proposed fp is strictly better.
|
||||||
|
|
||||||
|
logger.debug(" Preferring proposed: current has a shorter path ({} < {}), and a better amount of trust ({} < {})",
|
||||||
|
proposedFpCost.length, currentFpCost.length,
|
||||||
|
proposedFpCost.amount, currentFpCost.amount)
|
||||||
|
|
||||||
|
bestNextNode[issuerFpr] = proposedFp
|
||||||
|
}
|
||||||
|
} else if (proposedFpCost.length == currentFpCost.length
|
||||||
|
&& proposedFpCost.amount > currentFpCost.amount) {
|
||||||
|
// Strictly better.
|
||||||
|
|
||||||
|
logger.debug(" Preferring proposed fp: same path length ({}), better amount ({} > {})",
|
||||||
|
proposedFpCost.length,
|
||||||
|
proposedFpCost.amount, currentFpCost.amount)
|
||||||
|
|
||||||
|
bestNextNode[issuerFpr] = proposedFp
|
||||||
|
} else if (proposedFpCost.length > currentFpCost.length
|
||||||
|
&& proposedFpCost.amount > currentFpCost.amount) {
|
||||||
|
// There's another possible path through here.
|
||||||
|
logger.debug(" Preferring current fp: proposed has more trust ({} > {}), but a longer path ({} > {})",
|
||||||
|
proposedFpCost.amount, currentFpCost.amount,
|
||||||
|
proposedFpCost.length, currentFpCost.length)
|
||||||
|
} else {
|
||||||
|
logger.debug(" Preferring current fp: it is strictly better (depth: {}, {}; amount: {}, {})",
|
||||||
|
proposedFpCost.length, currentFpCost.length,
|
||||||
|
proposedFpCost.amount, currentFpCost.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow the forward pointers and reconstruct the paths.
|
||||||
|
val authRpaths: HashMap<Fingerprint, Pair<Path, Int>> = hashMapOf()
|
||||||
|
|
||||||
|
for ((issuerFpr, fp) in bestNextNode.entries) {
|
||||||
|
var fp = fp // Shadow for write access
|
||||||
|
|
||||||
|
// If roots were specified, then only return the optimal
|
||||||
|
// paths from the roots.
|
||||||
|
if (roots.size() > 0 && !roots.isRoot(issuerFpr)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val c = fp.next
|
||||||
|
val issuer =
|
||||||
|
if (c != null) {
|
||||||
|
c.issuer
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// The target.
|
||||||
|
if (!selfSigned) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply any policy to the self certification.
|
||||||
|
//
|
||||||
|
// XXX: Self-signatures should be first class and not
|
||||||
|
// synthesized like this on the fly.
|
||||||
|
val selfsig = EdgeComponent(
|
||||||
|
target, target, targetUserid,
|
||||||
|
|
||||||
|
// FIXME! Use userid binding signature by default, reference time only as fallback:
|
||||||
|
|
||||||
|
// target_ua.map(|ua| ua.binding_signature_creation_time())
|
||||||
|
// .unwrap_or(self.network().reference_time()))
|
||||||
|
|
||||||
|
network.referenceTime.timestamp,
|
||||||
|
|
||||||
|
null, true, 120, Depth.limited(0), RegexSet.wildcard()
|
||||||
|
)
|
||||||
|
|
||||||
|
val fv = FilterValues(Depth.auto(0), 120, null)
|
||||||
|
if (filter.cost(selfsig, fv, true)) {
|
||||||
|
logger.debug("Policy on selfsig => amount: {}", fv.amount)
|
||||||
|
|
||||||
|
if (fv.amount == 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Policy says to ignore selfsig")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val p = Path(target)
|
||||||
|
logger.debug("Authenticated <{}, {}>:\n{}", targetFpr, targetUserid, p)
|
||||||
|
|
||||||
|
authRpaths[issuerFpr] = Pair(p, fv.amount)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Recovering path starting at {}", network.nodes[issuerFpr])
|
||||||
|
|
||||||
|
var amount = 120
|
||||||
|
|
||||||
|
// nodes[0] is the root; nodes[nodes.len() - 1] is the target.
|
||||||
|
val nodes: MutableList<EdgeComponent> = mutableListOf()
|
||||||
|
while (true) {
|
||||||
|
val c = fp.next ?: break
|
||||||
|
|
||||||
|
logger.debug(" {}", fp)
|
||||||
|
|
||||||
|
val fv = FilterValues(c.trustDepth, c.trustAmount, null)
|
||||||
|
|
||||||
|
val r = filter.cost(c, fv, true)
|
||||||
|
|
||||||
|
assert(r) {
|
||||||
|
"cost function returned different result, but must be constant !"
|
||||||
|
}
|
||||||
|
amount = min(fv.amount, amount)
|
||||||
|
|
||||||
|
nodes.add(c)
|
||||||
|
fp = bestNextNode[c.target.fingerprint]!! // FIXME !!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfSigned) {
|
||||||
|
val tail = nodes.last()
|
||||||
|
if (tail.userId != targetUserid) {
|
||||||
|
/// XXX: don't synthesize selfsigs
|
||||||
|
val selfsig = EdgeComponent(target, target, targetUserid, Date(),
|
||||||
|
null, true, 120, Depth.limited(0), RegexSet.wildcard())
|
||||||
|
nodes.add(selfsig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(" {}", fp)
|
||||||
|
|
||||||
|
logger.debug("\nShortest path from {} to <{} <-> {}>:\n {}",
|
||||||
|
issuer.fingerprint,
|
||||||
|
targetUserid, targetFpr,
|
||||||
|
nodes.withIndex().joinToString("\n ") { (i, certification) ->
|
||||||
|
"$i: $certification"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert(nodes.size > 0)
|
||||||
|
|
||||||
|
val p = Path(issuer)
|
||||||
|
for (n in nodes.iterator()) {
|
||||||
|
p.append(n)
|
||||||
|
}
|
||||||
|
logger.debug("Authenticated <{}, {}>:\n{}", targetFpr, targetUserid, p)
|
||||||
|
|
||||||
|
authRpaths[issuerFpr] = Pair(p, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if TRACE {
|
||||||
|
// t!("auth_rpaths:")
|
||||||
|
// let mut v: Vec<_> = auth_rpaths.iter().collect()
|
||||||
|
// v.sort_by(|(fpr_a, _), (fpr_b, _)| {
|
||||||
|
// let userid_a = self.network()
|
||||||
|
// .lookup_synopsis_by_fpr(*fpr_a).expect("already looked up")
|
||||||
|
// .primary_userid().map(|userid| {
|
||||||
|
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||||
|
// }).unwrap_or("".into())
|
||||||
|
// let userid_b = self.network()
|
||||||
|
// .lookup_synopsis_by_fpr(*fpr_b).expect("already looked up")
|
||||||
|
// .primary_userid().map(|userid| {
|
||||||
|
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||||
|
// }).unwrap_or("".into())
|
||||||
|
//
|
||||||
|
// userid_a.cmp(&userid_b).
|
||||||
|
// then(fpr_a.cmp(&fpr_b))
|
||||||
|
// })
|
||||||
|
// for (fpr, (path, amount)) in v {
|
||||||
|
// let userid = self.network()
|
||||||
|
// .lookup_synopsis_by_fpr(fpr).expect("already looked up")
|
||||||
|
// .primary_userid().map(|userid| {
|
||||||
|
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||||
|
// })
|
||||||
|
// .unwrap_or("<missing User ID>".into())
|
||||||
|
// t!(" <{}, {}>: {}",
|
||||||
|
// fpr, userid,
|
||||||
|
// format!("{} trust amount (max: {}), {} edges",
|
||||||
|
// amount, path.amount(),
|
||||||
|
// path.len() - 1))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return authRpaths
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue