From c98d6c4708a531936eaebdae364062e1b933d315 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 14:39:37 +0200 Subject: [PATCH] WIP: Identify command --- .../kotlin/org/pgpainless/wot/cli/WotCLI.kt | 50 +++++++++++++++---- .../wot/cli/subcommands/AuthenticateCmd.kt | 4 +- .../wot/cli/subcommands/IdentifyCmd.kt | 33 +++++++++++- .../org/pgpainless/wot/api/IdentifyAPI.kt | 10 +++- .../kotlin/org/pgpainless/wot/api/WoTAPI.kt | 13 ++++- .../org/pgpainless/wot/network/DepthTest.kt | 1 - 6 files changed, 94 insertions(+), 17 deletions(-) diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt index a746854c..dce33d46 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt @@ -15,12 +15,15 @@ import org.pgpainless.wot.network.Fingerprint import org.pgpainless.wot.network.ReferenceTime import org.pgpainless.wot.network.Root import org.pgpainless.wot.network.Roots +import pgp.cert_d.PGPCertificateDirectory import pgp.cert_d.PGPCertificateStoreAdapter +import pgp.cert_d.SpecialNames import pgp.cert_d.subkey_lookup.InMemorySubkeyLookupFactory import pgp.certificate_store.PGPCertificateStore import picocli.CommandLine import picocli.CommandLine.* import java.io.File +import java.text.SimpleDateFormat import java.util.concurrent.Callable import kotlin.system.exitProcess @@ -108,22 +111,28 @@ class WotCLI: Callable { private val trustRoots: Roots get() { - val trustRootFingerprints = if (mCertificateSource.gpg || gpgOwnertrust) { - readGpgOwnertrust().plus(mTrustRoot.map { Fingerprint(it) }) - } else { - mTrustRoot.map { Fingerprint(it) } + var trustRootFingerprints = mTrustRoot.map { Fingerprint(it) }.map { Root(it) } + if (mCertificateSource.gpg || gpgOwnertrust) { + trustRootFingerprints = trustRootFingerprints.plus(readGpgOwnertrust()) } - - return Roots(trustRootFingerprints.map { Root(it) }) + if (mCertificateSource.pgpCertD != null) { + try { + val rootCert = certificateStore.getCertificate(SpecialNames.TRUST_ROOT) + trustRootFingerprints = trustRootFingerprints.plus(Root(Fingerprint(rootCert.fingerprint), Int.MAX_VALUE)) + } catch (e: NoSuchElementException) { + // ignore + } + } + return Roots(trustRootFingerprints) } private val amount: Int get() = when { - mTrustAmount.amount != null -> mTrustAmount.amount!! // --amount=XY + mTrustAmount.amount != null -> mTrustAmount.amount!! // --amount=XY mTrustAmount.partial -> 40 // --partial mTrustAmount.full -> 120 // --full mTrustAmount.double -> 240 // --double - else -> if (certificationNetwork) 1200 else 120 // default 120, if --certification-network -> 1200 + else -> if (certificationNetwork) 1200 else 120 // default 120, if --certification-network -> 1200 } private val certificateStore: PGPCertificateStore @@ -149,15 +158,31 @@ class WotCLI: Callable { return PGPCertificateStoreAdapter(certD) } - fun readGpgOwnertrust(): List = Runtime.getRuntime() + fun readGpgOwnertrust(): List = Runtime.getRuntime() .exec("/usr/bin/gpg --export-ownertrust") .inputStream .bufferedReader() .readLines() + .asSequence() .filterNot { it.startsWith("#") } .filterNot { it.isBlank() } - .map { it.substring(0, it.indexOf(':')) } - .map { Fingerprint(it) } + .map { + Fingerprint(it.substring(0, it.indexOf(':'))) to it.elementAt(it.indexOf(':') + 1) } + .map { + it.first to when (it.second.digitToInt()) { + 2 -> null // unknown + 3 -> 0 // not trust + 4 -> 40 // marginally trusted + 5 -> 120 // fully trusted + 6 -> Int.MAX_VALUE // ultimately trusted + else -> null + } + } + .filterNot { it.second == null } + .map { + Root(it.first, it.second!!) + } + .toList() /** * Execute the command. @@ -196,6 +221,9 @@ class WotCLI: Callable { CommandLine(WotCLI()).execute(*args) ) + @JvmStatic + val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd") + } override fun toString(): String { diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt index 2258a964..201667d7 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt @@ -45,7 +45,7 @@ class AuthenticateCmd: Callable { @CommandLine.Option(names = ["--email"], description = ["Consider all user-IDs that contain the given email address."]) var email = false - private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd") + /** * Execute the command. @@ -83,7 +83,7 @@ class AuthenticateCmd: Callable { sb.appendLine(" ├ ${certification.issuer.fingerprint}${issuerUserId}") } } - sb.appendLine(" │ certified the following binding on ${dateFormat.format(certification.creationTime)}") + sb.appendLine(" │ certified the following binding on ${WotCLI.dateFormat.format(certification.creationTime)}") } sb.appendLine(" └ ${result.fingerprint} \"${result.userId}\"") } diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt index 0011d30e..b39a9d1f 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt @@ -4,7 +4,9 @@ package org.pgpainless.wot.cli.subcommands +import org.pgpainless.wot.api.IdentifyAPI import org.pgpainless.wot.cli.WotCLI +import org.pgpainless.wot.network.Fingerprint import picocli.CommandLine import picocli.CommandLine.Command import picocli.CommandLine.Parameters @@ -26,7 +28,36 @@ class IdentifyCmd: Callable { */ override fun call(): Int { val api = parent.api - TODO("Not yet implemented") + val result = api.identify(IdentifyAPI.Arguments(Fingerprint(fingerprint))) + println(formatResult(result)) + return exitCode(result) + } + + fun formatResult(result: IdentifyAPI.Result): String { + if (result.target == null || result.paths.isEmpty()) { + return "No paths found." + } + + return buildString { + result.paths.keys.forEach { userId -> + val target = result.target!! + appendLine("[✓] ${target.fingerprint} $userId: fully authenticated: (${result.percentage(userId)}%)") + result.paths[userId]!!.paths.forEach {path -> + val root = path.root + val userIdString = if (root.userIds.isEmpty()) "" else " (${root.userIds.keys.first()})" + appendLine(" ◯ ${root.fingerprint}$userIdString") + path.certifications.forEachIndexed { index, edge -> + appendLine(" │ certified the following binding on ${WotCLI.dateFormat.format(edge.creationTime)}") + append(" ").append(if (index == path.certifications.lastIndex) "└" else "├") + .appendLine(" ${edge.target.fingerprint} \"${edge.userId}\"") + } + } + } + } + } + + fun exitCode(result: IdentifyAPI.Result): Int { + return if(result.paths.isEmpty()) -1 else 0 } } \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt index 67e3809e..317e21c8 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt @@ -5,6 +5,7 @@ package org.pgpainless.wot.api import org.pgpainless.wot.network.Fingerprint +import org.pgpainless.wot.network.Node import org.pgpainless.wot.query.Paths interface IdentifyAPI { @@ -13,5 +14,12 @@ interface IdentifyAPI { data class Arguments(val fingerprint: Fingerprint) - data class Result(val paths: Paths) + data class Result(val paths: Map, val target: Node?, val targetAmount: Int) { + fun percentage(userId: String): Int? { + if (paths[userId] == null) { + return null + } + return paths[userId]!!.amount * 100 / targetAmount + } + } } diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt index a2aba631..ed8366cf 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt @@ -9,6 +9,8 @@ import org.pgpainless.wot.network.Fingerprint import org.pgpainless.wot.network.Network import org.pgpainless.wot.network.ReferenceTime import org.pgpainless.wot.network.Roots +import org.pgpainless.wot.query.Path +import org.pgpainless.wot.query.Paths /** * Web of Trust API, offering different operations. @@ -48,7 +50,16 @@ class WoTAPI( } override fun identify(arguments: IdentifyAPI.Arguments): IdentifyAPI.Result { - TODO("Not yet implemented") + val cert = network.nodes[arguments.fingerprint] ?: return IdentifyAPI.Result(mutableMapOf(), null, trustAmount) + val allPaths = mutableMapOf() + cert.userIds.keys.toList().forEach { + val query = Query(network, trustRoots, certificationNetwork) + val paths = query.authenticate(arguments.fingerprint, it, trustAmount) + if (paths.amount != 0) { + allPaths[it] = paths + } + } + return IdentifyAPI.Result(allPaths, cert, trustAmount) } override fun list(): ListAPI.Result { diff --git a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/network/DepthTest.kt b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/network/DepthTest.kt index 855fc604..cf775cc2 100644 --- a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/network/DepthTest.kt +++ b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/network/DepthTest.kt @@ -125,7 +125,6 @@ class DepthTest { assertThrows { limited(-1) } assertThrows { limited(256) } assertThrows { auto(-1) } - assertThrows { auto(256) } } @Test