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

WIP: Identify command

This commit is contained in:
Paul Schaub 2023-07-11 14:39:37 +02:00
parent fbdcae3c81
commit c98d6c4708
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
6 changed files with 94 additions and 17 deletions

View file

@ -15,12 +15,15 @@ import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.network.ReferenceTime import org.pgpainless.wot.network.ReferenceTime
import org.pgpainless.wot.network.Root import org.pgpainless.wot.network.Root
import org.pgpainless.wot.network.Roots import org.pgpainless.wot.network.Roots
import pgp.cert_d.PGPCertificateDirectory
import pgp.cert_d.PGPCertificateStoreAdapter import pgp.cert_d.PGPCertificateStoreAdapter
import pgp.cert_d.SpecialNames
import pgp.cert_d.subkey_lookup.InMemorySubkeyLookupFactory import pgp.cert_d.subkey_lookup.InMemorySubkeyLookupFactory
import pgp.certificate_store.PGPCertificateStore import pgp.certificate_store.PGPCertificateStore
import picocli.CommandLine import picocli.CommandLine
import picocli.CommandLine.* import picocli.CommandLine.*
import java.io.File import java.io.File
import java.text.SimpleDateFormat
import java.util.concurrent.Callable import java.util.concurrent.Callable
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -108,22 +111,28 @@ class WotCLI: Callable<Int> {
private val trustRoots: Roots private val trustRoots: Roots
get() { get() {
val trustRootFingerprints = if (mCertificateSource.gpg || gpgOwnertrust) { var trustRootFingerprints = mTrustRoot.map { Fingerprint(it) }.map { Root(it) }
readGpgOwnertrust().plus(mTrustRoot.map { Fingerprint(it) }) if (mCertificateSource.gpg || gpgOwnertrust) {
} else { trustRootFingerprints = trustRootFingerprints.plus(readGpgOwnertrust())
mTrustRoot.map { Fingerprint(it) }
} }
if (mCertificateSource.pgpCertD != null) {
return Roots(trustRootFingerprints.map { Root(it) }) 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 private val amount: Int
get() = when { get() = when {
mTrustAmount.amount != null -> mTrustAmount.amount!! // --amount=XY mTrustAmount.amount != null -> mTrustAmount.amount!! // --amount=XY
mTrustAmount.partial -> 40 // --partial mTrustAmount.partial -> 40 // --partial
mTrustAmount.full -> 120 // --full mTrustAmount.full -> 120 // --full
mTrustAmount.double -> 240 // --double 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 private val certificateStore: PGPCertificateStore
@ -149,15 +158,31 @@ class WotCLI: Callable<Int> {
return PGPCertificateStoreAdapter(certD) return PGPCertificateStoreAdapter(certD)
} }
fun readGpgOwnertrust(): List<Fingerprint> = Runtime.getRuntime() fun readGpgOwnertrust(): List<Root> = Runtime.getRuntime()
.exec("/usr/bin/gpg --export-ownertrust") .exec("/usr/bin/gpg --export-ownertrust")
.inputStream .inputStream
.bufferedReader() .bufferedReader()
.readLines() .readLines()
.asSequence()
.filterNot { it.startsWith("#") } .filterNot { it.startsWith("#") }
.filterNot { it.isBlank() } .filterNot { it.isBlank() }
.map { it.substring(0, it.indexOf(':')) } .map {
.map { Fingerprint(it) } 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. * Execute the command.
@ -196,6 +221,9 @@ class WotCLI: Callable<Int> {
CommandLine(WotCLI()).execute(*args) CommandLine(WotCLI()).execute(*args)
) )
@JvmStatic
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
} }
override fun toString(): String { override fun toString(): String {

View file

@ -45,7 +45,7 @@ class AuthenticateCmd: Callable<Int> {
@CommandLine.Option(names = ["--email"], description = ["Consider all user-IDs that contain the given email address."]) @CommandLine.Option(names = ["--email"], description = ["Consider all user-IDs that contain the given email address."])
var email = false var email = false
private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
/** /**
* Execute the command. * Execute the command.
@ -83,7 +83,7 @@ class AuthenticateCmd: Callable<Int> {
sb.appendLine("${certification.issuer.fingerprint}${issuerUserId}") 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}\"") sb.appendLine("${result.fingerprint} \"${result.userId}\"")
} }

View file

@ -4,7 +4,9 @@
package org.pgpainless.wot.cli.subcommands package org.pgpainless.wot.cli.subcommands
import org.pgpainless.wot.api.IdentifyAPI
import org.pgpainless.wot.cli.WotCLI import org.pgpainless.wot.cli.WotCLI
import org.pgpainless.wot.network.Fingerprint
import picocli.CommandLine import picocli.CommandLine
import picocli.CommandLine.Command import picocli.CommandLine.Command
import picocli.CommandLine.Parameters import picocli.CommandLine.Parameters
@ -26,7 +28,36 @@ class IdentifyCmd: Callable<Int> {
*/ */
override fun call(): Int { override fun call(): Int {
val api = parent.api 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
} }
} }

View file

@ -5,6 +5,7 @@
package org.pgpainless.wot.api package org.pgpainless.wot.api
import org.pgpainless.wot.network.Fingerprint import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.network.Node
import org.pgpainless.wot.query.Paths import org.pgpainless.wot.query.Paths
interface IdentifyAPI { interface IdentifyAPI {
@ -13,5 +14,12 @@ interface IdentifyAPI {
data class Arguments(val fingerprint: Fingerprint) data class Arguments(val fingerprint: Fingerprint)
data class Result(val paths: Paths) data class Result(val paths: Map<String, Paths>, val target: Node?, val targetAmount: Int) {
fun percentage(userId: String): Int? {
if (paths[userId] == null) {
return null
}
return paths[userId]!!.amount * 100 / targetAmount
}
}
} }

View file

@ -9,6 +9,8 @@ import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.network.Network import org.pgpainless.wot.network.Network
import org.pgpainless.wot.network.ReferenceTime import org.pgpainless.wot.network.ReferenceTime
import org.pgpainless.wot.network.Roots 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. * Web of Trust API, offering different operations.
@ -48,7 +50,16 @@ class WoTAPI(
} }
override fun identify(arguments: IdentifyAPI.Arguments): IdentifyAPI.Result { 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<String, Paths>()
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 { override fun list(): ListAPI.Result {

View file

@ -125,7 +125,6 @@ class DepthTest {
assertThrows<IllegalArgumentException> { limited(-1) } assertThrows<IllegalArgumentException> { limited(-1) }
assertThrows<IllegalArgumentException> { limited(256) } assertThrows<IllegalArgumentException> { limited(256) }
assertThrows<IllegalArgumentException> { auto(-1) } assertThrows<IllegalArgumentException> { auto(-1) }
assertThrows<IllegalArgumentException> { auto(256) }
} }
@Test @Test