Refactoring and dynamic test suite

This commit is contained in:
Paul Schaub 2022-03-17 15:27:28 +01:00
parent 38ef283313
commit 3af16baa20
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
23 changed files with 287 additions and 162 deletions

View File

@ -10,6 +10,7 @@ group 'org.pgpainless'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
@ -20,7 +21,7 @@ dependencies {
testImplementation 'com.ginsberg:junit5-system-exit:1.1.2'
testImplementation 'org.mockito:mockito-core:4.3.1'
implementation("org.pgpainless:pgpainless-cert-d:0.1.0")
implementation("org.pgpainless:pgpainless-cert-d:0.1.1")
implementation project(':wkd-java')
implementation "info.picocli:picocli:4.6.3"

View File

@ -12,14 +12,14 @@ import org.pgpainless.certificate_store.CertificateFactory;
import org.pgpainless.key.info.KeyRingInfo;
import pgp.certificate_store.Certificate;
import pgp.wkd.CertificateAndUserIds;
import pgp.wkd.CertificateReader;
import pgp.wkd.discovery.CertificateParser;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class CertificateReaderImpl implements CertificateReader {
public class CertificateParserImpl implements CertificateParser {
@Override
public List<CertificateAndUserIds> read(InputStream inputStream) throws IOException {
List<CertificateAndUserIds> certificatesAndUserIds = new ArrayList<>();

View File

@ -1,21 +1,21 @@
package pgp.wkd.cli;
import pgp.wkd.AbstractDiscover;
import pgp.wkd.CertificateReader;
import pgp.wkd.HttpUrlConnectionWKDFetcher;
import pgp.wkd.WKDFetcher;
import pgp.wkd.discovery.CertificateDiscoveryImplementation;
import pgp.wkd.discovery.CertificateParser;
import pgp.wkd.discovery.HttpUrlConnectionCertificateFetcher;
import pgp.wkd.discovery.CertificateFetcher;
public class DiscoverImpl extends AbstractDiscover {
public class DiscoverImpl extends CertificateDiscoveryImplementation {
public DiscoverImpl() {
super(new CertificateReaderImpl(), new HttpUrlConnectionWKDFetcher());
super(new CertificateParserImpl(), new HttpUrlConnectionCertificateFetcher());
}
public DiscoverImpl(WKDFetcher fetcher) {
super(new CertificateReaderImpl(), fetcher);
public DiscoverImpl(CertificateFetcher fetcher) {
super(new CertificateParserImpl(), fetcher);
}
public DiscoverImpl(CertificateReader certificateReader, WKDFetcher fetcher) {
super(certificateReader, fetcher);
public DiscoverImpl(CertificateParser certificateParser, CertificateFetcher fetcher) {
super(certificateParser, fetcher);
}
}

View File

@ -7,13 +7,13 @@ package pgp.wkd.cli.command;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.util.io.Streams;
import pgp.certificate_store.Certificate;
import pgp.wkd.Discover;
import pgp.wkd.HttpUrlConnectionWKDFetcher;
import pgp.wkd.discovery.CertificateDiscoverer;
import pgp.wkd.discovery.HttpUrlConnectionCertificateFetcher;
import pgp.wkd.MalformedUserIdException;
import pgp.wkd.WKDAddress;
import pgp.wkd.WKDAddressHelper;
import pgp.wkd.WKDDiscoveryResult;
import pgp.wkd.WKDFetcher;
import pgp.wkd.discovery.DiscoveryResult;
import pgp.wkd.discovery.CertificateFetcher;
import pgp.wkd.cli.CertNotFetchableException;
import pgp.wkd.cli.DiscoverImpl;
import picocli.CommandLine;
@ -42,14 +42,14 @@ public class Fetch implements Runnable {
boolean armor = false;
// TODO: Better way to inject fetcher implementation
public static WKDFetcher fetcher = new HttpUrlConnectionWKDFetcher();
public static CertificateFetcher fetcher = new HttpUrlConnectionCertificateFetcher();
@Override
public void run() {
Discover discover = new DiscoverImpl(fetcher);
CertificateDiscoverer certificateDiscoverer = new DiscoverImpl(fetcher);
WKDAddress address = addressFromUserId(userId);
WKDDiscoveryResult result = discover.discover(address);
DiscoveryResult result = certificateDiscoverer.discover(address);
if (!result.isSuccessful()) {
throw new CertNotFetchableException("Cannot fetch cert.");

View File

@ -4,9 +4,9 @@
package pgp.wkd.cli.test_suite;
import pgp.wkd.DiscoveryMethod;
import pgp.wkd.discovery.DiscoveryMethod;
import pgp.wkd.WKDAddress;
import pgp.wkd.WKDFetcher;
import pgp.wkd.discovery.CertificateFetcher;
import java.io.File;
import java.io.FileInputStream;
@ -15,17 +15,17 @@ import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
public class DirectoryBasedWkdFetcher implements WKDFetcher {
public class DirectoryBasedCertificateFetcher implements CertificateFetcher {
// The directory containing the .well-known subdirectory
private final Path rootPath;
public DirectoryBasedWkdFetcher(Path rootPath) {
public DirectoryBasedCertificateFetcher(Path rootPath) {
this.rootPath = rootPath;
}
@Override
public InputStream fetch(WKDAddress address, DiscoveryMethod method) throws IOException {
public InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException {
URI uri = address.getUri(method);
String path = uri.getPath();
File file = rootPath.resolve(path.substring(1)).toFile(); // get rid of leading slash at start of path

View File

@ -5,7 +5,9 @@
package pgp.wkd.cli.test_suite;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import pgp.wkd.cli.WKDCLI;
@ -17,6 +19,7 @@ import pgp.wkd.test_suite.TestSuiteGenerator;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -39,24 +42,29 @@ public class TestSuiteTestRunner {
suite = generator.generateTestSuiteInDirectory(tempFile, TestSuiteGenerator.Method.direct);
// Fetch certificates from a local directory instead of the internetzzz.
Fetch.fetcher = new DirectoryBasedWkdFetcher(tempPath);
Fetch.fetcher = new DirectoryBasedCertificateFetcher(tempPath);
}
@Test
void runTestsAgainstTestSuite() {
for (TestCase testCase : suite.getTestCases()) {
LOGGER.info(() -> "Execute Test Case '" + testCase.getTestTitle() + "'");
String mail = testCase.getLookupMailAddress();
@TestFactory
public Iterable<DynamicTest> testsFromTestSuite() {
return suite.getTestCases()
.stream()
.map(testCase -> DynamicTest.dynamicTest(
testCase.getTestTitle(),
() -> {
String mail = testCase.getLookupMailAddress();
int exitCode = WKDCLI.execute(new String[] {
"fetch", "--armor", mail
});
int exitCode = WKDCLI.execute(new String[] {
"fetch", "--armor", mail
});
if (testCase.isExpectSuccess()) {
assertEquals(0, exitCode, testCase.getTestDescription());
} else {
assertNotEquals(0, exitCode, testCase.getTestDescription());
}
}
if (testCase.isExpectSuccess()) {
assertEquals(0, exitCode, testCase.getTestDescription());
} else {
assertNotEquals(0, exitCode, testCase.getTestDescription());
}
}
))
.collect(Collectors.toList());
}
}

View File

@ -23,6 +23,9 @@ dependencies {
// Z-Base32
implementation 'com.sandinh:zbase32-commons-codec:1.0.0'
// @Nullable etc.
implementation "com.google.code.findbugs:jsr305:3.0.2"
}
test {

View File

@ -9,20 +9,33 @@ import pgp.certificate_store.Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* Tuple class which bundles a {@link Certificate} and a list of its valid or expired user ids.
*/
public class CertificateAndUserIds {
private final Certificate certificate;
private final List<String> userIds;
public CertificateAndUserIds(Certificate certificate, List<String> userIds) {
this.certificate = certificate;
this.userIds = userIds;
}
/**
* Return a list containing the valid or expired user-ids of the certificate.
*
* @return user ids
*/
public List<String> getUserIds() {
return new ArrayList<>(userIds);
}
/**
* Return the certificate itself.
*
* @return certificate
*/
public Certificate getCertificate() {
return certificate;
}

View File

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
import java.util.ArrayList;
import java.util.List;
public interface Discover {
WKDDiscoveryItem discover(DiscoveryMethod method, WKDAddress address);
default WKDDiscoveryResult discover(WKDAddress address) {
List<WKDDiscoveryItem> results = new ArrayList<>();
// advanced method
WKDDiscoveryItem advanced = discover(DiscoveryMethod.advanced, address);
results.add(advanced);
if (advanced.isSuccessful()) {
return new WKDDiscoveryResult(results);
}
// direct method
results.add(discover(DiscoveryMethod.direct, address));
return new WKDDiscoveryResult(results);
}
default WKDDiscoveryResult discoverByEmail(String email) throws MalformedUserIdException {
return discover(WKDAddress.fromEmail(email));
}
default WKDDiscoveryResult discoverByUserId(String userId) throws MalformedUserIdException {
return discover(WKDAddressHelper.wkdAddressFromUserId(userId));
}
}

View File

@ -1,11 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
public enum DiscoveryMethod {
advanced,
direct,
;
}

View File

@ -4,6 +4,10 @@
package pgp.wkd;
/**
* Exception that gets thrown when the application is presented with a malformed user-id.
* A malformed user-id is a user-id which does not contain an email address.
*/
public class MalformedUserIdException extends RuntimeException {
public MalformedUserIdException(String message) {

View File

@ -6,21 +6,36 @@ package pgp.wkd;
import pgp.certificate_store.Certificate;
/**
* A rejected OpenPGP certificate.
* The WKD specification requires that a certificate fetched via the Web Key Directory MUST contain the mail address
* that was used to look up the certificate as a user id.
*
* A rejected certificate may not have carried the lookup email address.
*/
public class RejectedCertificate {
private final Certificate certificate;
private final Throwable failure;
private final Throwable reasonForRejection;
public RejectedCertificate(Certificate certificate, Throwable failure) {
public RejectedCertificate(Certificate certificate, Throwable reasonForRejection) {
this.certificate = certificate;
this.failure = failure;
this.reasonForRejection = reasonForRejection;
}
/**
* Return the certificate.
* @return certificate
*/
public Certificate getCertificate() {
return certificate;
}
public Throwable getFailure() {
return failure;
/**
* Return the reason for rejection.
* @return rejection reason
*/
public Throwable getReasonForRejection() {
return reasonForRejection;
}
}

View File

@ -5,7 +5,9 @@
package pgp.wkd;
import org.apache.commons.codec.binary.ZBase32;
import pgp.wkd.discovery.DiscoveryMethod;
import javax.annotation.Nonnull;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
@ -26,7 +28,7 @@ public final class WKDAddress {
private static final String SCHEME = "https://";
private static final String ADV_SUBDOMAIN = "openpgpkey.";
private static final String DIRECT_WELL_KNOWN = "/.well-known/openpgpkey/hu/";
private static String ADV_WELL_KNOWN(String domain) {
private static String ADV_WELL_KNOWN(@Nonnull String domain) {
return "/.well-known/openpgpkey/" + domain + "/hu/";
}
@ -40,6 +42,7 @@ public final class WKDAddress {
private static final Pattern PATTERN_DOMAIN_PART = Pattern.compile("[a-zA-Z0-9.-]+$");
// Android API lvl 10 does not yet know StandardCharsets.UTF_8 :/
@SuppressWarnings("CharsetObjectCanBeUsed")
private static final Charset utf8 = Charset.forName("UTF8");
// Z-Base32 encoding is described in https://www.rfc-editor.org/rfc/rfc6189.html#section-5.1.6
private static final ZBase32 zBase32 = new ZBase32();
@ -70,14 +73,17 @@ public final class WKDAddress {
* @param domainPart domain part of the email address, case-insensitive
*
* @return WKD address
* @throws IllegalArgumentException in case of malformed local or domain part
*/
public static WKDAddress fromLocalAndDomainPart(String localPart, String domainPart) {
@Nonnull
public static WKDAddress fromLocalAndDomainPart(@Nonnull String localPart, @Nonnull String domainPart) {
if (!PATTERN_LOCAL_PART.matcher(localPart).matches()) {
throw new IllegalArgumentException("Invalid local part.");
}
if (!PATTERN_DOMAIN_PART.matcher(domainPart).matches()) {
throw new IllegalArgumentException("Invalid domain part.");
}
return new WKDAddress(localPart, domainPart);
}
@ -87,20 +93,35 @@ public final class WKDAddress {
* @param email email address, case sensitive
* @return WKDAddress object
*/
public static WKDAddress fromEmail(String email) throws MalformedUserIdException {
public static WKDAddress fromEmail(@Nonnull String email) throws MalformedUserIdException {
MailAddress mailAddress = parseMailAddress(email);
return new WKDAddress(mailAddress.getLocalPart(), mailAddress.getDomainPart());
}
public URI getUri(DiscoveryMethod method) {
if (method == DiscoveryMethod.advanced) {
return getAdvancedMethodURI();
} else if (method == DiscoveryMethod.direct) {
return getDirectMethodURI();
/**
* Return the {@link URI} for the respective {@link DiscoveryMethod}.
*
* @param method discovery method
* @return uri of the certificate
*/
@Nonnull
public URI getUri(@Nonnull DiscoveryMethod method) {
switch (method) {
case advanced:
return getAdvancedMethodURI();
case direct:
return getDirectMethodURI();
default:
throw new IllegalArgumentException("Invalid discovery method: " + method);
}
throw new IllegalArgumentException("Invalid discovery method.");
}
/**
* Return the email address from which the {@link WKDAddress} was created.
*
* @return email address
*/
@Nonnull
public String getEmail() {
return localPart + '@' + domainPart;
}
@ -117,6 +138,7 @@ public final class WKDAddress {
*
* @return URI using the direct lookup method
*/
@Nonnull
public URI getDirectMethodURI() {
return URI.create(SCHEME + domainPart + DIRECT_WELL_KNOWN + zbase32LocalPart + "?l=" + percentEncodedLocalPart);
}
@ -133,6 +155,7 @@ public final class WKDAddress {
*
* @return URI using the advanced lookup method
*/
@Nonnull
public URI getAdvancedMethodURI() {
return URI.create(SCHEME + ADV_SUBDOMAIN + domainPart + ADV_WELL_KNOWN(domainPart) + zbase32LocalPart + "?l=" + percentEncodedLocalPart);
}
@ -143,7 +166,8 @@ public final class WKDAddress {
* @param string string
* @return zbase32 encoded sha1 sum of the string
*/
private String sha1AndZBase32Encode(String string) {
@Nonnull
private String sha1AndZBase32Encode(@Nonnull String string) {
String lowerCase = string.toLowerCase();
byte[] bytes = lowerCase.getBytes(utf8);
@ -166,7 +190,8 @@ public final class WKDAddress {
* @param string string
* @return percent encoded string
*/
private String percentEncode(String string) {
@Nonnull
private String percentEncode(@Nonnull String string) {
try {
return URLEncoder.encode(string, "UTF-8");
} catch (UnsupportedEncodingException e) {
@ -181,7 +206,9 @@ public final class WKDAddress {
* @param email email address string
* @return validated and split mail address
*/
private static MailAddress parseMailAddress(String email) throws MalformedUserIdException {
@Nonnull
private static MailAddress parseMailAddress(@Nonnull String email)
throws MalformedUserIdException {
Matcher matcher = PATTERN_EMAIL.matcher(email);
if (!matcher.matches()) {
throw new MalformedUserIdException("Invalid email address.");
@ -207,7 +234,7 @@ public final class WKDAddress {
* @param localPart local part
* @param domainPart domain part
*/
MailAddress(String localPart, String domainPart) {
MailAddress(@Nonnull String localPart, @Nonnull String domainPart) {
this.localPart = localPart;
this.domainPart = domainPart;
}
@ -218,6 +245,7 @@ public final class WKDAddress {
*
* @return local part
*/
@Nonnull
public String getLocalPart() {
return localPart;
}
@ -228,6 +256,7 @@ public final class WKDAddress {
*
* @return domain part
*/
@Nonnull
public String getDomainPart() {
return domainPart;
}

View File

@ -4,6 +4,7 @@
package pgp.wkd;
import javax.annotation.Nonnull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -31,7 +32,9 @@ public class WKDAddressHelper {
* @throws IllegalArgumentException in case the user-id does not match the expected format
* and does not contain an email address.
*/
public static String emailFromUserId(String userId) throws MalformedUserIdException {
@Nonnull
public static String emailFromUserId(String userId)
throws MalformedUserIdException {
Matcher matcher = PATTERN_USER_ID.matcher(userId);
if (!matcher.matches()) {
throw new MalformedUserIdException("User-ID does not follow excepted pattern \"Firstname Lastname <email.address> [Optional Comment]\"");
@ -47,7 +50,9 @@ public class WKDAddressHelper {
* @param userId user-id
* @return WKD address for the user-id's email address.
*/
public static WKDAddress wkdAddressFromUserId(String userId) throws MalformedUserIdException {
@Nonnull
public static WKDAddress wkdAddressFromUserId(String userId)
throws MalformedUserIdException {
String email = emailFromUserId(userId);
return WKDAddress.fromEmail(email);
}

View File

@ -2,26 +2,27 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pgp.wkd.WKDAddress;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
public abstract class AbstractUriWKDFetcher implements WKDFetcher {
public abstract class AbstractUriCertificateFetcher implements CertificateFetcher {
private static final Logger LOGGER = LoggerFactory.getLogger(WKDFetcher.class);
private static final Logger LOGGER = LoggerFactory.getLogger(CertificateFetcher.class);
@Override
public InputStream fetch(WKDAddress address, DiscoveryMethod method) throws IOException {
public InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException {
URI uri = address.getUri(method);
try {
return fetchUri(uri);
return fetchFromUri(uri);
} catch (IOException e) {
LOGGER.debug("Could not fetch key using " + method + " method from " + uri.toString(), e);
LOGGER.debug("Could not fetch key using " + method + " method from " + uri, e);
throw e;
}
}
@ -34,6 +35,6 @@ public abstract class AbstractUriWKDFetcher implements WKDFetcher {
* @throws java.net.ConnectException in case the file or host does not exist
* @throws IOException in case of an IO-error
*/
protected abstract InputStream fetchUri(URI uri) throws IOException;
protected abstract InputStream fetchFromUri(URI uri) throws IOException;
}

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd.discovery;
import pgp.wkd.MalformedUserIdException;
import pgp.wkd.WKDAddress;
import pgp.wkd.WKDAddressHelper;
import java.util.ArrayList;
import java.util.List;
public interface CertificateDiscoverer {
DiscoveryResponse discover(DiscoveryMethod method, WKDAddress address);
default DiscoveryResult discover(WKDAddress address) {
List<DiscoveryResponse> results = new ArrayList<>();
// advanced method
DiscoveryResponse advanced = discover(DiscoveryMethod.advanced, address);
results.add(advanced);
if (advanced.isSuccessful()) {
return new DiscoveryResult(results);
}
// direct method
results.add(discover(DiscoveryMethod.direct, address));
return new DiscoveryResult(results);
}
default DiscoveryResult discoverByEmail(String email) throws MalformedUserIdException {
return discover(WKDAddress.fromEmail(email));
}
default DiscoveryResult discoverByUserId(String userId) throws MalformedUserIdException {
return discover(WKDAddressHelper.wkdAddressFromUserId(userId));
}
}

View File

@ -2,29 +2,33 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import pgp.certificate_store.Certificate;
import pgp.wkd.CertificateAndUserIds;
import pgp.wkd.MissingUserIdException;
import pgp.wkd.RejectedCertificate;
import pgp.wkd.WKDAddress;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class AbstractDiscover implements Discover {
public class CertificateDiscoveryImplementation implements CertificateDiscoverer {
protected final CertificateReader reader;
protected final WKDFetcher fetcher;
protected final CertificateParser reader;
protected final CertificateFetcher fetcher;
public AbstractDiscover(CertificateReader reader, WKDFetcher fetcher) {
public CertificateDiscoveryImplementation(CertificateParser reader, CertificateFetcher fetcher) {
this.reader = reader;
this.fetcher = fetcher;
}
@Override
public WKDDiscoveryItem discover(DiscoveryMethod method, WKDAddress address) {
public DiscoveryResponse discover(DiscoveryMethod method, WKDAddress address) {
try {
InputStream inputStream = fetcher.fetch(address, method);
InputStream inputStream = fetcher.fetchCertificate(address, method);
List<CertificateAndUserIds> fetchedCertificates = reader.read(inputStream);
List<RejectedCertificate> rejectedCertificates = new ArrayList<>();
@ -50,10 +54,10 @@ public class AbstractDiscover implements Discover {
}
}
return WKDDiscoveryItem.success(method, address, acceptableCertificates, rejectedCertificates);
return DiscoveryResponse.success(method, address, acceptableCertificates, rejectedCertificates);
} catch (IOException e) {
return WKDDiscoveryItem.failure(method, address, e);
return DiscoveryResponse.failure(method, address, e);
}
}
}

View File

@ -2,7 +2,9 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import pgp.wkd.WKDAddress;
import java.io.IOException;
import java.io.InputStream;
@ -11,7 +13,7 @@ import java.io.InputStream;
* Abstract class for fetching OpenPGP certificates from the WKD.
* This class can be extended to fetch files from remote servers using different HTTP clients.
*/
public interface WKDFetcher {
public interface CertificateFetcher {
/**
* Attempt to fetch an OpenPGP certificate from the Web Key Directory.
@ -21,5 +23,5 @@ public interface WKDFetcher {
*
* @throws IOException in case of an error
*/
InputStream fetch(WKDAddress address, DiscoveryMethod method) throws IOException;
InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException;
}

View File

@ -2,13 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import pgp.wkd.CertificateAndUserIds;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public interface CertificateReader {
public interface CertificateParser {
List<CertificateAndUserIds> read(InputStream inputStream) throws IOException;
}

View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd.discovery;
public enum DiscoveryMethod {
/**
* Advanced method.
* This is the preferred method which MUST be checked first.
*
* @see <a href="https://www.ietf.org/archive/id/draft-koch-openpgp-webkey-service-13.html#section-3.1-5">OpenPGP Web Key Directory: Advanced Method</a>
*/
advanced,
/**
* Direct method.
* This is the fallback method.
*
* @see <a href="https://www.ietf.org/archive/id/draft-koch-openpgp-webkey-service-13.html#section-3.1-10">OpenPGP web Key Directory: Direct Method</a>
*/
direct
}

View File

@ -2,78 +2,95 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import pgp.certificate_store.Certificate;
import pgp.wkd.RejectedCertificate;
import pgp.wkd.WKDAddress;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
public final class WKDDiscoveryItem {
public final class DiscoveryResponse {
private final DiscoveryMethod method;
private final WKDAddress address;
private final List<Certificate> certificates;
private final List<RejectedCertificate> rejectedCertificates;
private final Throwable failure;
private final Throwable fetchingFailure;
/**
* Constructor for a {@link WKDDiscoveryItem} object.
* Constructor for a {@link DiscoveryResponse} object.
* @param method discovery method
* @param address wkd address used for discovery
* @param certificates list of successfully fetched certificates
* @param rejectedCertificates list of invalid fetched certificates (e.g. missing user-id)
* @param failure general fetching error (e.g. connection error, 404...)
* @param fetchingFailure general fetching error (e.g. connection error, 404...)
*/
private WKDDiscoveryItem(
private DiscoveryResponse(
DiscoveryMethod method,
WKDAddress address,
List<Certificate> certificates,
List<RejectedCertificate> rejectedCertificates,
Throwable failure) {
Throwable fetchingFailure) {
this.method = method;
this.address = address;
this.certificates = certificates;
this.rejectedCertificates = rejectedCertificates;
this.failure = failure;
this.fetchingFailure = fetchingFailure;
}
public static WKDDiscoveryItem success(DiscoveryMethod method, WKDAddress address, List<Certificate> certificates, List<RejectedCertificate> rejectedCertificates) {
return new WKDDiscoveryItem(method, address, certificates, rejectedCertificates, null);
public static DiscoveryResponse success(
@Nonnull DiscoveryMethod method,
@Nonnull WKDAddress address,
@Nonnull List<Certificate> certificates,
@Nonnull List<RejectedCertificate> rejectedCertificates) {
return new DiscoveryResponse(method, address, certificates, rejectedCertificates, null);
}
public static WKDDiscoveryItem failure(DiscoveryMethod method, WKDAddress address, Throwable failure) {
return new WKDDiscoveryItem(method, address, null, null, failure);
public static DiscoveryResponse failure(
@Nonnull DiscoveryMethod method,
@Nonnull WKDAddress address,
@Nonnull Throwable fetchingFailure) {
return new DiscoveryResponse(method, address, Collections.emptyList(), Collections.emptyList(), fetchingFailure);
}
@Nonnull
public DiscoveryMethod getMethod() {
return method;
}
@Nonnull
public WKDAddress getAddress() {
return address;
}
public boolean isSuccessful() {
return !hasFailure();
return !hasFetchingFailure();
}
@Nonnull
public List<Certificate> getCertificates() {
return certificates;
}
@Nonnull
public List<RejectedCertificate> getRejectedCertificates() {
return rejectedCertificates;
}
public Throwable getFailure() {
return failure;
@Nullable
public Throwable getFetchingFailure() {
return fetchingFailure;
}
public boolean hasCertificates() {
return certificates != null && !certificates.isEmpty();
}
public boolean hasFailure() {
return failure != null;
public boolean hasFetchingFailure() {
return fetchingFailure != null;
}
}

View File

@ -2,25 +2,27 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import pgp.certificate_store.Certificate;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
public class WKDDiscoveryResult {
public class DiscoveryResult {
private List<WKDDiscoveryItem> items;
private List<DiscoveryResponse> items;
public WKDDiscoveryResult(List<WKDDiscoveryItem> items) {
public DiscoveryResult(@Nonnull List<DiscoveryResponse> items) {
this.items = items;
}
@Nonnull
public List<Certificate> getCertificates() {
List<Certificate> certificates = new ArrayList<>();
for (WKDDiscoveryItem item : items) {
for (DiscoveryResponse item : items) {
if (item.isSuccessful()) {
certificates.addAll(item.getCertificates());
}
@ -29,7 +31,7 @@ public class WKDDiscoveryResult {
}
public boolean isSuccessful() {
for (WKDDiscoveryItem item : items) {
for (DiscoveryResponse item : items) {
if (item.isSuccessful() && item.hasCertificates()) {
return true;
}
@ -37,13 +39,15 @@ public class WKDDiscoveryResult {
return false;
}
public List<WKDDiscoveryItem> getItems() {
@Nonnull
public List<DiscoveryResponse> getItems() {
return items;
}
public List<WKDDiscoveryItem> getFailedItems() {
List<WKDDiscoveryItem> fails = new ArrayList<>();
for (WKDDiscoveryItem item : items) {
@Nonnull
public List<DiscoveryResponse> getFailedItems() {
List<DiscoveryResponse> fails = new ArrayList<>();
for (DiscoveryResponse item : items) {
if (!item.isSuccessful()) {
fails.add(item);
}

View File

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: Apache-2.0
package pgp.wkd;
package pgp.wkd.discovery;
import java.io.IOException;
import java.io.InputStream;
@ -12,11 +12,11 @@ import java.net.URI;
import java.net.URL;
/**
* Implementation of {@link WKDFetcher} using Java's {@link HttpURLConnection}.
* Implementation of {@link CertificateFetcher} using Java's {@link HttpURLConnection}.
*/
public class HttpUrlConnectionWKDFetcher extends AbstractUriWKDFetcher {
public class HttpUrlConnectionCertificateFetcher extends AbstractUriCertificateFetcher {
public InputStream fetchUri(URI uri) throws IOException {
public InputStream fetchFromUri(URI uri) throws IOException {
URL url = uri.toURL();
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");