mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 04:17:59 +01:00
Implement authenticate, identify, list, lookup
This commit is contained in:
parent
c98d6c4708
commit
ad270aa212
11 changed files with 143 additions and 78 deletions
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>)
|
||||
}
|
||||
|
|
|
@ -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>)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue