diff --git a/wkd-java-cli/src/main/java/pgp/wkd/cli/command/Fetch.java b/wkd-java-cli/src/main/java/pgp/wkd/cli/command/Fetch.java index 91d008e..b977bf2 100644 --- a/wkd-java-cli/src/main/java/pgp/wkd/cli/command/Fetch.java +++ b/wkd-java-cli/src/main/java/pgp/wkd/cli/command/Fetch.java @@ -10,7 +10,7 @@ import pgp.wkd.WKDAddressHelper; import pgp.wkd.cli.PGPainlessCertificateParser; import pgp.wkd.cli.RuntimeIOException; import pgp.wkd.discovery.CertificateDiscoverer; -import pgp.wkd.discovery.DefaultCertificateDiscoverer; +import pgp.wkd.discovery.ValidatingCertificateDiscoverer; import pgp.wkd.discovery.DiscoveryResult; import pgp.wkd.discovery.HttpsUrlConnectionCertificateFetcher; import pgp.wkd.exception.MalformedUserIdException; @@ -39,7 +39,7 @@ public class Fetch implements Runnable { ) boolean armor = false; - public static final CertificateDiscoverer DEFAULT_DISCOVERER = new DefaultCertificateDiscoverer( + public static final CertificateDiscoverer DEFAULT_DISCOVERER = new ValidatingCertificateDiscoverer( new PGPainlessCertificateParser(), new HttpsUrlConnectionCertificateFetcher()); private static CertificateDiscoverer discoverer = DEFAULT_DISCOVERER; diff --git a/wkd-java-cli/src/test/java/pgp/wkd/cli/test_suite/TestSuiteTestRunner.java b/wkd-java-cli/src/test/java/pgp/wkd/cli/test_suite/TestSuiteTestRunner.java index e3ee2a2..a7596d3 100644 --- a/wkd-java-cli/src/test/java/pgp/wkd/cli/test_suite/TestSuiteTestRunner.java +++ b/wkd-java-cli/src/test/java/pgp/wkd/cli/test_suite/TestSuiteTestRunner.java @@ -12,7 +12,7 @@ import pgp.wkd.cli.PGPainlessCertificateParser; import pgp.wkd.cli.WKDCLI; import pgp.wkd.cli.command.Fetch; import pgp.wkd.discovery.CertificateDiscoverer; -import pgp.wkd.discovery.DefaultCertificateDiscoverer; +import pgp.wkd.discovery.ValidatingCertificateDiscoverer; import pgp.wkd.discovery.DiscoveryMethod; import pgp.wkd.test_suite.TestCase; import pgp.wkd.test_suite.TestSuite; @@ -43,7 +43,7 @@ public class TestSuiteTestRunner { suite = generator.generateTestSuiteInDirectory(tempFile, DiscoveryMethod.direct); // Fetch certificates from a local directory instead of the internetzzz. - CertificateDiscoverer discoverer = new DefaultCertificateDiscoverer( + CertificateDiscoverer discoverer = new ValidatingCertificateDiscoverer( new PGPainlessCertificateParser(), new DirectoryBasedCertificateFetcher(tempPath)); Fetch.setCertificateDiscoverer(discoverer); } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/AbstractUriCertificateFetcher.java b/wkd-java/src/main/java/pgp/wkd/discovery/AbstractUriCertificateFetcher.java index 7383d89..56490af 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/AbstractUriCertificateFetcher.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/AbstractUriCertificateFetcher.java @@ -12,10 +12,27 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +/** + * Abstract implementation of the {@link CertificateFetcher} interface. + * The purpose of this class is to map {@link #fetchCertificate(WKDAddress, DiscoveryMethod)} + * and {@link #fetchPolicy(WKDAddress, DiscoveryMethod)} calls to {@link #fetchFromUri(URI)}. + * + * A concrete implementation of this class then simply needs to implement the latter method. + */ public abstract class AbstractUriCertificateFetcher implements CertificateFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(CertificateFetcher.class); + /** + * Fetch the contents of the file that the {@link URI} points to from the remote server. + * @param uri uri + * @return file contents + * + * @throws java.net.ConnectException in case the file or host does not exist + * @throws IOException in case of an IO-error + */ + protected abstract InputStream fetchFromUri(URI uri) throws IOException; + @Override public InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException { URI uri = address.getUri(method); @@ -38,14 +55,4 @@ public abstract class AbstractUriCertificateFetcher implements CertificateFetche } } - /** - * Fetch the contents of the file that the {@link URI} points to from the remote server. - * @param uri uri - * @return file contents - * - * @throws java.net.ConnectException in case the file or host does not exist - * @throws IOException in case of an IO-error - */ - protected abstract InputStream fetchFromUri(URI uri) throws IOException; - } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java b/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java index 6b11e77..473c0be 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java @@ -11,10 +11,26 @@ import pgp.wkd.WKDAddressHelper; import java.util.ArrayList; import java.util.List; +/** + * Interface which describes an API to discover OpenPGP certificates via the WKD. + */ public interface CertificateDiscoverer { + /** + * Discover OpenPGP certificates by querying the given
address
via the given
method
. + * + * @param method discovery method + * @param address wkd address + * @return response + */ DiscoveryResponse discover(DiscoveryMethod method, WKDAddress address); + /** + * Discover OpenPGP certificates by {@link WKDAddress}. + * + * @param address address + * @return discovery result + */ default DiscoveryResult discover(WKDAddress address) { List results = new ArrayList<>(); @@ -31,10 +47,26 @@ public interface CertificateDiscoverer { return new DiscoveryResult(results); } + /** + * Discover OpenPGP certificates by email address. + * + * @param email email address + * @return discovery result + * + * @throws MalformedUserIdException in case of a malformed email address + */ default DiscoveryResult discoverByEmail(String email) throws MalformedUserIdException { return discover(WKDAddress.fromEmail(email)); } + /** + * Discover OpenPGP certificates by user-id. + * + * @param userId user-id + * @return discovery result + * + * @throws MalformedUserIdException in case of a malformed user-id + */ default DiscoveryResult discoverByUserId(String userId) throws MalformedUserIdException { return discover(WKDAddressHelper.wkdAddressFromUserId(userId)); } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/CertificateParser.java b/wkd-java/src/main/java/pgp/wkd/discovery/CertificateParser.java index 001db2b..5e678b0 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/CertificateParser.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/CertificateParser.java @@ -10,7 +10,22 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +/** + * Interface for an OpenPGP certificate parser class. + */ public interface CertificateParser { + /** + * Read a list of OpenPGP certificates from the given input stream. + * The input stream contains binary OpenPGP certificate data. + * + * The result is a list of {@link CertificateAndUserIds}, where {@link CertificateAndUserIds#getUserIds()} only + * contains validly bound user-ids. + * + * @param inputStream input stream of binary OpenPGP certificates + * @return list of parsed certificates and their user-ids + * + * @throws IOException in case of an IO error + */ List read(InputStream inputStream) throws IOException; } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResponse.java b/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResponse.java index 1dd0fb6..e1b6ac1 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResponse.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResponse.java @@ -14,6 +14,9 @@ import javax.annotation.Nullable; import java.net.URI; import java.util.List; +/** + * A single response to a WKD query. + */ public final class DiscoveryResponse { private final DiscoveryMethod method; @@ -49,47 +52,100 @@ public final class DiscoveryResponse { this.missingPolicyFileException = missingPolicyFileException; } + /** + * Return the method that was used to fetch this response. + * + * @return method + */ @Nonnull public DiscoveryMethod getMethod() { return method; } + /** + * Return the WKD-Address which is queried. + * + * @return address + */ @Nonnull public WKDAddress getAddress() { return address; } + /** + * Return the URI that was queried against. + * + * @return URI + */ public URI getUri() { return getAddress().getUri(getMethod()); } + /** + * Return true, if the query was successful. + * That is, if there were no fetching errors, and if the server presented a policy. + * + * @return success + */ public boolean isSuccessful() { return !hasFetchingFailure() && hasPolicy(); } + /** + * Return the list of acceptable certificates that were returned by the WKD service. + * + * @return certificates + */ @Nonnull public List getCertificates() { return certificates; } + /** + * Return a list containing all rejected certificates returned by the WKD service. + * Certificates can be rejected for several reasons such as a missing user-id, or if the certificate is malformed. + * + * @return list of rejected certificates + */ @Nonnull public List getRejectedCertificates() { return rejectedCertificates; } + /** + * Return the cause of fetching errors, if any. + * A fetching failure might be e.g. a connection exception in case the WKD service cannot be reached. + * + * @return fetching failure + */ @Nullable public Throwable getFetchingFailure() { return fetchingFailure; } + /** + * Return true, if the result contains acceptable certificates. + * + * @return true if the response has certificates + */ public boolean hasCertificates() { return certificates != null && !certificates.isEmpty(); } + /** + * Return true, if there was a fetching failure. + * + * @return true if failure + */ public boolean hasFetchingFailure() { return fetchingFailure != null; } + /** + * Return true, if the WKD service presented a policy. + * + * @return true if policy available + */ public boolean hasPolicy() { return getPolicy() != null; } @@ -99,12 +155,18 @@ public final class DiscoveryResponse { return policy; } - public static Builder builder(@Nonnull DiscoveryMethod discoveryMethod, @Nonnull WKDAddress address) { - Builder builder = new Builder(discoveryMethod, address); - return builder; + /** + * Builder for {@link DiscoveryResponse}. + * + * @param discoveryMethod method used for discovery + * @param address WKD address + * @return builder + */ + static Builder builder(@Nonnull DiscoveryMethod discoveryMethod, @Nonnull WKDAddress address) { + return new Builder(discoveryMethod, address); } - public static class Builder { + static class Builder { private DiscoveryMethod discoveryMethod; private WKDAddress address; @@ -114,37 +176,37 @@ public final class DiscoveryResponse { private WKDPolicy policy; private MissingPolicyFileException missingPolicyFileException; - public Builder(DiscoveryMethod discoveryMethod, WKDAddress address) { + Builder(DiscoveryMethod discoveryMethod, WKDAddress address) { this.discoveryMethod = discoveryMethod; this.address = address; } - public Builder setAcceptableCertificates(List acceptableCertificates) { + Builder setAcceptableCertificates(List acceptableCertificates) { this.acceptableCertificates = acceptableCertificates; return this; } - public Builder setRejectedCertificates(List rejectedCertificates) { + Builder setRejectedCertificates(List rejectedCertificates) { this.rejectedCertificates = rejectedCertificates; return this; } - public Builder setFetchingFailure(Throwable throwable) { + Builder setFetchingFailure(Throwable throwable) { this.fetchingFailure = throwable; return this; } - public Builder setPolicy(WKDPolicy policy) { + Builder setPolicy(WKDPolicy policy) { this.policy = policy; return this; } - public Builder setMissingPolicyFileException(MissingPolicyFileException exception) { + Builder setMissingPolicyFileException(MissingPolicyFileException exception) { this.missingPolicyFileException = exception; return this; } - public DiscoveryResponse build() { + DiscoveryResponse build() { return new DiscoveryResponse( discoveryMethod, address, diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java b/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java index 4633fc6..0030812 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java @@ -14,14 +14,28 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +/** + * Result of discovering an OpenPGP certificate via WKD. + */ public class DiscoveryResult { - private List items; + private final List items; + /** + * Create a {@link DiscoveryResult} from a list of {@link DiscoveryResponse DiscoveryResponses}. + * Usually the list contains one or two responses (one for each {@link DiscoveryMethod}. + * + * @param items responses + */ public DiscoveryResult(@Nonnull List items) { this.items = items; } + /** + * Return the list of acceptable certificates that were discovered. + * + * @return certificates + */ @Nonnull public List getCertificates() { List certificates = new ArrayList<>(); @@ -34,6 +48,11 @@ public class DiscoveryResult { return certificates; } + /** + * Return true, if at least one {@link DiscoveryResponse} was successful and contained acceptable certificates. + * + * @return success + */ public boolean isSuccessful() { for (DiscoveryResponse item : items) { if (item.isSuccessful() && item.hasCertificates()) { @@ -69,7 +88,7 @@ public class DiscoveryResult { private void throwCertNotFetchableException() { Throwable cause = null; - for (DiscoveryResponse response : getItems()) { + for (DiscoveryResponse response : getResponses()) { // Find the most "useful" exception. // Rejections are more useful than fetching failures if (!response.getRejectedCertificates().isEmpty()) { @@ -82,19 +101,13 @@ public class DiscoveryResult { throw new CertNotFetchableException("Could not fetch certificates.", cause); } + /** + * Return the list of responses. + * + * @return responses + */ @Nonnull - public List getItems() { + public List getResponses() { return items; } - - @Nonnull - public List getFailedItems() { - List fails = new ArrayList<>(); - for (DiscoveryResponse item : items) { - if (!item.isSuccessful()) { - fails.add(item); - } - } - return fails; - } } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java b/wkd-java/src/main/java/pgp/wkd/discovery/ValidatingCertificateDiscoverer.java similarity index 90% rename from wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java rename to wkd-java/src/main/java/pgp/wkd/discovery/ValidatingCertificateDiscoverer.java index ae5ff9b..2613ce8 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/ValidatingCertificateDiscoverer.java @@ -16,12 +16,16 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; -public class DefaultCertificateDiscoverer implements CertificateDiscoverer { +/** + * Default implementation of the {@link CertificateDiscoverer}. + * This implementation validates the received certificates. + */ +public class ValidatingCertificateDiscoverer implements CertificateDiscoverer { protected final CertificateParser reader; protected final CertificateFetcher fetcher; - public DefaultCertificateDiscoverer(CertificateParser reader, CertificateFetcher fetcher) { + public ValidatingCertificateDiscoverer(CertificateParser reader, CertificateFetcher fetcher) { this.reader = reader; this.fetcher = fetcher; } diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java b/wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java index 5007d5d..5d4cfc1 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java @@ -10,6 +10,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +/** + * Class describing the contents of a policy file. + * The WKD policy file is found at ".well-known/policy" + */ public final class WKDPolicy { public static final String KEYWORD_MAILBOX_ONLY = "mailbox-only"; @@ -83,23 +87,69 @@ public final class WKDPolicy { return new WKDPolicy(mailboxOnly, daneOnly, authSubmit, protocolVersion, submissionAddress); } + /** + * Return
true
if the
mailbox-only
flag is set. + * + * The mail server provider does only accept keys with only a mailbox in the User ID. + * In particular User IDs with a real name in addition to the mailbox will be rejected as invalid. + * + * @return whether mailbox-only flag is set + */ public boolean isMailboxOnly() { return mailboxOnly; } + /** + * Return
true
if the
dane-only
flag is set. + * + * The mail server provider does not run a Web Key Directory but only an OpenPGP DANE service. + * The Web Key Directory Update protocol is used to update the keys for the DANE service. + * + * @return whether dane-only flag is set + */ public boolean isDaneOnly() { return daneOnly; } + /** + * Return
true
if the
auth-submit
flag is set. + * + * The submission of the mail to the server is done using an authenticated connection. + * Thus the submitted key will be published immediately without any confirmation request. + * + * @return whether auth-submit flag is set + */ public boolean isAuthSubmit() { return authSubmit; } + /** + * Return the protocol version. + * + * This keyword can be used to explicitly claim the support of a specific version of the Web Key Directory + * update protocol. + * This is in general not needed but implementations may have workarounds for providers which only support + * an old protocol version. + * If these providers update to a newer version they should add this keyword so that the implementation + * can disable the workaround. + * The value is an integer corresponding to the respective draft revision number. + * + * @return value of the protocol-version field + */ @Nullable public Integer getProtocolVersion() { return protocolVersion; } + /** + * Return the
submission-address
. + * + * An alternative way to specify the submission address. + * The value is the addr-spec part of the address to send requests to this server. + * If this keyword is used in addition to the submission-address file, both MUST have the same value. + * + * @return value of the submission-address field + */ @Nullable public String getSubmissionAddress() { return submissionAddress;