diff --git a/pgpainless-wot-cli/build.gradle b/pgpainless-wot-cli/build.gradle new file mode 100644 index 00000000..6ff93b76 --- /dev/null +++ b/pgpainless-wot-cli/build.gradle @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + } +} + +plugins { + id 'application' + id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion" + id "com.github.johnrengelman.shadow" version "6.1.0" +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + + // JUnit + testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" + + // CLI + implementation "info.picocli:picocli:4.6.3" + annotationProcessor "info.picocli:picocli-codegen:4.6.3" + + // Logging + testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + + // Web of Trust + implementation(project(":pgpainless-wot")) + + // Web Key Directory + implementation("org.pgpainless:wkd-java:0.1.1") +} + +mainClassName = 'org.pgpainless.wot.cli.WotCLI' + +application { + mainClass = mainClassName +} + +jar { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + manifest { + attributes 'Main-Class': "$mainClassName" + } + + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } { + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + } +} + +test { + useJUnitPlatform() +} diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt new file mode 100644 index 00000000..3e258244 --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli + +import org.pgpainless.util.DateUtil +import org.pgpainless.wot.cli.subcommands.* +import org.pgpainless.wot.dijkstra.sq.ReferenceTime +import picocli.CommandLine +import picocli.CommandLine.Command +import picocli.CommandLine.HelpCommand +import picocli.CommandLine.Option +import java.io.File +import java.util.* +import java.util.concurrent.Callable +import kotlin.system.exitProcess + +@Command(name = "pgpainless-wot", + subcommands = [ + AuthenticateCmd::class, + IdentifyCmd::class, + ListCmd::class, + LookupCmd::class, + PathCmd::class, + HelpCommand::class + ] +) +class WotCLI: Callable { + + @Option(names = ["--trust-root", "-r"], required = true) + var trustRoot: Array = arrayOf() + + @Option(names = ["--keyring", "-k"], description = ["Specify a keyring file."], required = true) + var keyring: File? = null + + /* + @Option(names = ["--gpg"], description = ["Read trust roots and keyring from GnuPG."]) + var gpg = false + + @Option(names = ["--network"], description = ["Look for missing certificates on a key server or the WKD."]) + var keyServer = "hkps://keyserver.ubuntu.com" + + @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 + */ + + @Option(names = ["--trust-amount", "-a"], description = ["The required amount of trust."]) + var amount = 1200 + + @Option(names = ["--time"], description = ["Reference time."]) + var time: String? = null + + fun getReferenceTime(): ReferenceTime { + return if (time == null) { + ReferenceTime.now() + } else { + val date = DateUtil.parseUTCDate(time) + ReferenceTime.timestamp(date) + } + } + + /** + * Execute the command. + * + * @return exit code + */ + override fun call(): Int { + require(trustRoot.isNotEmpty()) { + "Expected at least one trust-root." + } + + + return 0 + } + + companion object { + @JvmStatic + fun main(args: Array): Unit = exitProcess(CommandLine(WotCLI()).execute(*args)) + } +} diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt new file mode 100644 index 00000000..ac091996 --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli.subcommands + +import org.pgpainless.wot.api.AuthenticateAPI +import org.pgpainless.wot.cli.WotCLI +import picocli.CommandLine +import picocli.CommandLine.Command +import picocli.CommandLine.Parameters +import picocli.CommandLine.ParentCommand +import java.util.concurrent.Callable + +@Command(name = "authenticate") +class AuthenticateCmd: Callable { + + @ParentCommand + lateinit var parent: WotCLI + + @Parameters(index = "0") + lateinit var fingerprint: String + + @Parameters(index = "1") + lateinit var userId: String + + @CommandLine.Option(names = ["--email"], description = ["Consider all user-IDs that contain the given email address."]) + var email = false + + /** + * Execute the command. + * @return exit code + */ + override fun call(): Int { + val api = AuthenticateAPI() + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/Format.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/Format.kt new file mode 100644 index 00000000..6048e5a2 --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/Format.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli.subcommands + +/** + * Enum listing possible output formats. + */ +enum class Format(private val displayName: String) { + humanReadable("human-readable"), + dot("dot") + ; + + override fun toString(): String = displayName + + companion object { + @JvmStatic + fun fromString(displayName: String): Format { + for (format in Format.values()) { + if (format.displayName == displayName) { + return format + } + } + throw NoSuchElementException("Invalid displayName $displayName") + } + } +} \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt new file mode 100644 index 00000000..7eaef04a --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli.subcommands + +import org.pgpainless.wot.api.IdentifyAPI +import org.pgpainless.wot.cli.WotCLI +import picocli.CommandLine +import picocli.CommandLine.Command +import picocli.CommandLine.Parameters +import java.util.concurrent.Callable + +@Command(name = "identify") +class IdentifyCmd: Callable { + + @CommandLine.ParentCommand + lateinit var parent: WotCLI + + @Parameters(index = "0", description = ["Certificate fingerprint."]) + lateinit var fingerprint: String + + /** + * Execute the command. + * + * @return exit code + */ + override fun call(): Int { + val api = IdentifyAPI() + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt new file mode 100644 index 00000000..1d42a5fb --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli.subcommands + +import org.pgpainless.wot.api.ListAPI +import org.pgpainless.wot.cli.WotCLI +import picocli.CommandLine +import picocli.CommandLine.Command +import java.util.concurrent.Callable + +@Command(name = "list", description = ["Find all bindings that can be authenticated for all certificates."]) +class ListCmd: Callable { + + @CommandLine.ParentCommand + lateinit var parent: WotCLI + + /** + * Execute the command. + * + * @return exit code + */ + override fun call(): Int { + val api = ListAPI() + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt new file mode 100644 index 00000000..2209335d --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +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 + +@Command(name = "lookup") +class LookupCmd: Callable { + + @ParentCommand + lateinit var parent: WotCLI + + @Option(names = ["--email"], description = ["Consider all user-IDs that contain the given email address."]) + var email = false + + @Parameters(index = "0", description = ["User-ID"]) + lateinit var userId: String + + /** + * Execute the command. + * + * @return exit code + */ + override fun call(): Int { + val api = LookupAPI() + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/PathCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/PathCmd.kt new file mode 100644 index 00000000..a9048ae3 --- /dev/null +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/PathCmd.kt @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.cli.subcommands + +import org.pgpainless.wot.api.PathAPI +import org.pgpainless.wot.cli.WotCLI +import picocli.CommandLine +import picocli.CommandLine.Command +import picocli.CommandLine.Parameters +import java.util.concurrent.Callable + +@Command(name = "path", description = ["Verify and lint a path."]) +class PathCmd: Callable { + + @CommandLine.ParentCommand + lateinit var parent: WotCLI + + @Parameters(index = "*", + arity = "2..*", + description = ["List of fingerprints starting with the roots fingerprint or key ID and ending with the target certificates fingerprint or key ID and a user ID."], + ) + lateinit var keyIdsOrFingerprints: Array + + /** + * Execute the command. + * + * @return exit code + */ + override fun call(): Int { + val api = PathAPI() + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/pgpainless-wot/build.gradle b/pgpainless-wot/build.gradle index 9e877417..04baf940 100644 --- a/pgpainless-wot/build.gradle +++ b/pgpainless-wot/build.gradle @@ -2,9 +2,19 @@ // // SPDX-License-Identifier: Apache-2.0 +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + } +} + plugins { id 'java-library' id 'java-test-fixtures' + id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion" } group 'org.pgpainless' @@ -14,6 +24,8 @@ repositories { } dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" // JUnit testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" @@ -27,7 +39,7 @@ dependencies { testImplementation "ch.qos.logback:logback-classic:$logbackVersion" implementation(project(":pgpainless-core")) - implementation(project(":wot-dijkstra")) + api(project(":wot-dijkstra")) // Certificate store api "org.pgpainless:pgp-certificate-store:$certDJavaVersion" diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/api/AuthenticateAPI.kt b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/AuthenticateAPI.kt new file mode 100644 index 00000000..fad45fe4 --- /dev/null +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/AuthenticateAPI.kt @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +/** + * Authenticate a binding. + * A binding is a pair consisting of a certificate and a User ID. + */ +class AuthenticateAPI() { + +} \ No newline at end of file diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/api/IdentifyAPI.kt b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/IdentifyAPI.kt new file mode 100644 index 00000000..b0b83800 --- /dev/null +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/IdentifyAPI.kt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +class IdentifyAPI { +} \ No newline at end of file diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/api/ListAPI.kt b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/ListAPI.kt new file mode 100644 index 00000000..d6a63817 --- /dev/null +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/ListAPI.kt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +class ListAPI { +} \ No newline at end of file diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/api/LookupAPI.kt b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/LookupAPI.kt new file mode 100644 index 00000000..f833743b --- /dev/null +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/LookupAPI.kt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +class LookupAPI { +} \ No newline at end of file diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/api/PathAPI.kt b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/PathAPI.kt new file mode 100644 index 00000000..251935af --- /dev/null +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/api/PathAPI.kt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +class PathAPI { +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 1f62e559..f31f198c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,5 +8,6 @@ include 'pgpainless-core', 'pgpainless-sop', 'pgpainless-cli', 'pgpainless-wot', + 'pgpainless-wot-cli', 'wot-dijkstra'