From c2d4d283bca0f6c068997ff7c052ebfe00d3901a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Mar 2022 11:25:03 +0100 Subject: [PATCH] Even more refactoring --- .../java/pgp/wkd/cli/RuntimeIOException.java | 25 ++++++++++++ .../src/main/java/pgp/wkd/cli/WKDCLI.java | 7 ++-- .../main/java/pgp/wkd/cli/command/Fetch.java | 34 ++++++---------- .../java/pgp/wkd/MissingUserIdException.java | 18 --------- .../src/main/java/pgp/wkd/WKDAddress.java | 1 + .../main/java/pgp/wkd/WKDAddressHelper.java | 2 + .../wkd/discovery/CertificateDiscoverer.java | 2 +- .../DefaultCertificateDiscoverer.java | 4 +- .../pgp/wkd/discovery/DiscoveryResult.java | 25 ++++++++++++ .../exception}/CertNotFetchableException.java | 5 ++- .../MalformedUserIdException.java | 2 +- .../RejectedCertificateException.java | 39 +++++++++++++++++++ .../java/pgp/wkd/exception/package-info.java | 8 ++++ .../src/test/java/pgp/wkd/WKDAddressTest.java | 17 ++++++++ 14 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 wkd-java-cli/src/main/java/pgp/wkd/cli/RuntimeIOException.java delete mode 100644 wkd-java/src/main/java/pgp/wkd/MissingUserIdException.java rename {wkd-java-cli/src/main/java/pgp/wkd/cli => wkd-java/src/main/java/pgp/wkd/exception}/CertNotFetchableException.java (78%) rename wkd-java/src/main/java/pgp/wkd/{ => exception}/MalformedUserIdException.java (94%) create mode 100644 wkd-java/src/main/java/pgp/wkd/exception/RejectedCertificateException.java create mode 100644 wkd-java/src/main/java/pgp/wkd/exception/package-info.java diff --git a/wkd-java-cli/src/main/java/pgp/wkd/cli/RuntimeIOException.java b/wkd-java-cli/src/main/java/pgp/wkd/cli/RuntimeIOException.java new file mode 100644 index 0000000..2a97825 --- /dev/null +++ b/wkd-java-cli/src/main/java/pgp/wkd/cli/RuntimeIOException.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.wkd.cli; + +import java.io.IOException; + +/** + * {@link RuntimeException} wrapper for {@link IOException}. + * Background: We want to throw {@link IOException IOExceptions} in {@link Runnable#run()}. + */ +public class RuntimeIOException extends RuntimeException { + + private final IOException ioException; + + public RuntimeIOException(IOException ioe) { + super(ioe); + this.ioException = ioe; + } + + public IOException getIoException() { + return ioException; + } +} diff --git a/wkd-java-cli/src/main/java/pgp/wkd/cli/WKDCLI.java b/wkd-java-cli/src/main/java/pgp/wkd/cli/WKDCLI.java index 5c1bc19..d4ff56d 100644 --- a/wkd-java-cli/src/main/java/pgp/wkd/cli/WKDCLI.java +++ b/wkd-java-cli/src/main/java/pgp/wkd/cli/WKDCLI.java @@ -4,7 +4,8 @@ package pgp.wkd.cli; -import pgp.wkd.MissingUserIdException; +import pgp.wkd.exception.CertNotFetchableException; +import pgp.wkd.exception.RejectedCertificateException; import pgp.wkd.cli.command.Fetch; import picocli.CommandLine; @@ -28,8 +29,8 @@ public class WKDCLI { .setExitCodeExceptionMapper(new CommandLine.IExitCodeExceptionMapper() { @Override public int getExitCode(Throwable exception) { - if (exception instanceof MissingUserIdException) { - return MissingUserIdException.ERROR_CODE; + if (exception instanceof RejectedCertificateException) { + return ((RejectedCertificateException) exception).getErrorCode(); } else if (exception instanceof CertNotFetchableException) { return CertNotFetchableException.ERROR_CODE; } 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 d768fd2..9d0d37a 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 @@ -5,17 +5,15 @@ package pgp.wkd.cli.command; import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.util.io.Streams; -import pgp.certificate_store.Certificate; -import pgp.wkd.discovery.CertificateDiscoverer; -import pgp.wkd.discovery.HttpsUrlConnectionCertificateFetcher; -import pgp.wkd.MalformedUserIdException; import pgp.wkd.WKDAddress; import pgp.wkd.WKDAddressHelper; -import pgp.wkd.discovery.DiscoveryResult; -import pgp.wkd.discovery.CertificateFetcher; -import pgp.wkd.cli.CertNotFetchableException; import pgp.wkd.cli.HttpsCertificateDiscoverer; +import pgp.wkd.cli.RuntimeIOException; +import pgp.wkd.discovery.CertificateDiscoverer; +import pgp.wkd.discovery.CertificateFetcher; +import pgp.wkd.discovery.DiscoveryResult; +import pgp.wkd.discovery.HttpsUrlConnectionCertificateFetcher; +import pgp.wkd.exception.MalformedUserIdException; import picocli.CommandLine; import java.io.IOException; @@ -51,24 +49,14 @@ public class Fetch implements Runnable { WKDAddress address = addressFromUserId(userId); DiscoveryResult result = certificateDiscoverer.discover(address); - if (!result.isSuccessful()) { - throw new CertNotFetchableException("Cannot fetch cert."); - } - + OutputStream outputStream = armor ? new ArmoredOutputStream(System.out) : System.out; try { - if (armor) { - OutputStream out = new ArmoredOutputStream(System.out); - for (Certificate certificate : result.getCertificates()) { - Streams.pipeAll(certificate.getInputStream(), out); - } - out.close(); - } else { - for (Certificate certificate : result.getCertificates()) { - Streams.pipeAll(certificate.getInputStream(), System.out); - } + result.write(outputStream); + if (outputStream instanceof ArmoredOutputStream) { + outputStream.close(); } } catch (IOException e) { - throw new CertNotFetchableException("Certificate cannot be fetched.", e); + throw new RuntimeIOException(e); } } diff --git a/wkd-java/src/main/java/pgp/wkd/MissingUserIdException.java b/wkd-java/src/main/java/pgp/wkd/MissingUserIdException.java deleted file mode 100644 index b5e2bdd..0000000 --- a/wkd-java/src/main/java/pgp/wkd/MissingUserIdException.java +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package pgp.wkd; - -/** - * Exception that gets thrown when an OpenPGP certificate is not carrying a User-ID binding for the email address - * that was used to look the certificate up via WKD. - */ -public class MissingUserIdException extends RuntimeException { - - public static final int ERROR_CODE = 7; - - public MissingUserIdException(String message) { - super(message); - } -} diff --git a/wkd-java/src/main/java/pgp/wkd/WKDAddress.java b/wkd-java/src/main/java/pgp/wkd/WKDAddress.java index 83b957f..9bcefdd 100644 --- a/wkd-java/src/main/java/pgp/wkd/WKDAddress.java +++ b/wkd-java/src/main/java/pgp/wkd/WKDAddress.java @@ -6,6 +6,7 @@ package pgp.wkd; import org.apache.commons.codec.binary.ZBase32; import pgp.wkd.discovery.DiscoveryMethod; +import pgp.wkd.exception.MalformedUserIdException; import javax.annotation.Nonnull; import java.io.UnsupportedEncodingException; diff --git a/wkd-java/src/main/java/pgp/wkd/WKDAddressHelper.java b/wkd-java/src/main/java/pgp/wkd/WKDAddressHelper.java index 21247eb..cec33d3 100644 --- a/wkd-java/src/main/java/pgp/wkd/WKDAddressHelper.java +++ b/wkd-java/src/main/java/pgp/wkd/WKDAddressHelper.java @@ -4,6 +4,8 @@ package pgp.wkd; +import pgp.wkd.exception.MalformedUserIdException; + import javax.annotation.Nonnull; import java.util.regex.Matcher; import java.util.regex.Pattern; 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 b598e4c..6b11e77 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/CertificateDiscoverer.java @@ -4,7 +4,7 @@ package pgp.wkd.discovery; -import pgp.wkd.MalformedUserIdException; +import pgp.wkd.exception.MalformedUserIdException; import pgp.wkd.WKDAddress; import pgp.wkd.WKDAddressHelper; diff --git a/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java b/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java index 1b170aa..ca84c03 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/DefaultCertificateDiscoverer.java @@ -6,7 +6,7 @@ package pgp.wkd.discovery; import pgp.certificate_store.Certificate; import pgp.wkd.CertificateAndUserIds; -import pgp.wkd.MissingUserIdException; +import pgp.wkd.exception.RejectedCertificateException; import pgp.wkd.RejectedCertificate; import pgp.wkd.WKDAddress; @@ -47,7 +47,7 @@ public class DefaultCertificateDiscoverer implements CertificateDiscoverer { } if (!containsEmail) { rejectedCertificates.add(new RejectedCertificate(certificate, - new MissingUserIdException("Certificate " + certificate.getFingerprint() + + new RejectedCertificateException.MissingUserId("Certificate " + certificate.getFingerprint() + " does not contain user-id with email '" + email + "'"))); } else { acceptableCertificates.add(certificate); 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 a619d4a..550176f 100644 --- a/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java +++ b/wkd-java/src/main/java/pgp/wkd/discovery/DiscoveryResult.java @@ -5,8 +5,12 @@ package pgp.wkd.discovery; import pgp.certificate_store.Certificate; +import pgp.wkd.exception.CertNotFetchableException; import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -39,6 +43,27 @@ public class DiscoveryResult { return false; } + /** + * Write out the (successful) result (certificates) to the given {@link OutputStream}. + * This method does not close the output stream. + * + * @param outputStream output stream + */ + public void write(OutputStream outputStream) throws IOException { + if (!isSuccessful()) { + throw new CertNotFetchableException("Cannot fetch cert."); + } + + byte[] buf = new byte[4096]; + int read; + for (Certificate certificate : getCertificates()) { + InputStream certIn = certificate.getInputStream(); + while ((read = certIn.read(buf)) != -1) { + outputStream.write(buf, 0, read); + } + } + } + @Nonnull public List getItems() { return items; diff --git a/wkd-java-cli/src/main/java/pgp/wkd/cli/CertNotFetchableException.java b/wkd-java/src/main/java/pgp/wkd/exception/CertNotFetchableException.java similarity index 78% rename from wkd-java-cli/src/main/java/pgp/wkd/cli/CertNotFetchableException.java rename to wkd-java/src/main/java/pgp/wkd/exception/CertNotFetchableException.java index 5da94bb..f07f496 100644 --- a/wkd-java-cli/src/main/java/pgp/wkd/cli/CertNotFetchableException.java +++ b/wkd-java/src/main/java/pgp/wkd/exception/CertNotFetchableException.java @@ -2,8 +2,11 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.wkd.cli; +package pgp.wkd.exception; +/** + * Exception that gets thrown when a certificate cannot be fetched at all. + */ public class CertNotFetchableException extends RuntimeException { public static final int ERROR_CODE = 3; diff --git a/wkd-java/src/main/java/pgp/wkd/MalformedUserIdException.java b/wkd-java/src/main/java/pgp/wkd/exception/MalformedUserIdException.java similarity index 94% rename from wkd-java/src/main/java/pgp/wkd/MalformedUserIdException.java rename to wkd-java/src/main/java/pgp/wkd/exception/MalformedUserIdException.java index 45f6a1e..e6204a4 100644 --- a/wkd-java/src/main/java/pgp/wkd/MalformedUserIdException.java +++ b/wkd-java/src/main/java/pgp/wkd/exception/MalformedUserIdException.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package pgp.wkd; +package pgp.wkd.exception; /** * Exception that gets thrown when the application is presented with a malformed user-id. diff --git a/wkd-java/src/main/java/pgp/wkd/exception/RejectedCertificateException.java b/wkd-java/src/main/java/pgp/wkd/exception/RejectedCertificateException.java new file mode 100644 index 0000000..ee31de1 --- /dev/null +++ b/wkd-java/src/main/java/pgp/wkd/exception/RejectedCertificateException.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package pgp.wkd.exception; + +/** + * Subclasses of this exception are thrown when a fetched certificate is rejected for any reason. + */ +public abstract class RejectedCertificateException extends RuntimeException { + + public RejectedCertificateException(String message) { + super(message); + } + + /** + * Return an error code that identifies the exception. + * @return error code + */ + public abstract int getErrorCode(); + + /** + * Exception that gets thrown when an OpenPGP certificate is not carrying a User-ID binding for the email address + * that was used to look the certificate up via WKD. + */ + public static class MissingUserId extends RejectedCertificateException { + + public static final int ERROR_CODE = 7; + + public MissingUserId(String message) { + super(message); + } + + @Override + public int getErrorCode() { + return ERROR_CODE; + } + } +} diff --git a/wkd-java/src/main/java/pgp/wkd/exception/package-info.java b/wkd-java/src/main/java/pgp/wkd/exception/package-info.java new file mode 100644 index 0000000..5207813 --- /dev/null +++ b/wkd-java/src/main/java/pgp/wkd/exception/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Exceptions. + */ +package pgp.wkd.exception; diff --git a/wkd-java/src/test/java/pgp/wkd/WKDAddressTest.java b/wkd-java/src/test/java/pgp/wkd/WKDAddressTest.java index 66dd910..5167321 100644 --- a/wkd-java/src/test/java/pgp/wkd/WKDAddressTest.java +++ b/wkd-java/src/test/java/pgp/wkd/WKDAddressTest.java @@ -11,6 +11,7 @@ import java.net.URI; import java.util.Arrays; import org.junit.jupiter.api.Test; +import pgp.wkd.exception.MalformedUserIdException; public class WKDAddressTest { @@ -70,6 +71,22 @@ public class WKDAddressTest { } } + @Test + public void fromLocalAndDomainPartTest() { + String validLocalPart = "Alice93"; + String validDomainPart = "pgpainless.org"; + + String invalidLocalPart = "contains whitespace"; + String invalidDomainPart = "contains white.space"; + + WKDAddress address = WKDAddress.fromLocalAndDomainPart(validLocalPart, validDomainPart); + assertEquals("Alice93@pgpainless.org", address.getEmail()); + + assertThrows(IllegalArgumentException.class, () -> WKDAddress.fromLocalAndDomainPart(invalidLocalPart, validDomainPart)); + assertThrows(IllegalArgumentException.class, () -> WKDAddress.fromLocalAndDomainPart(validLocalPart, invalidDomainPart)); + assertThrows(IllegalArgumentException.class, () -> WKDAddress.fromLocalAndDomainPart(invalidLocalPart, invalidDomainPart)); + } + @Test public void testFromInvalidEmail() { for (String brokenEmail : Arrays.asList("john.doe", "@example.org", "john doe@example.org", "john.doe@example org")) {