mirror of https://github.com/shred/acme4j
Automatically generate CSR in Order class
With this change, it is not stricly required anymore to create the CSR oneself. The Order class contains all information to generate a basic CSR itself.pull/140/head
parent
e22b47f140
commit
e8b83d6423
|
@ -15,18 +15,23 @@ package org.shredzone.acme4j;
|
|||
|
||||
import static java.util.stream.Collectors.toUnmodifiableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.exception.AcmeNotSupportedException;
|
||||
import org.shredzone.acme4j.toolbox.JSON;
|
||||
import org.shredzone.acme4j.toolbox.JSON.Value;
|
||||
import org.shredzone.acme4j.toolbox.JSONBuilder;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -161,18 +166,91 @@ public class Order extends AcmeJsonResource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Finalizes the order, by providing a CSR.
|
||||
* Finalizes the order.
|
||||
* <p>
|
||||
* After a successful finalization, the certificate is available at
|
||||
* If the finalization was successful, the certificate is provided via
|
||||
* {@link #getCertificate()}.
|
||||
* <p>
|
||||
* Even though the ACME protocol uses the term "finalize an order", this method is
|
||||
* called {@link #execute(byte[])} to avoid confusion with the problematic
|
||||
* called {@link #execute(KeyPair)} to avoid confusion with the problematic
|
||||
* {@link Object#finalize()} method.
|
||||
*
|
||||
* @param domainKeyPair
|
||||
* The {@link KeyPair} that is going to be certified. This is <em>not</em>
|
||||
* your account's keypair!
|
||||
* @see #execute(KeyPair, Consumer)
|
||||
* @see #execute(PKCS10CertificationRequest)
|
||||
* @see #execute(byte[])
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(KeyPair domainKeyPair) throws AcmeException {
|
||||
execute(domainKeyPair, csrBuilder -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the order (see {@link #execute(KeyPair)}).
|
||||
* <p>
|
||||
* This method also accepts a builderConsumer that can be used to add further details
|
||||
* to the CSR (e.g. your organization). The identifiers (IPs, domain names, etc.) are
|
||||
* automatically added to the CSR.
|
||||
*
|
||||
* @param domainKeyPair
|
||||
* The {@link KeyPair} that is going to be used together with the certificate.
|
||||
* This is not your account's keypair!
|
||||
* @param builderConsumer
|
||||
* {@link Consumer} that adds further details to the provided
|
||||
* {@link CSRBuilder}.
|
||||
* @see #execute(KeyPair)
|
||||
* @see #execute(PKCS10CertificationRequest)
|
||||
* @see #execute(byte[])
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(KeyPair domainKeyPair, Consumer<CSRBuilder> builderConsumer) throws AcmeException {
|
||||
try {
|
||||
var csrBuilder = new CSRBuilder();
|
||||
csrBuilder.addIdentifiers(getIdentifiers());
|
||||
builderConsumer.accept(csrBuilder);
|
||||
csrBuilder.sign(domainKeyPair);
|
||||
execute(csrBuilder.getCSR());
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeException("Failed to create CSR", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the order (see {@link #execute(KeyPair)}).
|
||||
* <p>
|
||||
* This method receives a {@link PKCS10CertificationRequest} instance of a CSR that
|
||||
* was generated externally. Use this method to gain full control over the content of
|
||||
* the CSR. The CSR is not checked by acme4j, but just transported to the CA. It is
|
||||
* your responsibility that it matches to the order.
|
||||
*
|
||||
* @param csr
|
||||
* CSR containing the parameters for the certificate being requested, in DER
|
||||
* format
|
||||
* {@link PKCS10CertificationRequest} to be used for this order.
|
||||
* @see #execute(KeyPair)
|
||||
* @see #execute(KeyPair, Consumer)
|
||||
* @see #execute(byte[])
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public void execute(PKCS10CertificationRequest csr) throws AcmeException {
|
||||
try {
|
||||
execute(csr.getEncoded());
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeException("Invalid CSR", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the order (see {@link #execute(KeyPair)}).
|
||||
* <p>
|
||||
* This method receives a byte array containing an encoded CSR that was generated
|
||||
* externally. Use this method to gain full control over the content of the CSR. The
|
||||
* CSR is not checked by acme4j, but just transported to the CA. It is your
|
||||
* responsibility that it matches to the order.
|
||||
*
|
||||
* @param csr
|
||||
* Binary representation of a CSR containing the parameters for the
|
||||
* certificate being requested, in DER format
|
||||
*/
|
||||
public void execute(byte[] csr) throws AcmeException {
|
||||
LOG.debug("finalize");
|
||||
|
|
|
@ -17,7 +17,6 @@ import java.io.File;
|
|||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
|
@ -41,7 +40,6 @@ import org.shredzone.acme4j.challenge.Challenge;
|
|||
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
||||
import org.shredzone.acme4j.challenge.Http01Challenge;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -104,18 +102,8 @@ public class ClientTest {
|
|||
authorize(auth);
|
||||
}
|
||||
|
||||
// Generate a CSR for all of the domains, and sign it with the domain key pair.
|
||||
CSRBuilder csrb = new CSRBuilder();
|
||||
csrb.addDomains(domains);
|
||||
csrb.sign(domainKeyPair);
|
||||
|
||||
// Write the CSR to a file, for later use.
|
||||
try (Writer out = new FileWriter(DOMAIN_CSR_FILE)) {
|
||||
csrb.write(out);
|
||||
}
|
||||
|
||||
// Order the certificate
|
||||
order.execute(csrb.getEncoded());
|
||||
order.execute(domainKeyPair);
|
||||
|
||||
// Wait for the order to complete
|
||||
try {
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.shredzone.acme4j.challenge.Http01Challenge;
|
|||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
|
||||
import org.shredzone.acme4j.it.BammBammClient;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||
|
||||
/**
|
||||
|
@ -79,12 +78,7 @@ public class OrderHttpIT {
|
|||
client.httpRemoveToken(challenge.getToken());
|
||||
}
|
||||
|
||||
var csr = new CSRBuilder();
|
||||
csr.addDomain(TEST_DOMAIN);
|
||||
csr.sign(domainKeyPair);
|
||||
var encodedCsr = csr.getEncoded();
|
||||
|
||||
order.execute(encodedCsr);
|
||||
order.execute(domainKeyPair);
|
||||
|
||||
await()
|
||||
.pollInterval(1, SECONDS)
|
||||
|
|
|
@ -39,7 +39,6 @@ import org.shredzone.acme4j.challenge.Http01Challenge;
|
|||
import org.shredzone.acme4j.challenge.TlsAlpn01Challenge;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.exception.AcmeServerException;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
|
||||
/**
|
||||
* Tests a complete certificate order with different challenges.
|
||||
|
@ -179,12 +178,7 @@ public class OrderIT extends PebbleITBase {
|
|||
assertThat(auth.getStatus()).isEqualTo(Status.VALID);
|
||||
}
|
||||
|
||||
var csr = new CSRBuilder();
|
||||
csr.addDomain(domain);
|
||||
csr.sign(domainKeyPair);
|
||||
var encodedCsr = csr.getEncoded();
|
||||
|
||||
order.execute(encodedCsr);
|
||||
order.execute(domainKeyPair);
|
||||
|
||||
await()
|
||||
.pollInterval(1, SECONDS)
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.shredzone.acme4j.AccountBuilder;
|
|||
import org.shredzone.acme4j.Session;
|
||||
import org.shredzone.acme4j.Status;
|
||||
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
|
||||
/**
|
||||
* Tests a complete wildcard certificate order. Wildcard certificates currently only
|
||||
|
@ -95,13 +94,7 @@ public class OrderWildcardIT extends PebbleITBase {
|
|||
assertThat(auth.getStatus()).isEqualTo(Status.VALID);
|
||||
}
|
||||
|
||||
var csr = new CSRBuilder();
|
||||
csr.addDomain(TEST_DOMAIN);
|
||||
csr.addDomain(TEST_WILDCARD_DOMAIN);
|
||||
csr.sign(domainKeyPair);
|
||||
var encodedCsr = csr.getEncoded();
|
||||
|
||||
order.execute(encodedCsr);
|
||||
order.execute(domainKeyPair);
|
||||
|
||||
await()
|
||||
.pollInterval(1, SECONDS)
|
||||
|
@ -115,7 +108,10 @@ public class OrderWildcardIT extends PebbleITBase {
|
|||
assertThat(cert).isNotNull();
|
||||
assertThat(cert.getNotAfter()).isNotEqualTo(notBefore);
|
||||
assertThat(cert.getNotBefore()).isNotEqualTo(notAfter);
|
||||
assertThat(cert.getSubjectX500Principal().getName()).contains("CN=" + TEST_DOMAIN);
|
||||
assertThat(cert.getSubjectX500Principal().getName()).satisfiesAnyOf(
|
||||
name -> assertThat(name).contains("CN=" + TEST_DOMAIN),
|
||||
name -> assertThat(name).contains("CN=" + TEST_WILDCARD_DOMAIN)
|
||||
);
|
||||
|
||||
var san = cert.getSubjectAlternativeNames().stream()
|
||||
.filter(it -> ((Number) it.get(0)).intValue() == GeneralName.dNSName)
|
||||
|
|
|
@ -75,13 +75,40 @@ The response you have set up before is not needed any more. You can (and should)
|
|||
|
||||
## Finalize the Order
|
||||
|
||||
After successfully completing all authorizations, the order needs to be finalized by providing PKCS#10 CSR file. A single domain may be set as _Common Name_. Multiple domains must be provided as _Subject Alternative Name_. You must provide exactly the domains that you had passed to the `order()` method above, otherwise the finalization will fail. It depends on the CA if other CSR properties (_Organization_, _Organization Unit_ etc.) are accepted. Some may require these properties to be set, while others may ignore them when generating the certificate.
|
||||
After successfully completing all authorizations, the order needs to be finalized.
|
||||
|
||||
CSR files can be generated with command line tools like `openssl`. Unfortunately the standard Java does not offer classes for that, so you'd have to resort to [Bouncy Castle](http://www.bouncycastle.org/java.html) if you want to create a CSR programmatically. There is a [`CSRBuilder`](../acme4j-client/apidocs/org.shredzone.acme4j.utils/org/shredzone/acme4j/util/CSRBuilder.html) for your convenience. You can also use [`KeyPairUtils`](../acme4j-client/apidocs/org.shredzone.acme4j.utils/org/shredzone/acme4j/util/KeyPairUtils.html) for generating a new key pair for your domain.
|
||||
First of all, you will need to generate a new key pair that is used for certification and encryption. You can use [`KeyPairUtils`](../acme4j-client/apidocs/org.shredzone.acme4j.utils/org/shredzone/acme4j/util/KeyPairUtils.html) for generating a new key pair for your domain.
|
||||
|
||||
!!! tip
|
||||
Never use your account key pair as domain key pair, but always generate separate key pairs!
|
||||
|
||||
After that, the order can be finalized:
|
||||
|
||||
```java
|
||||
KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption
|
||||
|
||||
order.execute(domainKeyPair);
|
||||
```
|
||||
|
||||
_acme4j_ will automatically take care of creating a minimal CSR for this order. If you need to expand this CSR (e.g. with your company name), you can do so:
|
||||
|
||||
```java
|
||||
order.execute(domainKeyPair, csr -> {
|
||||
csr.setOrganization("ACME Corp.");
|
||||
});
|
||||
```
|
||||
|
||||
It depends on the CA if other CSR properties (like _Organization_, _Organization Unit_) are accepted. Some may even require these properties to be set, while others may ignore them when generating the certificate.
|
||||
|
||||
!!! note
|
||||
The correct technical term is _finalization_ of an order, according to RFC-8555. However, Java has a method called `Object.finalize()` which is problematic and should not be used. To avoid confusion with that method, the finalization methods are intentionally called `execute`.
|
||||
|
||||
## Using CSR Files
|
||||
|
||||
If you need more control over the CSR file, you can also provide a PKCS#10 CSR file, either as `PKCS10CertificationRequest` instance or as DER formatted binary. A single domain may be set as _Common Name_. Multiple domains must be provided as _Subject Alternative Name_. You must provide exactly the domains that you had passed to the `order()` method above, otherwise the finalization will fail.
|
||||
|
||||
You can use command like tools like `openssl` or Java frameworks like [Bouncy Castle](http://www.bouncycastle.org/java.html) for generating the CSR file. There is also a [`CSRBuilder`](../acme4j-client/apidocs/org.shredzone.acme4j.utils/org/shredzone/acme4j/util/CSRBuilder.html) for your convenience.
|
||||
|
||||
```java
|
||||
KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption
|
||||
|
||||
|
@ -94,13 +121,13 @@ csrb.sign(domainKeyPair);
|
|||
byte[] csr = csrb.getEncoded();
|
||||
```
|
||||
|
||||
It is a good idea to store the generated CSR somewhere, as you will need it again for renewal:
|
||||
Optionally the CSR can be written as a file, so you can use it again (e.g. for renewal of the certificate).
|
||||
|
||||
```java
|
||||
csrb.write(new FileWriter("example.csr"));
|
||||
```
|
||||
|
||||
After that, finalize the order:
|
||||
After that, finalize the order by providing the CSR:
|
||||
|
||||
```java
|
||||
order.execute(csr);
|
||||
|
|
Loading…
Reference in New Issue