1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-05 03:55:58 +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.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<Int> {
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<Int> {
return PGPCertificateStoreAdapter(certD)
}
fun readGpgOwnertrust(): List<Fingerprint> = Runtime.getRuntime()
fun readGpgOwnertrust(): List<Root> = 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<Int> {
CommandLine(WotCLI()).execute(*args)
)
@JvmStatic
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
}
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."])
var email = false
private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
/**
* Execute the command.
@ -83,7 +83,7 @@ class AuthenticateCmd: Callable<Int> {
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}\"")
}

View file

@ -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<Int> {
*/
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
}
}

View file

@ -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<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.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<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 {

View file

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