1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-17 09:04:50 +02:00

Implement authenticate, identify, list, lookup

This commit is contained in:
Paul Schaub 2023-07-12 20:46:30 +02:00
parent c98d6c4708
commit ad270aa212
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
11 changed files with 143 additions and 78 deletions

View file

@ -9,12 +9,14 @@ import org.pgpainless.certificate_store.PGPainlessCertD
import org.pgpainless.util.DateUtil
import org.pgpainless.wot.KeyRingCertificateStore
import org.pgpainless.wot.WebOfTrust
import org.pgpainless.wot.api.Binding
import org.pgpainless.wot.api.WoTAPI
import org.pgpainless.wot.cli.subcommands.*
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 org.pgpainless.wot.query.Path
import pgp.cert_d.PGPCertificateDirectory
import pgp.cert_d.PGPCertificateStoreAdapter
import pgp.cert_d.SpecialNames
@ -223,7 +225,6 @@ class WotCLI: Callable<Int> {
@JvmStatic
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
}
override fun toString(): String {

View file

@ -54,7 +54,7 @@ class AuthenticateCmd: Callable<Int> {
override fun call(): Int {
val result = parent.api.authenticate(AuthenticateAPI.Arguments(
Fingerprint(fingerprint), userId, email))
print(formatResult(result))
formatResult(result)
if (result.percentage < 100) {
return -1
}
@ -64,30 +64,10 @@ class AuthenticateCmd: Callable<Int> {
/**
* Format the [AuthenticateAPI.Result] as a [String] which can be printed to standard out.
*/
internal fun formatResult(result: AuthenticateAPI.Result): String {
internal fun formatResult(result: AuthenticateAPI.Result) {
if (result.percentage < 100) {
return "No paths found."
println("No paths found.")
}
val sb = StringBuilder()
sb.appendLine("[✓] ${result.fingerprint} ${result.userId}: fully authenticated (${result.percentage}%)")
for ((pIndex, path: Path) in result.paths.paths.withIndex()) {
sb.appendLine(" Path #${pIndex + 1} of ${result.paths.paths.size}, trust amount ${path.amount}:")
for ((cIndex, certification) in path.certifications.withIndex()) {
val issuerUserId = certification.issuer.userIds.keys.firstOrNull()?.let { " (\"${it}\")" } ?: ""
when (cIndex) {
0 -> {
sb.appendLine("${certification.issuer.fingerprint}${issuerUserId}")
}
else -> {
sb.appendLine("${certification.issuer.fingerprint}${issuerUserId}")
}
}
sb.appendLine(" │ certified the following binding on ${WotCLI.dateFormat.format(certification.creationTime)}")
}
sb.appendLine("${result.fingerprint} \"${result.userId}\"")
}
return sb.toString()
println(result.binding.toConsoleOut(result.targetAmount, WotCLI.dateFormat))
}
}

View file

@ -10,6 +10,7 @@ import org.pgpainless.wot.network.Fingerprint
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.Parameters
import java.text.SimpleDateFormat
import java.util.concurrent.Callable
@Command(name = "identify")
@ -29,35 +30,24 @@ class IdentifyCmd: Callable<Int> {
override fun call(): Int {
val api = parent.api
val result = api.identify(IdentifyAPI.Arguments(Fingerprint(fingerprint)))
println(formatResult(result))
println(formatResult(result, api.trustAmount, WotCLI.dateFormat))
return exitCode(result)
}
fun formatResult(result: IdentifyAPI.Result): String {
if (result.target == null || result.paths.isEmpty()) {
fun formatResult(result: IdentifyAPI.Result, targetAmount: Int, dateFormat: SimpleDateFormat): String {
if (result.bindings.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}\"")
}
}
result.bindings.forEach {
appendLine(it.toConsoleOut(targetAmount, dateFormat))
}
}
}
fun exitCode(result: IdentifyAPI.Result): Int {
return if(result.paths.isEmpty()) -1 else 0
return if(result.bindings.isEmpty()) -1 else 0
}
}

View file

@ -22,6 +22,12 @@ class ListCmd: Callable<Int> {
*/
override fun call(): Int {
val api = parent.api
TODO("Not yet implemented")
val result = api.list()
println(buildString {
result.bindings.forEach {
appendLine(it.toConsoleOut(api.trustAmount, WotCLI.dateFormat))
}
})
return 0
}
}

View file

@ -4,6 +4,7 @@
package org.pgpainless.wot.cli.subcommands
import org.pgpainless.wot.api.LookupAPI
import org.pgpainless.wot.cli.WotCLI
import picocli.CommandLine.*
import java.util.concurrent.Callable
@ -27,6 +28,10 @@ class LookupCmd: Callable<Int> {
*/
override fun call(): Int {
val api = parent.api
TODO("Not yet implemented")
val result = api.lookup(LookupAPI.Arguments(userId, email))
result.bindings.forEach {
println(it.toConsoleOut(api.trustAmount, WotCLI.dateFormat))
}
return if (result.bindings.isEmpty()) -1 else 0
}
}

View file

@ -5,7 +5,6 @@
package org.pgpainless.wot.api
import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.query.Paths
/**
* Authenticate a binding.
@ -36,13 +35,9 @@ interface AuthenticateAPI {
* @param targetAmount the targeted trust amount required to achieve full authentication
* @param paths the number of paths
*/
data class Result(val fingerprint: Fingerprint, val userId: String, private val targetAmount: Int, val paths: Paths) {
/**
* Percentage of authentication. 100% means fully authenticated binding.
*/
data class Result(val binding: Binding, val targetAmount: Int) {
val percentage: Int
get() = paths.amount * 100 / targetAmount
get() = binding.percentage(targetAmount)
}
}

View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.wot.api
import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.query.Path
import org.pgpainless.wot.query.Paths
import java.text.SimpleDateFormat
data class Binding(val fingerprint: Fingerprint, val userId: String, val paths: Paths) {
/**
* Percentage of authentication. 100% means fully authenticated binding.
*/
fun percentage(targetAmount: Int): Int {
return paths.amount * 100 / targetAmount
}
fun toConsoleOut(targetAmount: Int, dateFormat: SimpleDateFormat): String {
return buildString {
val percentage = percentage(targetAmount)
val authLevel = when (paths.amount) {
in 0..39 -> "not authenticated"
in 40..119 -> "partially authenticated"
in 120 .. 239 -> "fully authenticated"
else -> {if (percentage < 0) "not authenticated" else "doubly authenticated"}
}
append(if (percentage >= 100) "[✓] " else "[ ] ")
appendLine("$fingerprint $userId: $authLevel (${percentage(targetAmount)}%)")
for ((pIndex, path: Path) in paths.paths.withIndex()) {
appendLine(" Path #${pIndex + 1} of ${paths.paths.size}, trust amount ${path.amount}:")
for ((cIndex, certification) in path.certifications.withIndex()) {
val issuerUserId = certification.issuer.userIds.keys.firstOrNull()?.let { " (\"$it\")" } ?: ""
when (cIndex) {
0 -> {
appendLine("${certification.issuer.fingerprint}$issuerUserId")
}
else -> {
appendLine("${certification.issuer.fingerprint}$issuerUserId")
}
}
appendLine(" │ certified the following binding on ${dateFormat.format(certification.creationTime)}")
}
appendLine("$fingerprint \"$userId\"")
}
}
}
}

View file

@ -14,12 +14,5 @@ interface IdentifyAPI {
data class Arguments(val fingerprint: Fingerprint)
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
}
}
data class Result(val bindings: List<Binding>, val targetAmount: Int)
}

