diff --git a/README.md b/README.md index 1a1f9a15..d2e6d0bb 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It is an independent open source implementation that is not affiliated with or e * Supports [RFC 8738](https://tools.ietf.org/html/rfc8738) IP identifier validation * Supports [RFC 8739](https://tools.ietf.org/html/rfc8739) short-term automatic certificate renewal (experimental) * Easy to use Java API -* Requires JRE 8 (update 101) or higher +* Requires JRE 8 (update 101) or higher. For building the project, Java 9 or higher is required. * Built with maven, packages available at [Maven Central](http://search.maven.org/#search|ga|1|g%3A%22org.shredzone.acme4j%22) * Requires [jose4j](https://bitbucket.org/b_c/jose4j/wiki/Home), [Bouncy Castle](https://www.bouncycastle.org/) and [slf4j](http://www.slf4j.org/) as dependencies. If you have other means of generating key pairs and CSRs, you can even do without `acme4j-utils` and Bouncy Castle as dependency. * Extensive unit and integration tests @@ -23,7 +23,7 @@ It is an independent open source implementation that is not affiliated with or e ## Usage * See the [online documentation](https://shredzone.org/maven/acme4j/) about how to use _acme4j_. -* For a quick start, have a look at [the source code of an example](https://github.com/shred/acme4j/blob/master/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java). +* For a quick start, have a look at [the source code of an example](https://github.com/shred/acme4j/blob/master/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java). ## Contribute diff --git a/acme4j-client/pom.xml b/acme4j-client/pom.xml index da79efcd..e9301504 100644 --- a/acme4j-client/pom.xml +++ b/acme4j-client/pom.xml @@ -41,13 +41,11 @@ org.apache.maven.plugins - maven-jar-plugin + maven-surefire-plugin - - - org.shredzone.acme4j - - + + false diff --git a/acme4j-client/src/main/java/module-info.java b/acme4j-client/src/main/java/module-info.java new file mode 100644 index 00000000..3dfe4708 --- /dev/null +++ b/acme4j-client/src/main/java/module-info.java @@ -0,0 +1,33 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2020 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +module org.shredzone.acme4j { + requires static com.github.spotbugs.annotations; + requires org.jose4j; + requires org.slf4j; + + exports org.shredzone.acme4j; + exports org.shredzone.acme4j.challenge; + exports org.shredzone.acme4j.connector; + exports org.shredzone.acme4j.exception; + exports org.shredzone.acme4j.provider; + exports org.shredzone.acme4j.toolbox; + + uses org.shredzone.acme4j.provider.AcmeProvider; + + provides org.shredzone.acme4j.provider.AcmeProvider + with org.shredzone.acme4j.provider.GenericAcmeProvider, + org.shredzone.acme4j.provider.letsencrypt.LetsEncryptAcmeProvider, + org.shredzone.acme4j.provider.pebble.PebbleAcmeProvider; +} diff --git a/acme4j-example/pom.xml b/acme4j-example/pom.xml index f92b05fc..414499a8 100644 --- a/acme4j-example/pom.xml +++ b/acme4j-example/pom.xml @@ -35,17 +35,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - - - - org.shredzone.acme4j.example - - - - org.codehaus.mojo exec-maven-plugin @@ -58,7 +47,7 @@ - org.shredzone.acme4j.ClientTest + org.shredzone.acme4j.example.ClientTest diff --git a/acme4j-example/src/main/java/module-info.java b/acme4j-example/src/main/java/module-info.java new file mode 100644 index 00000000..eaf613e1 --- /dev/null +++ b/acme4j-example/src/main/java/module-info.java @@ -0,0 +1,23 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2020 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +module org.shredzone.acme4j.example { + requires org.shredzone.acme4j; + requires org.shredzone.acme4j.utils; + + requires java.desktop; + + requires org.bouncycastle.provider; + requires org.slf4j; +} diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java similarity index 87% rename from acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java rename to acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java index d6a32880..8f3ffca5 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java @@ -11,7 +11,7 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ -package org.shredzone.acme4j; +package org.shredzone.acme4j.example; import java.io.File; import java.io.FileReader; @@ -28,6 +28,13 @@ import java.util.Collection; import javax.swing.JOptionPane; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.shredzone.acme4j.Account; +import org.shredzone.acme4j.AccountBuilder; +import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Certificate; +import org.shredzone.acme4j.Order; +import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Status; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; @@ -63,14 +70,14 @@ public class ClientTest { private static final Logger LOG = LoggerFactory.getLogger(ClientTest.class); - private enum ChallengeType { HTTP, DNS } + private enum ChallengeType {HTTP, DNS} /** * Generates a certificate for the given domains. Also takes care for the registration * process. * * @param domains - * Domains to get a common certificate for + * Domains to get a common certificate for */ public void fetchCertificate(Collection domains) throws IOException, AcmeException { // Load the user key file. If there is no key file, create a new one. @@ -144,8 +151,8 @@ public class ClientTest { } /** - * Loads a user key pair from {@value #USER_KEY_FILE}. If the file does not exist, - * a new key pair is generated and saved. + * Loads a user key pair from {@link #USER_KEY_FILE}. If the file does not exist, a + * new key pair is generated and saved. *

* Keep this key pair in a safe place! In a production environment, you will not be * able to access your account again if you should lose the key pair. @@ -170,7 +177,7 @@ public class ClientTest { } /** - * Loads a domain key pair from {@value #DOMAIN_KEY_FILE}. If the file does not exist, + * Loads a domain key pair from {@link #DOMAIN_KEY_FILE}. If the file does not exist, * a new key pair is generated and saved. * * @return Domain {@link KeyPair}. @@ -196,12 +203,12 @@ public class ClientTest { *

* This is a simple way of finding your {@link Account}. A better way is to get the * URL of your new account with {@link Account#getLocation()} and store it somewhere. - * If you need to get access to your account later, reconnect to it via - * {@link Session#login(URL, KeyPair)} by using the stored location. + * If you need to get access to your account later, reconnect to it via {@link + * Session#login(URL, KeyPair)} by using the stored location. * * @param session - * {@link Session} to bind with - * @return {@link Login} that is connected to your account + * {@link Session} to bind with + * @return {@link Account} */ private Account findOrRegisterAccount(Session session, KeyPair accountKey) throws AcmeException { // Ask the user to accept the TOS, if server provides us with a link. @@ -211,9 +218,9 @@ public class ClientTest { } Account account = new AccountBuilder() - .agreeToTermsOfService() - .useKeyPair(accountKey) - .create(session); + .agreeToTermsOfService() + .useKeyPair(accountKey) + .create(session); LOG.info("Registered a new user, URL: {}", account.getLocation()); return account; @@ -224,7 +231,7 @@ public class ClientTest { * retrieve a signed certificate for the domain later. * * @param auth - * {@link Authorization} to perform + * {@link Authorization} to perform */ private void authorize(Authorization auth) throws AcmeException { LOG.info("Authorization for domain {}", auth.getIdentifier().getDomain()); @@ -299,7 +306,7 @@ public class ClientTest { * use a servlet that returns {@link Http01Challenge#getAuthorization()}. * * @param auth - * {@link Authorization} to find the challenge in + * {@link Authorization} to find the challenge in * @return {@link Challenge} to verify */ public Challenge httpChallenge(Authorization auth) throws AcmeException { @@ -312,7 +319,7 @@ public class ClientTest { // Output the challenge, wait for acknowledge... LOG.info("Please create a file in your web server's base directory."); LOG.info("It must be reachable at: http://{}/.well-known/acme-challenge/{}", - auth.getIdentifier().getDomain(), challenge.getToken()); + auth.getIdentifier().getDomain(), challenge.getToken()); LOG.info("File name: {}", challenge.getToken()); LOG.info("Content: {}", challenge.getAuthorization()); LOG.info("The file must not contain any leading or trailing whitespaces or line breaks!"); @@ -321,10 +328,10 @@ public class ClientTest { StringBuilder message = new StringBuilder(); message.append("Please create a file in your web server's base directory.\n\n"); message.append("http://") - .append(auth.getIdentifier().getDomain()) - .append("/.well-known/acme-challenge/") - .append(challenge.getToken()) - .append("\n\n"); + .append(auth.getIdentifier().getDomain()) + .append("/.well-known/acme-challenge/") + .append(challenge.getToken()) + .append("\n\n"); message.append("Content:\n\n"); message.append(challenge.getAuthorization()); acceptChallenge(message.toString()); @@ -341,7 +348,7 @@ public class ClientTest { * production environment, you would rather configure your DNS automatically. * * @param auth - * {@link Authorization} to find the challenge in + * {@link Authorization} to find the challenge in * @return {@link Challenge} to verify */ public Challenge dnsChallenge(Authorization auth) throws AcmeException { @@ -354,15 +361,15 @@ public class ClientTest { // Output the challenge, wait for acknowledge... LOG.info("Please create a TXT record:"); LOG.info("_acme-challenge.{}. IN TXT {}", - auth.getIdentifier().getDomain(), challenge.getDigest()); + auth.getIdentifier().getDomain(), challenge.getDigest()); LOG.info("If you're ready, dismiss the dialog..."); StringBuilder message = new StringBuilder(); message.append("Please create a TXT record:\n\n"); message.append("_acme-challenge.") - .append(auth.getIdentifier().getDomain()) - .append(". IN TXT ") - .append(challenge.getDigest()); + .append(auth.getIdentifier().getDomain()) + .append(". IN TXT ") + .append(challenge.getDigest()); acceptChallenge(message.toString()); return challenge; @@ -373,13 +380,13 @@ public class ClientTest { * dismissal. If the user cancelled the dialog, an exception is thrown. * * @param message - * Instructions to be shown in the dialog + * Instructions to be shown in the dialog */ public void acceptChallenge(String message) throws AcmeException { int option = JOptionPane.showConfirmDialog(null, - message, - "Prepare Challenge", - JOptionPane.OK_CANCEL_OPTION); + message, + "Prepare Challenge", + JOptionPane.OK_CANCEL_OPTION); if (option == JOptionPane.CANCEL_OPTION) { throw new AcmeException("User cancelled the challenge"); } @@ -390,13 +397,13 @@ public class ClientTest { * dismissal. * * @param message - * Instructions to be shown in the dialog + * Instructions to be shown in the dialog */ public void completeChallenge(String message) throws AcmeException { JOptionPane.showMessageDialog(null, - message, - "Complete Challenge", - JOptionPane.INFORMATION_MESSAGE); + message, + "Complete Challenge", + JOptionPane.INFORMATION_MESSAGE); } /** @@ -404,13 +411,13 @@ public class ClientTest { * user denies confirmation, an exception is thrown. * * @param agreement - * {@link URI} of the Terms of Service + * {@link URI} of the Terms of Service */ public void acceptAgreement(URI agreement) throws AcmeException { int option = JOptionPane.showConfirmDialog(null, - "Do you accept the Terms of Service?\n\n" + agreement, - "Accept ToS", - JOptionPane.YES_NO_OPTION); + "Do you accept the Terms of Service?\n\n" + agreement, + "Accept ToS", + JOptionPane.YES_NO_OPTION); if (option == JOptionPane.NO_OPTION) { throw new AcmeException("User did not accept Terms of Service"); } @@ -420,7 +427,7 @@ public class ClientTest { * Invokes this example. * * @param args - * Domains to get a certificate for + * Domains to get a certificate for */ public static void main(String... args) { if (args.length == 0) { diff --git a/acme4j-it/pom.xml b/acme4j-it/pom.xml index 39ada1b7..ce9f8857 100644 --- a/acme4j-it/pom.xml +++ b/acme4j-it/pom.xml @@ -102,17 +102,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - - - - org.shredzone.acme4j.it - - - - io.fabric8 docker-maven-plugin diff --git a/acme4j-it/src/main/java/module-info.java b/acme4j-it/src/main/java/module-info.java new file mode 100644 index 00000000..77f3c153 --- /dev/null +++ b/acme4j-it/src/main/java/module-info.java @@ -0,0 +1,22 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2020 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +module org.shredzone.acme4j.it { + requires org.shredzone.acme4j; + requires org.shredzone.acme4j.utils; + + requires com.github.spotbugs.annotations; + requires org.apache.httpcomponents.httpclient; + requires org.apache.httpcomponents.httpcore; +} diff --git a/acme4j-utils/pom.xml b/acme4j-utils/pom.xml index 0d932192..7a95bfb3 100644 --- a/acme4j-utils/pom.xml +++ b/acme4j-utils/pom.xml @@ -28,22 +28,6 @@ acme4j Utils acme4j utilities - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.shredzone.acme4j.utils - - - - - - - org.shredzone.acme4j diff --git a/acme4j-utils/src/main/java/module-info.java b/acme4j-utils/src/main/java/module-info.java new file mode 100644 index 00000000..d296b35b --- /dev/null +++ b/acme4j-utils/src/main/java/module-info.java @@ -0,0 +1,23 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2020 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +module org.shredzone.acme4j.utils { + requires org.shredzone.acme4j; + + requires com.github.spotbugs.annotations; + requires org.bouncycastle.pkix; + requires org.bouncycastle.provider; + + exports org.shredzone.acme4j.util; +} diff --git a/pom.xml b/pom.xml index 0a1988f8..823f13c5 100644 --- a/pom.xml +++ b/pom.xml @@ -73,9 +73,27 @@ maven-compiler-plugin 3.8.0 - 1.8 - 1.8 + 8 + + + default-compile + + 9 + + + + base-compile + + compile + + + + module-info.java + + + + com.github.spotbugs @@ -95,8 +113,8 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.1 - + 3.0.0-M4 + classes 10 java.net.HttpURLConnection @@ -152,7 +170,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.2.0 syntax,reference true @@ -214,42 +232,4 @@ test - - - - - java-9 - - [9,10] - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - --no-module-directories - - - - - - - java-11 - - [11,) - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - - - - - - diff --git a/src/doc/docs/index.md b/src/doc/docs/index.md index 634fd6d9..e306eaff 100644 --- a/src/doc/docs/index.md +++ b/src/doc/docs/index.md @@ -19,16 +19,16 @@ Latest version: ![maven central](https://shredzone.org/maven-central/org.shredzo * Supports [RFC 8738](https://tools.ietf.org/html/rfc8738) IP identifier validation * Supports [RFC 8739](https://tools.ietf.org/html/rfc8739) short-term automatic certificate renewal (experimental) * Easy to use Java API -* Requires JRE 8 (update 101) or higher +* Requires JRE 8 (update 101) or higher. For building the project, Java 9 or higher is required. * Built with maven, packages available at [Maven Central](http://search.maven.org/#search|ga|1|g%3A%22org.shredzone.acme4j%22) * Requires [jose4j](https://bitbucket.org/b_c/jose4j/wiki/Home), [Bouncy Castle](https://www.bouncycastle.org/) and [slf4j](http://www.slf4j.org/) as dependencies. If you have other means of generating key pairs and CSRs, you can even do without `acme4j-utils` and Bouncy Castle as dependency. * Extensive unit and integration tests ## Quick Start -[This source code](acme4j-example/apidocs/src-html/org/shredzone/acme4j/ClientTest.html) gives an example of how to get a TLS certificate with _acme4j_. +[This source code](acme4j-example/apidocs/src-html/org/shredzone/acme4j/example/ClientTest.html) gives an example of how to get a TLS certificate with _acme4j_. -You can run the `org.shredzone.acme4j.ClientTest` class in your IDE, with the domains to be registered as parameters. The test client can also be invoked via maven in a command line: +You can run the `org.shredzone.acme4j.example.ClientTest` class in your IDE, with the domains to be registered as parameters. The test client can also be invoked via maven in a command line: ```sh mvn exec:java -Dexec.args="example.com example.org" @@ -51,7 +51,7 @@ acme4j-utils The Java module name is `org.shredzone.acme4j.utils`. acme4j-example -: This module only contains [an example code](https://shredzone.org/maven/acme4j/acme4j-example/apidocs/src-html/org/shredzone/acme4j/ClientTest.html) that demonstrates how to get a certificate with _acme4j_. It depends on `acme4j-client` and `acme4j-utils`. It is not useful as a dependency in other projects. +: This module only contains [an example code](https://shredzone.org/maven/acme4j/acme4j-example/apidocs/src-html/org/shredzone/acme4j/example/ClientTest.html) that demonstrates how to get a certificate with _acme4j_. It depends on `acme4j-client` and `acme4j-utils`. It is not useful as a dependency in other projects. acme4j-it : [`acme4j-it`](https://mvnrepository.com/artifact/org.shredzone.acme4j/acme4j-it/latest) mainly serves as integration test suite for _acme4j_ itself. It is not really useful as a dependency in other projects. However if you write own integration tests using [pebble](https://github.com/letsencrypt/pebble) and [pebble-challtestsrv](https://hub.docker.com/r/letsencrypt/pebble-challtestsrv), you may find the [`challtestsrv` configuration client](https://shredzone.org/maven/acme4j/acme4j-it/apidocs/org/shredzone/acme4j/it/BammBammClient.html) useful in your project. diff --git a/src/doc/docs/migration.md b/src/doc/docs/migration.md index 538d7512..6ffe0994 100644 --- a/src/doc/docs/migration.md +++ b/src/doc/docs/migration.md @@ -4,6 +4,8 @@ This document will help you migrate your code to the latest _acme4j_ version. ## Migration to Version 2.10 +- acme4j now provides real `module-info.java` definitions. It also means that for _building_ this project, Java 9 is the minimum requirement now. + - In a preparation for Java 9 modules, the JSR305 null-safe annotations have been replaced by SpotBugs annotations. This _should_ have no impact on your code, as the method signatures themselves are unchanged. However, the compiler could now complain about some `null` dereferences that have been undetected before. Reason is that JSR305 uses the `javax.annotations` package, which leads to split packages in a Java 9 modular environment. - When fetching the directory, acme4j now evaluates HTTP caching headers instead of just caching the directory for 1 hour. However, Let's Encrypt explicitly forbids caching, which means that a fresh copy of the directory is now fetched from the server every time it is needed. I don't like it, but it is the RFC conformous behavior. It needs to be [fixed on Let's Encrypt side](https://github.com/letsencrypt/boulder/issues/4814).