mirror of
https://codeberg.org/PGPainless/wkd-java.git
synced 2024-12-22 05:37:58 +01:00
Add support for fetching policy objects
This commit is contained in:
parent
cb996733fb
commit
d7cddf26bb
9 changed files with 318 additions and 22 deletions
|
@ -10,6 +10,7 @@ import pgp.wkd.discovery.CertificateFetcher;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
|
@ -26,6 +27,15 @@ public class DirectoryBasedCertificateFetcher implements CertificateFetcher {
|
|||
|
||||
@Override
|
||||
public InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException {
|
||||
return inputStreamFromFile(address, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream fetchPolicy(WKDAddress address, DiscoveryMethod method) throws IOException {
|
||||
return inputStreamFromFile(address, method);
|
||||
}
|
||||
|
||||
private InputStream inputStreamFromFile(WKDAddress address, DiscoveryMethod method) throws FileNotFoundException {
|
||||
URI uri = address.getUri(method);
|
||||
String path = uri.getPath().substring(1); // get rid of leading slash at start of path
|
||||
File file = rootPath.resolve(path).toFile();
|
||||
|
|
|
@ -27,6 +27,17 @@ public abstract class AbstractUriCertificateFetcher implements CertificateFetche
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream fetchPolicy(WKDAddress address, DiscoveryMethod method) throws IOException {
|
||||
URI uri = address.getPolicyUri(method);
|
||||
try {
|
||||
return fetchFromUri(uri);
|
||||
} catch (IOException e) {
|
||||
LOGGER.debug("Could not fetch policy file using " + method + " method from " + uri, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the contents of the file that the {@link URI} points to from the remote server.
|
||||
* @param uri uri
|
||||
|
|
|
@ -24,4 +24,15 @@ public interface CertificateFetcher {
|
|||
* @throws IOException in case of an error
|
||||
*/
|
||||
InputStream fetchCertificate(WKDAddress address, DiscoveryMethod method) throws IOException;
|
||||
|
||||
/**
|
||||
* Fetch the policy file belonging to the address and discovery method.
|
||||
*
|
||||
* @param address WKDAddress object
|
||||
* @param method discovery method
|
||||
* @return input stream containing the WKD policy file
|
||||
*
|
||||
* @throws IOException in case of an error
|
||||
*/
|
||||
InputStream fetchPolicy(WKDAddress address, DiscoveryMethod method) throws IOException;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package pgp.wkd.discovery;
|
|||
|
||||
import pgp.certificate_store.Certificate;
|
||||
import pgp.wkd.CertificateAndUserIds;
|
||||
import pgp.wkd.exception.MissingPolicyFileException;
|
||||
import pgp.wkd.exception.RejectedCertificateException;
|
||||
import pgp.wkd.RejectedCertificate;
|
||||
import pgp.wkd.WKDAddress;
|
||||
|
@ -27,9 +28,18 @@ public class DefaultCertificateDiscoverer implements CertificateDiscoverer {
|
|||
|
||||
@Override
|
||||
public DiscoveryResponse discover(DiscoveryMethod method, WKDAddress address) {
|
||||
DiscoveryResponse.Builder builder = DiscoveryResponse.builder(method, address);
|
||||
|
||||
fetchPolicy(method, address, builder);
|
||||
fetchCertificates(method, address, builder);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void fetchCertificates(DiscoveryMethod method, WKDAddress address, DiscoveryResponse.Builder builder) {
|
||||
try {
|
||||
InputStream inputStream = fetcher.fetchCertificate(address, method);
|
||||
List<CertificateAndUserIds> fetchedCertificates = reader.read(inputStream);
|
||||
InputStream certificateIn = fetcher.fetchCertificate(address, method);
|
||||
List<CertificateAndUserIds> fetchedCertificates = reader.read(certificateIn);
|
||||
|
||||
List<RejectedCertificate> rejectedCertificates = new ArrayList<>();
|
||||
List<Certificate> acceptableCertificates = new ArrayList<>();
|
||||
|
@ -54,10 +64,21 @@ public class DefaultCertificateDiscoverer implements CertificateDiscoverer {
|
|||
}
|
||||
}
|
||||
|
||||
return DiscoveryResponse.success(method, address, acceptableCertificates, rejectedCertificates);
|
||||
builder.setAcceptableCertificates(acceptableCertificates);
|
||||
builder.setRejectedCertificates(rejectedCertificates);
|
||||
|
||||
} catch (IOException e) {
|
||||
return DiscoveryResponse.failure(method, address, e);
|
||||
builder.setFetchingFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchPolicy(DiscoveryMethod method, WKDAddress address, DiscoveryResponse.Builder builder) {
|
||||
try {
|
||||
InputStream policyIn = fetcher.fetchPolicy(address, method);
|
||||
WKDPolicy policy = WKDPolicy.fromInputStream(policyIn);
|
||||
builder.setPolicy(policy);
|
||||
} catch (IOException e) {
|
||||
builder.setMissingPolicyFileException(new MissingPolicyFileException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ package pgp.wkd.discovery;
|
|||
import pgp.certificate_store.Certificate;
|
||||
import pgp.wkd.RejectedCertificate;
|
||||
import pgp.wkd.WKDAddress;
|
||||
import pgp.wkd.exception.MissingPolicyFileException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class DiscoveryResponse {
|
||||
|
@ -21,6 +21,8 @@ public final class DiscoveryResponse {
|
|||
private final List<Certificate> certificates;
|
||||
private final List<RejectedCertificate> rejectedCertificates;
|
||||
private final Throwable fetchingFailure;
|
||||
private final WKDPolicy policy;
|
||||
private final MissingPolicyFileException missingPolicyFileException;
|
||||
|
||||
/**
|
||||
* Constructor for a {@link DiscoveryResponse} object.
|
||||
|
@ -35,27 +37,16 @@ public final class DiscoveryResponse {
|
|||
WKDAddress address,
|
||||
List<Certificate> certificates,
|
||||
List<RejectedCertificate> rejectedCertificates,
|
||||
Throwable fetchingFailure) {
|
||||
Throwable fetchingFailure,
|
||||
WKDPolicy policy,
|
||||
MissingPolicyFileException missingPolicyFileException) {
|
||||
this.method = method;
|
||||
this.address = address;
|
||||
this.certificates = certificates;
|
||||
this.rejectedCertificates = rejectedCertificates;
|
||||
this.fetchingFailure = fetchingFailure;
|
||||
}
|
||||
|
||||
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 DiscoveryResponse failure(
|
||||
@Nonnull DiscoveryMethod method,
|
||||
@Nonnull WKDAddress address,
|
||||
@Nonnull Throwable fetchingFailure) {
|
||||
return new DiscoveryResponse(method, address, Collections.emptyList(), Collections.emptyList(), fetchingFailure);
|
||||
this.policy = policy;
|
||||
this.missingPolicyFileException = missingPolicyFileException;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -73,7 +64,7 @@ public final class DiscoveryResponse {
|
|||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return !hasFetchingFailure();
|
||||
return !hasFetchingFailure() && hasPolicy();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -98,4 +89,71 @@ public final class DiscoveryResponse {
|
|||
public boolean hasFetchingFailure() {
|
||||
return fetchingFailure != null;
|
||||
}
|
||||
|
||||
public boolean hasPolicy() {
|
||||
return getPolicy() != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WKDPolicy getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
|
||||
public static Builder builder(@Nonnull DiscoveryMethod discoveryMethod, @Nonnull WKDAddress address) {
|
||||
Builder builder = new Builder(discoveryMethod, address);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private DiscoveryMethod discoveryMethod;
|
||||
private WKDAddress address;
|
||||
private List<Certificate> acceptableCertificates;
|
||||
private List<RejectedCertificate> rejectedCertificates;
|
||||
private Throwable fetchingFailure;
|
||||
private WKDPolicy policy;
|
||||
private MissingPolicyFileException missingPolicyFileException;
|
||||
|
||||
public Builder(DiscoveryMethod discoveryMethod, WKDAddress address) {
|
||||
this.discoveryMethod = discoveryMethod;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Builder setAcceptableCertificates(List<Certificate> acceptableCertificates) {
|
||||
this.acceptableCertificates = acceptableCertificates;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRejectedCertificates(List<RejectedCertificate> rejectedCertificates) {
|
||||
this.rejectedCertificates = rejectedCertificates;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFetchingFailure(Throwable throwable) {
|
||||
this.fetchingFailure = throwable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPolicy(WKDPolicy policy) {
|
||||
this.policy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMissingPolicyFileException(MissingPolicyFileException exception) {
|
||||
this.missingPolicyFileException = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiscoveryResponse build() {
|
||||
return new DiscoveryResponse(
|
||||
discoveryMethod,
|
||||
address,
|
||||
acceptableCertificates,
|
||||
rejectedCertificates,
|
||||
fetchingFailure,
|
||||
policy,
|
||||
missingPolicyFileException
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
92
wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java
Normal file
92
wkd-java/src/main/java/pgp/wkd/discovery/WKDPolicy.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.discovery;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public final class WKDPolicy {
|
||||
|
||||
public static final String KEYWORD_MAILBOX_ONLY = "mailbox-only";
|
||||
public static final String KEYWORD_DANE_ONLY = "dane-only";
|
||||
public static final String KEYWORD_AUTH_SUBMIT = "auth-submit";
|
||||
public static final String KEYWORD_PROTOCOL_VERSION = "protocol-version";
|
||||
public static final String KEYWORD_SUBMISSION_ADDRESS = "submission-address";
|
||||
|
||||
private final boolean mailboxOnly;
|
||||
private final boolean daneOnly;
|
||||
private final boolean authSubmit;
|
||||
private final Integer protocolVersion;
|
||||
private final String submissionAddress;
|
||||
|
||||
private WKDPolicy(boolean mailboxOnly, boolean daneOnly, boolean authSubmit, Integer protocolVersion, String submissionAddress) {
|
||||
this.mailboxOnly = mailboxOnly;
|
||||
this.daneOnly = daneOnly;
|
||||
this.authSubmit = authSubmit;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.submissionAddress = submissionAddress;
|
||||
}
|
||||
|
||||
public static WKDPolicy fromInputStream(InputStream inputStream) throws IOException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
|
||||
boolean mailboxOnly = false;
|
||||
boolean daneOnly = false;
|
||||
boolean authSubmit = false;
|
||||
Integer protocolVersion = null;
|
||||
String submissionAddress = null;
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
String prepared = line.trim();
|
||||
if (prepared.equals(KEYWORD_MAILBOX_ONLY)) {
|
||||
mailboxOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (prepared.equals(KEYWORD_DANE_ONLY)) {
|
||||
daneOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (prepared.equals(KEYWORD_AUTH_SUBMIT)) {
|
||||
authSubmit = true;
|
||||
continue;
|
||||
}
|
||||
if (prepared.startsWith(KEYWORD_PROTOCOL_VERSION + ": ")) {
|
||||
protocolVersion = Integer.parseInt(prepared.substring(KEYWORD_PROTOCOL_VERSION.length() + 2));
|
||||
continue;
|
||||
}
|
||||
if (prepared.startsWith(KEYWORD_SUBMISSION_ADDRESS + ": ")) {
|
||||
submissionAddress = prepared.substring(KEYWORD_SUBMISSION_ADDRESS.length() + 2).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return new WKDPolicy(mailboxOnly, daneOnly, authSubmit, protocolVersion, submissionAddress);
|
||||
}
|
||||
|
||||
public boolean isMailboxOnly() {
|
||||
return mailboxOnly;
|
||||
}
|
||||
|
||||
public boolean isDaneOnly() {
|
||||
return daneOnly;
|
||||
}
|
||||
|
||||
public boolean isAuthSubmit() {
|
||||
return authSubmit;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSubmissionAddress() {
|
||||
return submissionAddress;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.exception;
|
||||
|
||||
public class MissingPolicyFileException extends RuntimeException {
|
||||
|
||||
public MissingPolicyFileException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
64
wkd-java/src/test/java/pgp/wkd/WKDPolicyTest.java
Normal file
64
wkd-java/src/test/java/pgp/wkd/WKDPolicyTest.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import pgp.wkd.discovery.WKDPolicy;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class WKDPolicyTest {
|
||||
|
||||
@Test
|
||||
public void parseEmptyPolicy() throws IOException {
|
||||
ByteArrayInputStream empty = new ByteArrayInputStream(new byte[0]);
|
||||
WKDPolicy policy = WKDPolicy.fromInputStream(empty);
|
||||
|
||||
assertFalse(policy.isMailboxOnly());
|
||||
assertFalse(policy.isDaneOnly());
|
||||
assertFalse(policy.isAuthSubmit());
|
||||
assertNull(policy.getProtocolVersion());
|
||||
assertNull(policy.getSubmissionAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSparsePolicy() throws IOException {
|
||||
ByteArrayInputStream sparse = new ByteArrayInputStream(
|
||||
"protocol-version: 13\n".getBytes(StandardCharsets.UTF_8));
|
||||
WKDPolicy policy = WKDPolicy.fromInputStream(sparse);
|
||||
|
||||
assertFalse(policy.isMailboxOnly());
|
||||
assertFalse(policy.isDaneOnly());
|
||||
assertFalse(policy.isAuthSubmit());
|
||||
assertEquals(13, policy.getProtocolVersion());
|
||||
assertNull(policy.getSubmissionAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseFullPolicy() throws IOException {
|
||||
ByteArrayInputStream full = new ByteArrayInputStream(
|
||||
("mailbox-only\n" +
|
||||
"dane-only\n" +
|
||||
"auth-submit\n" +
|
||||
"protocol-version: 12\n" +
|
||||
"submission-address: key-submission-example.org@directory.example.org")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
WKDPolicy policy = WKDPolicy.fromInputStream(full);
|
||||
|
||||
assertTrue(policy.isMailboxOnly());
|
||||
assertTrue(policy.isDaneOnly());
|
||||
assertTrue(policy.isAuthSubmit());
|
||||
|
||||
assertEquals(12, policy.getProtocolVersion());
|
||||
assertEquals("key-submission-example.org@directory.example.org", policy.getSubmissionAddress());
|
||||
}
|
||||
}
|
|
@ -40,6 +40,15 @@ public abstract class WkdDirectoryStructure {
|
|||
}
|
||||
}
|
||||
|
||||
protected void touch(File file) throws IOException {
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
throw new IOException("Cannot create file '" + file.getAbsolutePath() + "'.");
|
||||
}
|
||||
if (!file.isFile()) {
|
||||
throw new IOException("Cannot create file '" + file.getAbsolutePath() + "': Is not a file.");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract URI getAddress(String mail);
|
||||
|
||||
public abstract File resolve(Path path);
|
||||
|
@ -47,10 +56,12 @@ public abstract class WkdDirectoryStructure {
|
|||
public static class DirectMethod extends WkdDirectoryStructure {
|
||||
|
||||
private final File hu;
|
||||
private final File policy;
|
||||
|
||||
public DirectMethod(File rootDirectory, String domain) {
|
||||
super(rootDirectory, domain);
|
||||
this.hu = new File(openpgpkey, "hu");
|
||||
this.policy = new File(openpgpkey, "policy");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,6 +83,8 @@ public abstract class WkdDirectoryStructure {
|
|||
mkdir(wellKnown);
|
||||
mkdir(openpgpkey);
|
||||
mkdir(hu);
|
||||
|
||||
touch(policy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -89,11 +102,13 @@ public abstract class WkdDirectoryStructure {
|
|||
|
||||
private final File domainFile;
|
||||
private final File hu;
|
||||
private final File policy;
|
||||
|
||||
public AdvancedMethod(File rootDir, String domain) {
|
||||
super(rootDir, domain);
|
||||
this.domainFile = new File(openpgpkey, domain);
|
||||
this.hu = new File(domainFile, "hu");
|
||||
this.policy = new File(domainFile, "policy");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,6 +131,8 @@ public abstract class WkdDirectoryStructure {
|
|||
mkdir(openpgpkey);
|
||||
mkdir(domainFile);
|
||||
mkdir(hu);
|
||||
|
||||
touch(policy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in a new issue