2023-06-29 17:49:12 +02:00
|
|
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package org.pgpainless.wot.cli
|
|
|
|
|
2023-07-08 00:54:58 +02:00
|
|
|
import org.pgpainless.PGPainless
|
2023-06-29 19:23:40 +02:00
|
|
|
import org.pgpainless.certificate_store.PGPainlessCertD
|
2023-06-29 17:49:12 +02:00
|
|
|
import org.pgpainless.util.DateUtil
|
2023-07-08 02:05:07 +02:00
|
|
|
import org.pgpainless.wot.KeyRingCertificateStore
|
2023-07-07 15:41:17 +02:00
|
|
|
import org.pgpainless.wot.WebOfTrust
|
2023-07-08 02:05:07 +02:00
|
|
|
import org.pgpainless.wot.api.WoTAPI
|
2023-06-29 17:49:12 +02:00
|
|
|
import org.pgpainless.wot.cli.subcommands.*
|
2023-07-03 20:11:01 +02:00
|
|
|
import org.pgpainless.wot.dijkstra.sq.Fingerprint
|
2023-06-29 17:49:12 +02:00
|
|
|
import org.pgpainless.wot.dijkstra.sq.ReferenceTime
|
2023-06-29 19:23:40 +02:00
|
|
|
import pgp.cert_d.PGPCertificateStoreAdapter
|
|
|
|
import pgp.cert_d.subkey_lookup.InMemorySubkeyLookupFactory
|
|
|
|
import pgp.certificate_store.PGPCertificateStore
|
2023-06-29 17:49:12 +02:00
|
|
|
import picocli.CommandLine
|
2023-06-29 19:03:19 +02:00
|
|
|
import picocli.CommandLine.*
|
2023-06-29 17:49:12 +02:00
|
|
|
import java.io.File
|
|
|
|
import java.util.concurrent.Callable
|
|
|
|
import kotlin.system.exitProcess
|
|
|
|
|
2023-07-07 15:41:17 +02:00
|
|
|
/**
|
|
|
|
* Command Line Interface for pgpainless-wot, modelled after the reference implementation "sq-wot".
|
|
|
|
*
|
|
|
|
* @see <a href="https://gitlab.com/sequoia-pgp/sequoia-wot/">Sequoia Web of Trust Reference Implementation</a>
|
|
|
|
*/
|
2023-06-29 17:49:12 +02:00
|
|
|
@Command(name = "pgpainless-wot",
|
|
|
|
subcommands = [
|
|
|
|
AuthenticateCmd::class,
|
|
|
|
IdentifyCmd::class,
|
|
|
|
ListCmd::class,
|
|
|
|
LookupCmd::class,
|
|
|
|
PathCmd::class,
|
|
|
|
HelpCommand::class
|
|
|
|
]
|
|
|
|
)
|
|
|
|
class WotCLI: Callable<Int> {
|
|
|
|
|
|
|
|
@Option(names = ["--trust-root", "-r"], required = true)
|
2023-07-08 02:05:07 +02:00
|
|
|
var mTrustRoot: Array<String> = arrayOf()
|
2023-06-29 17:49:12 +02:00
|
|
|
|
2023-06-29 19:23:40 +02:00
|
|
|
@ArgGroup(exclusive = true, multiplicity = "1")
|
2023-07-08 02:05:07 +02:00
|
|
|
lateinit var mCertificateSource: CertificateSource
|
2023-06-29 19:23:40 +02:00
|
|
|
|
|
|
|
class CertificateSource {
|
|
|
|
@Option(names = ["--keyring", "-k"], description = ["Specify a keyring file."], required = true)
|
2023-07-08 02:05:07 +02:00
|
|
|
var keyring: Array<File>? = null
|
2023-06-29 19:23:40 +02:00
|
|
|
|
|
|
|
@Option(names = ["--cert-d"], description = ["Specify a pgp-cert-d base directory."], required = true)
|
|
|
|
var pgpCertD: File? = null
|
|
|
|
|
|
|
|
@Option(names = ["--gpg"], description = ["Read trust roots and keyring from GnuPG."])
|
|
|
|
var gpg = false
|
|
|
|
}
|
2023-06-29 17:49:12 +02:00
|
|
|
|
2023-06-29 19:23:40 +02:00
|
|
|
/*
|
2023-06-29 17:49:12 +02:00
|
|
|
@Option(names = ["--network"], description = ["Look for missing certificates on a key server or the WKD."])
|
2023-07-08 00:54:58 +02:00
|
|
|
var network: Boolean = false
|
|
|
|
|
|
|
|
@Option(names = ["--keyserver"], description=["Change the default keyserver"])
|
|
|
|
var keyServer: String = "hkps://keyserver.ubuntu.com"
|
|
|
|
|
|
|
|
@Option(names = ["--gpg-ownertrust"])
|
|
|
|
var gpgOwnertrust: Boolean = false
|
|
|
|
*/
|
2023-06-29 17:49:12 +02:00
|
|
|
|
|
|
|
@Option(names = ["--certification-network"], description = ["Treat the web of trust as a certification network instead of an authentication network."])
|
|
|
|
var certificationNetwork = false
|
|
|
|
|
|
|
|
@Option(names = ["--gossip"], description = ["Find arbitrary paths by treating all certificates as trust-roots with zero trust."])
|
|
|
|
var gossip = false
|
|
|
|
|
2023-07-08 00:54:58 +02:00
|
|
|
@ArgGroup(exclusive = true, multiplicity = "1")
|
2023-07-08 02:05:07 +02:00
|
|
|
lateinit var mTrustAmount: TrustAmount
|
2023-07-08 00:54:58 +02:00
|
|
|
|
|
|
|
class TrustAmount {
|
|
|
|
@Option(names = ["--trust-amount", "-a"], description = ["The required amount of trust."])
|
|
|
|
var amount: Int? = null
|
|
|
|
|
|
|
|
@Option(names = ["--partial"])
|
|
|
|
var partial: Boolean = false
|
|
|
|
|
|
|
|
@Option(names = ["--full"])
|
|
|
|
var full: Boolean = false
|
|
|
|
|
|
|
|
@Option(names = ["--double"])
|
|
|
|
var double: Boolean = false
|
|
|
|
}
|
|
|
|
|
2023-06-29 17:49:12 +02:00
|
|
|
|
|
|
|
@Option(names = ["--time"], description = ["Reference time."])
|
2023-07-08 02:05:07 +02:00
|
|
|
var mTime: String? = null
|
2023-06-29 17:49:12 +02:00
|
|
|
|
2023-07-08 00:54:58 +02:00
|
|
|
@Option(names = ["--known-notation"], description = ["Add a notation to the list of known notations."])
|
|
|
|
var knownNotations: Array<String> = arrayOf()
|
|
|
|
|
2023-07-03 20:11:01 +02:00
|
|
|
private val referenceTime: ReferenceTime
|
|
|
|
get() {
|
2023-07-08 02:05:07 +02:00
|
|
|
return mTime?.let {
|
|
|
|
ReferenceTime.timestamp(DateUtil.parseUTCDate(mTime!!))
|
2023-07-08 00:54:58 +02:00
|
|
|
} ?: ReferenceTime.now()
|
2023-06-29 17:49:12 +02:00
|
|
|
}
|
|
|
|
|
2023-07-08 02:05:07 +02:00
|
|
|
private val trustRoots: List<Fingerprint>
|
2023-07-03 20:11:01 +02:00
|
|
|
get() {
|
2023-07-08 02:05:07 +02:00
|
|
|
if (mCertificateSource.gpg) {
|
|
|
|
return readGpgOwnertrust().plus(mTrustRoot.map { Fingerprint(it) })
|
2023-07-03 20:11:01 +02:00
|
|
|
}
|
|
|
|
|
2023-07-08 02:05:07 +02:00
|
|
|
return mTrustRoot.map { Fingerprint(it) }
|
2023-06-29 19:23:40 +02:00
|
|
|
}
|
|
|
|
|
2023-07-08 02:05:07 +02:00
|
|
|
private val amount: Int
|
|
|
|
get() = when {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
private val certificateStore: PGPCertificateStore
|
2023-07-03 20:11:01 +02:00
|
|
|
get() {
|
2023-07-08 02:05:07 +02:00
|
|
|
if (mCertificateSource.gpg) {
|
|
|
|
return KeyRingCertificateStore(
|
|
|
|
PGPainless.readKeyRing().publicKeyRingCollection(
|
|
|
|
Runtime.getRuntime().exec("/usr/bin/gpg --export").inputStream
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (mCertificateSource.keyring != null) {
|
|
|
|
return KeyRingCertificateStore(
|
|
|
|
mCertificateSource.keyring!!.map {
|
|
|
|
PGPainless.readKeyRing().publicKeyRingCollection(it.inputStream())
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
val certD = PGPainlessCertD.fileBased(
|
|
|
|
mCertificateSource.pgpCertD,
|
|
|
|
InMemorySubkeyLookupFactory())
|
|
|
|
return PGPCertificateStoreAdapter(certD)
|
2023-07-03 20:11:01 +02:00
|
|
|
}
|
2023-06-29 19:23:40 +02:00
|
|
|
|
2023-07-08 02:05:07 +02:00
|
|
|
fun readGpgOwnertrust(): List<Fingerprint> = Runtime.getRuntime()
|
|
|
|
.exec("/usr/bin/gpg --export-ownertrust")
|
|
|
|
.inputStream
|
|
|
|
.bufferedReader()
|
|
|
|
.readLines()
|
|
|
|
.filterNot { it.startsWith("#") }
|
|
|
|
.filterNot { it.isBlank() }
|
|
|
|
.map { it.substring(0, it.indexOf(':')) }
|
|
|
|
.map { Fingerprint(it) }
|
2023-07-08 00:54:58 +02:00
|
|
|
|
2023-06-29 17:49:12 +02:00
|
|
|
/**
|
|
|
|
* Execute the command.
|
|
|
|
*
|
|
|
|
* @return exit code
|
|
|
|
*/
|
|
|
|
override fun call(): Int {
|
2023-07-08 02:05:07 +02:00
|
|
|
require(mTrustRoot.isNotEmpty()) {
|
2023-06-29 17:49:12 +02:00
|
|
|
"Expected at least one trust-root."
|
|
|
|
}
|
|
|
|
|
2023-07-08 00:54:58 +02:00
|
|
|
for (notation in knownNotations) {
|
|
|
|
PGPainless.getPolicy().notationRegistry.addKnownNotation(notation)
|
|
|
|
}
|
2023-06-29 17:49:12 +02:00
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2023-07-03 20:11:01 +02:00
|
|
|
val api: WoTAPI
|
|
|
|
get() {
|
2023-07-07 15:41:17 +02:00
|
|
|
val network = WebOfTrust(certificateStore)
|
|
|
|
.buildNetwork(referenceTime = referenceTime)
|
2023-07-03 20:11:01 +02:00
|
|
|
return WoTAPI(
|
2023-07-07 15:41:17 +02:00
|
|
|
network = network,
|
2023-07-03 20:11:01 +02:00
|
|
|
trustRoots = trustRoots,
|
2023-07-08 02:05:07 +02:00
|
|
|
gossip = gossip,
|
|
|
|
certificationNetwork = certificationNetwork,
|
2023-07-03 20:11:01 +02:00
|
|
|
trustAmount = amount,
|
|
|
|
referenceTime = referenceTime)
|
|
|
|
}
|
|
|
|
|
2023-06-29 17:49:12 +02:00
|
|
|
companion object {
|
2023-07-08 02:05:07 +02:00
|
|
|
|
2023-06-29 17:49:12 +02:00
|
|
|
@JvmStatic
|
2023-07-08 02:05:07 +02:00
|
|
|
fun main(args: Array<String>): Unit = exitProcess(
|
|
|
|
CommandLine(WotCLI()).execute(*args)
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun toString(): String {
|
|
|
|
val source = if (mCertificateSource.gpg) {
|
|
|
|
"gpg"
|
|
|
|
} else {
|
|
|
|
mCertificateSource.pgpCertD ?: mCertificateSource.keyring?.contentToString() ?: "null"
|
|
|
|
}
|
|
|
|
return "trustroot=${trustRoots}, source=$source, gossip=$gossip, amount=$amount," +
|
|
|
|
" referenceTime=${referenceTime.timestamp}, notations=${knownNotations.contentToString()}"
|
2023-06-29 17:49:12 +02:00
|
|
|
}
|
|
|
|
}
|