diff --git a/.reuse/dep5 b/.reuse/dep5 index b8bb6be..b613adc 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -13,3 +13,8 @@ Source: https://pgpainless.org Files: gradle* Copyright: 2015 the original author or authors. License: Apache-2.0 + +# Woodpecker build files +Files: .woodpecker/* +Copyright: 2022 the original author or authors. +License: Apache-2.0 diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml new file mode 100644 index 0000000..f504b44 --- /dev/null +++ b/.woodpecker/.build.yml @@ -0,0 +1,12 @@ +pipeline: + run: + image: gradle:7.5-jdk8 + commands: + - git checkout $CI_COMMIT_BRANCH + # Code works + - gradle test + # Code is clean + - gradle check javadocAll + # Code has coverage + - gradle jacocoRootReport coveralls + secrets: [COVERALLS_REPO_TOKEN] diff --git a/.woodpecker/.reuse.yml b/.woodpecker/.reuse.yml new file mode 100644 index 0000000..58f17e6 --- /dev/null +++ b/.woodpecker/.reuse.yml @@ -0,0 +1,7 @@ +# Code is licensed properly +# See https://reuse.software/ +pipeline: + reuse: + image: fsfe/reuse:latest + commands: + - reuse lint \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2f77f3f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ + + +# Changelog + +## 0.1.3 +- Bump `bc-util` to `1.75` +- Bump `jackson-databind` to `2.15.2` +- Add support for resource bundles for i18n + +## 0.1.2 +- Bump `slf4j` to `1.7.36` +- Bump `logback` to `1.2.11` +- Bump `lombok` to `1.18.24` +- CLI: Set `keys.openpgp.org` as default key server +- Add name and description to parent command + +## 0.1.1 +- Add `vks-java-cli`: CLI frontend for `vks-java` +- Bump Bouncy Castle dependencies to `1.71` + +## 0.1.0 +- Initial release + - `vks-java`: Client side API to communicate with Verifying Key Servers diff --git a/README.md b/README.md index 5b6369b..7a6897f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@ SPDX-License-Identifier: Apache-2.0 # Verifying Key Server - Client API for Java +[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/vks-java/status.svg)](https://ci.codeberg.org/PGPainless/vks-java) +[![Coverage Status](https://coveralls.io/repos/github/pgpainless/vks-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/vks-java?branch=main) +[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/vks-java)](https://api.reuse.software/info/github.com/pgpainless/vks-java) + Client-side API for fetching keys from - and publishing keys to - Verifying OpenPGP Key Servers (VKS). An example implementation of a Verifying Key Server is [Hagrid](https://gitlab.com/hagrid-keyserver/hagrid), which is running https://keys.openpgp.org. + +## Modules + +This repository contains the following modules: + +* [vks-java](/vks-java): Client-side Java API for communicating with VKS servers +* [vks-java-cli](/vks-java-cli): CLI frontend for `vks-java` diff --git a/build.gradle b/build.gradle index 8738719..ce23326 100644 --- a/build.gradle +++ b/build.gradle @@ -70,9 +70,6 @@ allprojects { } project.ext { - junitVersion = '5.8.2' - slf4jVersion = '1.7.32' - logbackVersion = '1.2.10' rootConfigDir = new File(rootDir, 'config') gitCommit = getGitCommit() isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI')) diff --git a/settings.gradle b/settings.gradle index a636bb0..25d5c44 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,5 @@ rootProject.name = 'VKS-Java' -include 'vks-java' \ No newline at end of file +include 'vks-java', + 'vks-java-cli' \ No newline at end of file diff --git a/version.gradle b/version.gradle index ef567db..c9d1def 100644 --- a/version.gradle +++ b/version.gradle @@ -4,9 +4,17 @@ allprojects { ext { - shortVersion = '0.1.0' - isSnapshot = false + shortVersion = '0.1.4' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 + bouncycastleVersion = '1.75' + junitVersion = '5.8.2' + jsrVersion = '3.0.2' + slf4jVersion = '1.7.36' + logbackVersion = '1.2.11' + lombokVersion = '1.18.24' + picocliVersion = '4.6.3' + jacksonDataBindVersion = '2.15.2' } } diff --git a/vks-java-cli/README.md b/vks-java-cli/README.md new file mode 100644 index 0000000..8dc777a --- /dev/null +++ b/vks-java-cli/README.md @@ -0,0 +1,53 @@ + + +# VKS-Java-CLI + +[![javadoc](https://javadoc.io/badge2/org.pgpainless/vks-java-cli/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/vks-java-cli) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/vks-java-cli)](https://search.maven.org/artifact/org.pgpainless/vks-java-cli) + +Command Line Frontend for VKS-Java + +## Building + +To build the CLI app, use `gradle build`. An archive containing an executable can then be found in `vks-java-cli/build/distributions/`. +Extract it and navigate to the `bin` subdirectory, where you can find `vks-java-cli`/`vks-java-cli.bat` executables. + +## Usage Examples + +```shell +Usage: vkscli [COMMAND] +Commands: + help Displays help information about the specified command + get Retrieve an OpenPGP certificate from the key server + upload Upload an OpenPGP certificate to the key server + request-verification Request verification for unverified user-ids +``` + +By default, the CLI application uses `https://keys.openpgp.org` as key server. +To use another VKS, use the option `--key-server https://your.key.server` in any command. + +To retrieve a key from a Verifying Key Server, use the `get` subcommand: + +```shell +$ ./vks-java-cli get -e vanitasvitae@fsfe.org > foo.asc +$ ./vks-java-cli get -f 7F9116FEA90A5983936C7CFAA027DB2F3E1E118A > foo.asc +$ ./vks-java-cli get -i -2535611045697927659 > foo.asc +``` + +To upload a key, use the `upload` subcommand: + +```shell +$ ./vks-java-cli upload -r < foo.asc +``` + +The option `-r` automatically requests verification mails for unpublished mail addresses. + +To manually request verification mails, use the `request-verification` subcommand, passing it the token acquired by the `upload` command: + +```shell +$ ./vks-java-cli request-verification -t -a foo@bar.baz -a other@email.address +``` diff --git a/vks-java-cli/build.gradle b/vks-java-cli/build.gradle new file mode 100644 index 0000000..bf714f8 --- /dev/null +++ b/vks-java-cli/build.gradle @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id 'application' +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +dependencies { + // JUnit + testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + // VKS-Java + implementation project(":vks-java") + + // CLI + implementation "info.picocli:picocli:$picocliVersion" +} + +application { + mainClass = 'pgp.vks.client.cli.VKSCLI' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/vks-java-cli/src/main/java/pgp/vks/client/cli/GetCmd.java b/vks-java-cli/src/main/java/pgp/vks/client/cli/GetCmd.java new file mode 100644 index 0000000..43ea165 --- /dev/null +++ b/vks-java-cli/src/main/java/pgp/vks/client/cli/GetCmd.java @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.vks.client.cli; + +import pgp.vks.client.Get; +import pgp.vks.client.VKS; +import picocli.CommandLine; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.Locale; +import java.util.ResourceBundle; + +@CommandLine.Command( + name = "get", + resourceBundle = "msg_get") +public class GetCmd implements Runnable { + + @CommandLine.Mixin + VKSCLI.KeyServerMixin keyServerMixin; + + @CommandLine.ArgGroup(exclusive = true, multiplicity = "1") + Exclusive by; + + static class Exclusive { + @CommandLine.Option(names = {"-f", "--by-fingerprint"}) + String fingerprint; + + @CommandLine.Option(names = {"-i", "--by-keyid"}) + Long keyId; + + @CommandLine.Option(names = {"-e", "--by-email"}) + String email; + } + + private final ResourceBundle msg; + + public GetCmd() { + msg = ResourceBundle.getBundle("msg_get", Locale.getDefault()); + } + + public void run() { + VKS vks; + try { + vks = keyServerMixin.parent.getApi(); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + + Get get = vks.get(); + InputStream inputStream = null; + try { + if (by.fingerprint != null) { + inputStream = get.byFingerprint(by.fingerprint); + } else if (by.keyId != null) { + inputStream = get.byKeyId(by.keyId); + } else if (by.email != null) { + inputStream = get.byEmail(by.email); + } else { + throw new IllegalArgumentException(msg.getString("error.missing_by_option")); + } + + int read; + byte[] buf = new byte[4096]; + while ((read = inputStream.read(buf)) != -1) { + System.out.write(buf, 0, read); + } + + } catch (IOException e) { + throw new AssertionError(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + } + } +} diff --git a/vks-java-cli/src/main/java/pgp/vks/client/cli/RequestVerificationCmd.java b/vks-java-cli/src/main/java/pgp/vks/client/cli/RequestVerificationCmd.java new file mode 100644 index 0000000..2fba390 --- /dev/null +++ b/vks-java-cli/src/main/java/pgp/vks/client/cli/RequestVerificationCmd.java @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.vks.client.cli; + +import pgp.vks.client.RequestVerify; +import pgp.vks.client.VKS; +import picocli.CommandLine; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +@CommandLine.Command( + name = "request-verification", + resourceBundle = "msg_request_verification") +public class RequestVerificationCmd implements Runnable { + + @CommandLine.Mixin + VKSCLI.KeyServerMixin keyServerMixin; + + @CommandLine.Option(names = {"-t", "--token"}, + required = true, arity = "1", paramLabel = "TOKEN") + String token; + + @CommandLine.Option(names = {"-l", "--locale"}) + List locale = Arrays.asList("en_US", "en_GB"); + + @CommandLine.Option(names = {"-e", "--email"}, required = true, arity = "1..*") + String[] addresses = new String[0]; + + private final ResourceBundle msg; + + public RequestVerificationCmd() { + msg = ResourceBundle.getBundle("msg_request_verification", Locale.getDefault()); + } + + @Override + public void run() { + VKS vks; + try { + vks = keyServerMixin.parent.getApi(); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + + RequestVerify requestVerify = vks.requestVerification(); + try { + RequestVerify.Response response = requestVerify + .forEmailAddresses(addresses) + .execute(token, locale); + System.out.printf(msg.getString("output.mails_sent"), response.getKeyFingerprint()); + System.out.printf(msg.getString("output.token"), response.getToken()); + System.out.println(msg.getString("output.status")); + for (String address : response.getStatus().keySet()) { + System.out.println("\t" + address + "\t" + response.getStatus().get(address)); + } + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/vks-java-cli/src/main/java/pgp/vks/client/cli/UploadCmd.java b/vks-java-cli/src/main/java/pgp/vks/client/cli/UploadCmd.java new file mode 100644 index 0000000..c3927bc --- /dev/null +++ b/vks-java-cli/src/main/java/pgp/vks/client/cli/UploadCmd.java @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.vks.client.cli; + +import pgp.vks.client.RequestVerify; +import pgp.vks.client.Status; +import pgp.vks.client.Upload; +import pgp.vks.client.VKS; +import pgp.vks.client.exception.CertCannotBePublishedException; +import picocli.CommandLine; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +@CommandLine.Command( + name = "upload", + resourceBundle = "msg_upload") +public class UploadCmd implements Runnable { + + @CommandLine.Mixin + VKSCLI.KeyServerMixin keyServerMixin; + + @CommandLine.Option(names = {"-r", "--request-verification"}) + boolean requestVerification; + + private final ResourceBundle msg; + + public UploadCmd() { + msg = ResourceBundle.getBundle("msg_upload", Locale.getDefault()); + } + + public void run() { + VKS vks; + try { + vks = keyServerMixin.parent.getApi(); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + + Upload upload = vks.upload(); + try { + Upload.Response response = upload.cert(System.in); + + // Unpublished mail addresses + List unpublished = new ArrayList<>(); + int maxMailLen = 0; + for (String address : response.getStatus().keySet()) { + Status status = response.getStatus().get(address); + if (address.length() > maxMailLen) { + maxMailLen = address.length(); + } + if (status != Status.published && status != Status.revoked) { + unpublished.add(address); + } + } + + String msgUpload = String.format(msg.getString("output.uploaded_key"), + response.getKeyFingerprint(), response.getToken()); + System.out.println(msgUpload); + + String msgStatus = msg.getString("output.status"); + if (!requestVerification || unpublished.isEmpty()) { + System.out.println(msgStatus); + for (String address : response.getStatus().keySet()) { + Status status = response.getStatus().get(address); + System.out.format("%-" + maxMailLen + "s %s\n", address, status); + } + return; + } + + RequestVerify.Response verifyResponse = vks.requestVerification().forEmailAddresses(unpublished.toArray(new String[0])) + .execute(response.getToken()); + System.out.println(msgStatus); + for (String address : verifyResponse.getStatus().keySet()) { + Status status = response.getStatus().get(address); + System.out.format("%-" + maxMailLen + "s %s\n", address, status); + } + } catch (CertCannotBePublishedException e) { + throw new AssertionError(e.getMessage()); + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/vks-java-cli/src/main/java/pgp/vks/client/cli/VKSCLI.java b/vks-java-cli/src/main/java/pgp/vks/client/cli/VKSCLI.java new file mode 100644 index 0000000..142e192 --- /dev/null +++ b/vks-java-cli/src/main/java/pgp/vks/client/cli/VKSCLI.java @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.vks.client.cli; + +import pgp.vks.client.VKS; +import pgp.vks.client.VKSImpl; +import picocli.CommandLine; + +import java.net.MalformedURLException; +import java.util.Locale; +import java.util.ResourceBundle; + +@CommandLine.Command( + name = "vks", + resourceBundle = "msg_vks", + subcommands = { + CommandLine.HelpCommand.class, + GetCmd.class, + UploadCmd.class, + RequestVerificationCmd.class + } +) +public class VKSCLI { + + String keyServer = "https://keys.openpgp.org"; + + public static void main(String[] args) { + int exitCode = execute(args); + if (exitCode != 0) { + System.exit(exitCode); + } + } + + public static int execute(String[] args) { + CommandLine cmd = new CommandLine(VKSCLI.class); + cmd.setExitCodeExceptionMapper(new CommandLine.IExitCodeExceptionMapper() { + @Override + public int getExitCode(Throwable exception) { + return 1; + } + }); + cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("msg_help", Locale.getDefault())); + return cmd.setCommandName("vkscli") + .execute(args); + } + + public VKS getApi() throws MalformedURLException { + return new VKSImpl(keyServer); + } + + public static class KeyServerMixin { + + @CommandLine.ParentCommand + VKSCLI parent; + + @CommandLine.Option(names = "--key-server", + paramLabel = "KEYSERVER") + public void setKeyServer(String keyServer) { + parent.keyServer = keyServer; + } + } +} diff --git a/vks-java-cli/src/main/java/pgp/vks/client/cli/package-info.java b/vks-java-cli/src/main/java/pgp/vks/client/cli/package-info.java new file mode 100644 index 0000000..d6af2a8 --- /dev/null +++ b/vks-java-cli/src/main/java/pgp/vks/client/cli/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Command Line Interface for VKS-Java. + */ +package pgp.vks.client.cli; diff --git a/vks-java-cli/src/main/resources/msg_get.properties b/vks-java-cli/src/main/resources/msg_get.properties new file mode 100644 index 0000000..70ab33d --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_get.properties @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Retrieve an OpenPGP certificate from the key server +by-fingerprint=Retrieve a key by its fingerprint (NOT prefixed with '0x') +by-keyid=Retrieve a key by its decimal key ID or that of one of its subkeys +by-email=Retrieve a key by email address +error.missing_by_option=Missing --by-* option. +key-server=Address of the Verifying Key Server.%nDefaults to 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_get_de.properties b/vks-java-cli/src/main/resources/msg_get_de.properties new file mode 100644 index 0000000..b13c717 --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_get_de.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Empfange ein OpenPGP Zertifikat vom Schlüsselserver +by-fingerprint=Finde das Zertifikat anhand seines Fingerabdrucks (OHNE Präfix '0x') +by-keyid=Finde das Zertifikat anhand seiner Schlüssel-ID oder der eines seiner Unterschlüssel +by-email=Finde das Zertifikat anhand einer E-Mail-Adresse +error.missing_by_option=Fehlende --by-* Option. +usage.synopsisHeading=Nutzung:\u0020 +usage.optionListHeading=Optionen:%n + +key-server=Adresse des verifizierenden Schlüsselservers.%nStandardmäßig: 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_help.properties b/vks-java-cli/src/main/resources/msg_help.properties new file mode 100644 index 0000000..f288ae3 --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_help.properties @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Displays help information about the specified command +usage.synopsisHeading=Usage:\u0020 diff --git a/vks-java-cli/src/main/resources/msg_help_de.properties b/vks-java-cli/src/main/resources/msg_help_de.properties new file mode 100644 index 0000000..304bb42 --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_help_de.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Zeige Hilfetext für den angegebenen Befehl +usage.synopsisHeading=Nutzung:\u0020 +usage.optionListHeading=Optionen:%n diff --git a/vks-java-cli/src/main/resources/msg_request_verification.properties b/vks-java-cli/src/main/resources/msg_request_verification.properties new file mode 100644 index 0000000..b8d64ae --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_request_verification.properties @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Request verification for unverified user-ids +token=Access token. Can be retrieved by uploading the certificate. +locale=Locale (language) for the verification mail +email=Email addresses to request a verification mail for +output.mails_sent=Verification E-Mails for certificate %s have been sent.%n +output.token=Token: %s%n +output.status=Status: +key-server=Address of the Verifying Key Server.%nDefaults to 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_request_verification_de.properties b/vks-java-cli/src/main/resources/msg_request_verification_de.properties new file mode 100644 index 0000000..773630c --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_request_verification_de.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Fordere Verifikation von unveröffentlichten Nutzeridentitäten an +token=Zugangstoken. Kann durch das Hochladen des Zertifikates erhalten werden. +locale=Gebietsschema (Sprache) für die E-Mail-Verifikation +email=E-Mail-Adresse für die eine Verifikation angefragt werden soll +output.mails_sent=E-Mail-Verifikationen für Zertifikat %s wurden versendet.%n%n +output.token=Zugangstoken: %s%n +output.status=Status: +usage.synopsisHeading=Nutzung:\u0020 +usage.optionListHeading=Optionen:%n + +key-server=Adresse des verifizierenden Schlüsselservers.%nStandardmäßig: 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_upload.properties b/vks-java-cli/src/main/resources/msg_upload.properties new file mode 100644 index 0000000..d2fa507 --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_upload.properties @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Upload an OpenPGP certificate to the key server +request-verification=Request verification mails for unpublished email addresses +output.uploaded_key=Uploaded key: %s%nToken: %s +output.status=Status: +key-server=Address of the Verifying Key Server.%nDefaults to 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_upload_de.properties b/vks-java-cli/src/main/resources/msg_upload_de.properties new file mode 100644 index 0000000..cb38d3d --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_upload_de.properties @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Lade ein OpenPGP Zertifikat auf den Schlüsselserver hoch +request-verification=Fordere E-Mailverifikation für unveröffentlichte E-Mail-Adressen an +output.uploaded_key=Hochgeladenes Zertifikat: %s%nToken: %s +output.status=Status: +usage.synopsisHeading=Nutzung:\u0020 +usage.optionListHeading=Optionen:%n + +key-server=Adresse des verifizierenden Schlüsselservers.%nStandardmäßig: 'https://keys.openpgp.org' diff --git a/vks-java-cli/src/main/resources/msg_vks.properties b/vks-java-cli/src/main/resources/msg_vks.properties new file mode 100644 index 0000000..5ba0b01 --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_vks.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Interact with Verifying Key Servers +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading=Commands:%n diff --git a/vks-java-cli/src/main/resources/msg_vks_de.properties b/vks-java-cli/src/main/resources/msg_vks_de.properties new file mode 100644 index 0000000..5b3099d --- /dev/null +++ b/vks-java-cli/src/main/resources/msg_vks_de.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Interagiere mit verifizierenden Schlüsselservern +usage.synopsisHeading=Nutzung:\u0020 +usage.commandListHeading=Befehle:%n diff --git a/vks-java/README.md b/vks-java/README.md new file mode 100644 index 0000000..f499bdb --- /dev/null +++ b/vks-java/README.md @@ -0,0 +1,28 @@ + + +# VKS-Java + +[![javadoc](https://javadoc.io/badge2/org.pgpainless/vks-java/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/vks-java) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/vks-java)](https://search.maven.org/artifact/org.pgpainless/vks-java) + +Client Side API for Communicating with Verifying Key Servers. + +```java +VKS vks = new VKSImpl("https://keys.openpgp.org/"); + +// Key Discovery via Email, key-id or fingerprint +InputStream bobsKey = vks.get().byEmail("bob@pgpainless.org"); + +// Upload Key to the VKS +InputStream myKey = ... +Upload.Response uploadResponse = vks.upload().cert(myKey); + +// Request email verification of user-ids +RequestVerify.Response verifyResponse = vks.requestVerification() + .forEmailAddress("bob@pgpainless.org") + .execute(uploadResponse.getToken()); +``` diff --git a/vks-java/build.gradle b/vks-java/build.gradle index f290450..61c30d2 100644 --- a/vks-java/build.gradle +++ b/vks-java/build.gradle @@ -13,23 +13,24 @@ repositories { } dependencies { + // JUnit testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" // Arrays.areEqual, Base64... - implementation("org.bouncycastle:bcutil-jdk15on:1.70") + implementation "org.bouncycastle:bcutil-jdk15to18:$bouncycastleVersion" // @Nonnull, @Nullable... - implementation "com.google.code.findbugs:jsr305:3.0.2" + implementation "com.google.code.findbugs:jsr305:$jsrVersion" // Lombok (@SneakyThrows...) - compileOnly 'org.projectlombok:lombok:1.18.22' - annotationProcessor 'org.projectlombok:lombok:1.18.22' - testCompileOnly 'org.projectlombok:lombok:1.18.22' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' + compileOnly "org.projectlombok:lombok:$lombokVersion" + annotationProcessor "org.projectlombok:lombok:$lombokVersion" + testCompileOnly "org.projectlombok:lombok:$lombokVersion" + testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion" // JSON - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' + implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDataBindVersion" } test { diff --git a/vks-java/src/main/java/pgp/vks/client/RequestVerify.java b/vks-java/src/main/java/pgp/vks/client/RequestVerify.java index 3d7fb5d..e28f69f 100644 --- a/vks-java/src/main/java/pgp/vks/client/RequestVerify.java +++ b/vks-java/src/main/java/pgp/vks/client/RequestVerify.java @@ -9,20 +9,54 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +/** + * Request email verification for email addresses of user-ids on a certificate. + */ public interface RequestVerify { + /** + * Request email verification for the given email address. + * + * @param emailAddress email address + * @return builder + */ default ForEmailAddresses forEmailAddress(String emailAddress) { return forEmailAddresses(emailAddress); } + /** + * Request email verification for one or more email addresses. + * + * @param emailAddresses email addresses + * @return builder + */ ForEmailAddresses forEmailAddresses(String... emailAddresses); interface ForEmailAddresses { + /** + * Execute the verification request using the given
token
, retrieved earlier by uploading the + * certificate to the server. + * + * @param token access token to authorize the request + * @return servers successful response + * + * @throws IOException in case of an error + */ default Response execute(String token) throws IOException { return execute(token, Arrays.asList("en_US", "en_GB")); } + /** + * Execute the verification request using the given
token
, retrieved earlier by uploading the + * certificate to the server. + * + * @param token access token to authorize the request + * @param locale list of preferred locales for the verification emails + * @return servers successful response + * + * @throws IOException in case of an error + */ Response execute(String token, List locale) throws IOException; } @@ -41,14 +75,29 @@ public interface RequestVerify { this.token = token; } + /** + * Return the uppercase OpenPGP fingerprint of the certificate. + * + * @return fingerprint + */ public String getKeyFingerprint() { return keyFingerprint; } + /** + * Return a map of {@link Status States} of email addresses of user-ids on the certificate after requesting + * verification. + * @return status map + */ public Map getStatus() { return status; } + /** + * Return an access token to be used to make further requests to the API. + * + * @return token + */ public String getToken() { return token; } diff --git a/vks-java/src/main/java/pgp/vks/client/Status.java b/vks-java/src/main/java/pgp/vks/client/Status.java index a8ead89..5fa35d3 100644 --- a/vks-java/src/main/java/pgp/vks/client/Status.java +++ b/vks-java/src/main/java/pgp/vks/client/Status.java @@ -4,6 +4,9 @@ package pgp.vks.client; +/** + * Enum representing different states an email address of a user-id on a certificate can be in. + */ public enum Status { /** diff --git a/vks-java/src/main/java/pgp/vks/client/VKSImpl.java b/vks-java/src/main/java/pgp/vks/client/VKSImpl.java index b760c88..e273ab9 100644 --- a/vks-java/src/main/java/pgp/vks/client/VKSImpl.java +++ b/vks-java/src/main/java/pgp/vks/client/VKSImpl.java @@ -27,6 +27,11 @@ public class VKSImpl implements VKS { this.api = new URLMapper(vksService); } + /** + * Return a {@link VKSImpl} instance targeting the
https://keys.openpgp.org
VKS. + * + * @return VKS instance + */ @SneakyThrows public static VKS keysDotOpenPgpDotOrg() { return new VKSImpl("https://keys.openpgp.org"); diff --git a/vks-java/src/main/java/pgp/vks/client/exception/CertCannotBePublishedException.java b/vks-java/src/main/java/pgp/vks/client/exception/CertCannotBePublishedException.java index 07b5fc3..2e9eca1 100644 --- a/vks-java/src/main/java/pgp/vks/client/exception/CertCannotBePublishedException.java +++ b/vks-java/src/main/java/pgp/vks/client/exception/CertCannotBePublishedException.java @@ -6,6 +6,9 @@ package pgp.vks.client.exception; import java.net.ConnectException; +/** + * Exception gets thrown when a public key certificate cannot be published for some reason. + */ public class CertCannotBePublishedException extends ConnectException { public CertCannotBePublishedException(String errorMessage) { diff --git a/vks-java/src/main/java/pgp/vks/client/exception/CertNotFoundException.java b/vks-java/src/main/java/pgp/vks/client/exception/CertNotFoundException.java index 039eea7..e18056d 100644 --- a/vks-java/src/main/java/pgp/vks/client/exception/CertNotFoundException.java +++ b/vks-java/src/main/java/pgp/vks/client/exception/CertNotFoundException.java @@ -6,12 +6,11 @@ package pgp.vks.client.exception; import java.net.ConnectException; +/** + * Exception gets thrown when the queried OpenPGP certificate cannot be found on the server. + */ public class CertNotFoundException extends ConnectException { - public CertNotFoundException() { - super(); - } - public CertNotFoundException(String message) { super(message); } diff --git a/vks-java/src/main/java/pgp/vks/client/exception/UnsupportedApiException.java b/vks-java/src/main/java/pgp/vks/client/exception/UnsupportedApiException.java index 7f017b6..a1e399f 100644 --- a/vks-java/src/main/java/pgp/vks/client/exception/UnsupportedApiException.java +++ b/vks-java/src/main/java/pgp/vks/client/exception/UnsupportedApiException.java @@ -4,6 +4,10 @@ package pgp.vks.client.exception; +/** + * Exception gets thrown when an unsupported API {@link pgp.vks.client.VKS.Version} is used. + * E.g. user wants to use some command using v2 API, but implementation only supports v1. + */ public class UnsupportedApiException extends RuntimeException { public UnsupportedApiException(String message) { diff --git a/vks-java/src/main/java/pgp/vks/client/v1/dto/ErrorResponseDto.java b/vks-java/src/main/java/pgp/vks/client/v1/dto/ErrorResponseDto.java index 3208791..71567c0 100644 --- a/vks-java/src/main/java/pgp/vks/client/v1/dto/ErrorResponseDto.java +++ b/vks-java/src/main/java/pgp/vks/client/v1/dto/ErrorResponseDto.java @@ -6,6 +6,11 @@ package pgp.vks.client.v1.dto; import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Error response that gets returned by the server if a POST request fails. + * + * @see VKS API Documentation + */ public class ErrorResponseDto { private final String error; @@ -14,6 +19,11 @@ public class ErrorResponseDto { this.error = error; } + /** + * Return the error message. + * + * @return error message + */ @JsonProperty("error") public String getError() { return error; diff --git a/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadRequestDto.java b/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadRequestDto.java index e351a39..865deb1 100644 --- a/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadRequestDto.java +++ b/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadRequestDto.java @@ -14,6 +14,11 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +/** + * Request for uploading a certificate to the VKS. + * + * @see VKS API Documentation + */ public class UploadRequestDto { private static final byte[] ARMOR_HEADER = "-----BEGIN PGP PUBLIC KEY BLOCK-----".getBytes(Charset.forName("UTF8")); @@ -24,6 +29,15 @@ public class UploadRequestDto { this.keytext = keytext; } + /** + * Create an {@link UploadRequestDto} from an ASCII armored or binary OpenPGP certificate which is read from the + * given
certInStream
. + * + * @param certInStream {@link InputStream} containing the ASCII armored or binary OpenPGP certificate + * @return request DTO + * + * @throws IOException in case of an IO error + */ public static UploadRequestDto fromInputStream(InputStream certInStream) throws IOException { ByteArrayOutputStream certBuf = new ByteArrayOutputStream(); Streams.pipeAll(certInStream, certBuf); @@ -31,11 +45,26 @@ public class UploadRequestDto { return fromBytes(certBuf.toByteArray()); } + /** + * Create an {@link UploadRequestDto} from an ASCII armored or binary OpenPGP certificate which is read from the + * given
keytext
byte array. + * + * @param keytext byte array containing the ASCII armored or binary OpenPGP certificate + * @return request DTO + */ public static UploadRequestDto fromBytes(byte[] keytext) { String armoredOrBase64 = new String(base64IfNecessary(keytext)); return new UploadRequestDto(armoredOrBase64); } + /** + * Prepare a serialized OpenPGP certificate for upload. + * If the given
certBytes
contain an ASCII armored OpenPGP certificate, do nothing. + * Otherwise, consider the bytes to be the binary representation of an OpenPGP certificate and wrap it in Base64 encoding. + * + * @param certBytes certificate bytes + * @return Unmodified ASCII armored, or base64 encoded binary OpenPGP certificate + */ private static byte[] base64IfNecessary(byte[] certBytes) { if (!Arrays.areEqual(certBytes, 0, ARMOR_HEADER.length, ARMOR_HEADER, 0, ARMOR_HEADER.length)) { certBytes = Base64.encode(certBytes); diff --git a/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadResponseDto.java b/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadResponseDto.java index b2298c9..03d132b 100644 --- a/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadResponseDto.java +++ b/vks-java/src/main/java/pgp/vks/client/v1/dto/UploadResponseDto.java @@ -11,6 +11,11 @@ import pgp.vks.client.Upload; import java.util.HashMap; import java.util.Map; +/** + * The VKS servers response to a successful upload. + * + * @see VKS API Documentation + */ public class UploadResponseDto { private final String key_fpr; @@ -25,21 +30,41 @@ public class UploadResponseDto { this.token = token; } + /** + * Uppercase fingerprint of the uploaded OpenPGP certificate. + * + * @return fingerprint + */ @JsonProperty("key_fpr") public String getKeyFingerprint() { return key_fpr; } + /** + * Access token which can be used for a limited time to request verification emails for user-ids on the certificate. + * + * @return access token + */ @JsonProperty("token") public String getToken() { return token; } + /** + * Map of {@link Status States} of email addresses of user-ids on the certificate. + * + * @return email status map + */ @JsonProperty("status") public Map getStatus() { return new HashMap<>(status); } + /** + * Convert the DTO into an entity. + * + * @return entity + */ public Upload.Response toEntity() { return new Upload.Response(getKeyFingerprint(), getStatus(), getToken()); } diff --git a/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationRequestDto.java b/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationRequestDto.java index 999be64..47c2608 100644 --- a/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationRequestDto.java +++ b/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationRequestDto.java @@ -8,6 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; +/** + * Request for email verification of one or more unpublished/pending user-ids. + * + * @see VKS API Documentation + */ public class VerificationRequestDto { private final String token; @@ -22,16 +27,31 @@ public class VerificationRequestDto { this.locale = locale; } + /** + * Return the token which was previously required by uploading a certificate using an {@link UploadRequestDto}. + * + * @return access token used to authenticate the request + */ @JsonProperty("token") public String getToken() { return token; } + /** + * {@link List} of email addresses for which to request email verification. + * + * @return email addresses + */ @JsonProperty("addresses") public List getAddresses() { return addresses; } + /** + * {@link List} of preferred locales for the verification mails. + * + * @return locale list + */ @JsonProperty("locale") public List getLocale() { return locale; diff --git a/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationResponseDto.java b/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationResponseDto.java index 78a9597..bbd6e14 100644 --- a/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationResponseDto.java +++ b/vks-java/src/main/java/pgp/vks/client/v1/dto/VerificationResponseDto.java @@ -10,6 +10,11 @@ import pgp.vks.client.Status; import java.util.Map; +/** + * VKS servers response to a successful verification request. + * + * @see VKS API Documentation + */ public class VerificationResponseDto { private final String key_fpr; @@ -24,21 +29,41 @@ public class VerificationResponseDto { this.token = token; } + /** + * Uppercase OpenPGP fingerprint of the certificate. + * + * @return fingerprint + */ @JsonProperty("key_fpr") public String getKeyFingerprint() { return key_fpr; } + /** + * Access token which can be used to authenticate further verification requests. + * + * @return token + */ @JsonProperty("token") public String getToken() { return token; } + /** + * Map of {@link Status States} of email addresses of user-ids on the certificate. + * + * @return email address status map + */ @JsonProperty("status") public Map getStatus() { return status; } + /** + * Convert this DTO to an entity. + * + * @return entity + */ public RequestVerify.Response toEntity() { return new RequestVerify.Response(getKeyFingerprint(), getStatus(), getToken()); }