View file

@ -4,11 +4,9 @@
package org.pgpainless.wot.api
import org.pgpainless.wot.query.Paths
interface ListAPI {
fun list(): Result
data class Result(val paths: Paths)
data class Result(val bindings: List<Binding>)
}

View file

@ -4,13 +4,11 @@
package org.pgpainless.wot.api
import org.pgpainless.wot.query.Paths
interface LookupAPI {
fun lookup(arguments: Arguments): Result
data class Arguments(val userId: String, val email: Boolean = false)
data class Result(val paths: Paths)
data class Result(val bindings: List<Binding>)
}

View file

@ -5,11 +5,7 @@
package org.pgpainless.wot.api
import org.pgpainless.wot.dijkstra.Query
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.network.*
import org.pgpainless.wot.query.Paths
/**
@ -21,7 +17,6 @@ import org.pgpainless.wot.query.Paths
* @param certificationNetwork if true, all certifications are treated as delegations with infinite trust depth and no regular expressions
* @param trustAmount minimum trust amount
* @param referenceTime reference time at which the web of trust is evaluated
* @param knownNotationRegistry registry of known notations
*/
class WoTAPI(
val network: Network,
@ -46,32 +41,87 @@ class WoTAPI(
override fun authenticate(arguments: AuthenticateAPI.Arguments): AuthenticateAPI.Result {
val query = Query(network, trustRoots, certificationNetwork)
val paths = query.authenticate(arguments.fingerprint, arguments.userId, trustAmount)
return AuthenticateAPI.Result(arguments.fingerprint, arguments.userId, trustAmount, paths)
return AuthenticateAPI.Result(Binding(arguments.fingerprint, arguments.userId, paths), trustAmount)
}
override fun identify(arguments: IdentifyAPI.Arguments): IdentifyAPI.Result {
val cert = network.nodes[arguments.fingerprint] ?: return IdentifyAPI.Result(mutableMapOf(), null, trustAmount)
val allPaths = mutableMapOf<String, Paths>()
val cert = network.nodes[arguments.fingerprint]
?: return IdentifyAPI.Result(listOf(), trustAmount)
val bindings = mutableListOf<Binding>()
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
bindings.add(Binding(arguments.fingerprint, it, paths))
}
}
return IdentifyAPI.Result(allPaths, cert, trustAmount)
return IdentifyAPI.Result(bindings, trustAmount)
}
override fun list(): ListAPI.Result {
TODO("Not yet implemented")
val bindings = mutableListOf<Binding>()
network.nodes.forEach {
bindings.addAll(identify(IdentifyAPI.Arguments(it.key)).bindings)
}
return ListAPI.Result(bindings)
}
override fun lookup(arguments: LookupAPI.Arguments): LookupAPI.Result {
TODO("Not yet implemented")
val userId = arguments.userId
val email = arguments.email
println("Looking up $userId email=$email")
val candidates = network.nodes.values.mapNotNull { node ->
val matches = node.mapToMatchingUserIds(userId, email)
if (matches.isEmpty()) {
null
} else {
node to matches
}
}
println("found ${candidates.size} candidates:")
candidates.joinToString {
"${it.first.fingerprint} ${it.second.joinToString { u -> u }}"
}
val results = mutableListOf<Binding>()
candidates.forEach {
val node = it.first
val userIds = it.second
for (mUserId in userIds) {
authenticate(AuthenticateAPI.Arguments(node.fingerprint, mUserId, email)).let { result ->
if (result.binding.paths.paths.isNotEmpty()) {
results.add(result.binding)
}
}
}
}
return LookupAPI.Result(results)
}
override fun path(arguments: PathAPI.Arguments): PathAPI.Result {
TODO("Not yet implemented")
}
private fun Node.mapToMatchingUserIds(userId: String, email: Boolean): List<String> {
val list = mutableListOf<String>()
userIds.forEach { entry ->
if (email) {
if (entry.key.contains("<$userId>")) {
list.add(entry.key)
}
} else {
if (entry.key == userId) {
list.add(entry.key)
}
}
}
return list
}
}