1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-17 00:54:50 +02:00
pgpainless/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt

241 lines
8.5 KiB
Kotlin
Raw Normal View History

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
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
import org.pgpainless.wot.WebOfTrust
2023-07-13 16:53:35 +02:00
import org.pgpainless.wot.cli.format.Formatter
2023-07-08 02:05:07 +02:00
import org.pgpainless.wot.api.WoTAPI
2023-07-13 16:53:35 +02:00
import org.pgpainless.wot.cli.format.HumanReadableFormatter
2023-06-29 17:49:12 +02:00
import org.pgpainless.wot.cli.subcommands.*
2023-07-09 13:03:57 +02:00
import org.pgpainless.wot.network.Fingerprint
import org.pgpainless.wot.network.ReferenceTime
2023-07-11 01:34:07 +02:00
import org.pgpainless.wot.network.Root
import org.pgpainless.wot.network.Roots
import pgp.cert_d.PGPCertificateStoreAdapter
2023-07-11 14:39:37 +02:00
import pgp.cert_d.SpecialNames
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
2023-07-11 14:39:37 +02:00
import java.text.SimpleDateFormat
2023-06-29 17:49:12 +02:00
import java.util.concurrent.Callable
import kotlin.system.exitProcess
/**
* 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> {
2023-07-11 01:34:07 +02:00
@Option(names = ["--trust-root", "-r"])
2023-07-08 02:05:07 +02:00
var mTrustRoot: Array<String> = arrayOf()
2023-06-29 17:49:12 +02:00
@ArgGroup(exclusive = true, multiplicity = "1")
2023-07-08 02:05:07 +02:00
lateinit var mCertificateSource: CertificateSource
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
@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 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"
2023-07-11 01:34:07 +02:00
*/
2023-07-08 00:54:58 +02:00
@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-11 01:34:07 +02:00
@ArgGroup(exclusive = true)
var mTrustAmount: TrustAmount = 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()
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-11 01:34:07 +02:00
private val trustRoots: Roots
get() {
2023-07-11 14:39:37 +02:00
var trustRootFingerprints = mTrustRoot.map { Fingerprint(it) }.map { Root(it) }
if (mCertificateSource.gpg || gpgOwnertrust) {
trustRootFingerprints = trustRootFingerprints.plus(readGpgOwnertrust())
}
2023-07-11 14:39:37 +02:00
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)
}
2023-07-08 02:05:07 +02:00
private val amount: Int
get() = when {
2023-07-11 14:39:37 +02:00
mTrustAmount.amount != null -> mTrustAmount.amount!! // --amount=XY
2023-07-08 02:05:07 +02:00
mTrustAmount.partial -> 40 // --partial
mTrustAmount.full -> 120 // --full
mTrustAmount.double -> 240 // --double
2023-07-11 14:39:37 +02:00
else -> if (certificationNetwork) 1200 else 120 // default 120, if --certification-network -> 1200
2023-07-08 02:05:07 +02:00
}
private val certificateStore: PGPCertificateStore
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-13 16:53:35 +02:00
val formatter: Formatter = HumanReadableFormatter()
2023-07-11 14:39:37 +02:00
fun readGpgOwnertrust(): List<Root> = Runtime.getRuntime()
2023-07-08 02:05:07 +02:00
.exec("/usr/bin/gpg --export-ownertrust")
.inputStream
.bufferedReader()
.readLines()
2023-07-11 14:39:37 +02:00
.asSequence()
2023-07-08 02:05:07 +02:00
.filterNot { it.startsWith("#") }
.filterNot { it.isBlank() }
2023-07-11 14:39:37 +02:00
.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()
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
}
val api: WoTAPI
get() {
val network = WebOfTrust(certificateStore)
.buildNetwork(referenceTime = referenceTime)
return WoTAPI(
network = network,
trustRoots = trustRoots,
2023-07-08 02:05:07 +02:00
gossip = gossip,
certificationNetwork = certificationNetwork,
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)
)
2023-07-11 14:39:37 +02:00
@JvmStatic
val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
2023-07-08 02:05:07 +02:00
}
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
}
}