mirror of https://github.com/shred/acme4j
Create tls-alpn-01 cert in challenge class
parent
16b02efe23
commit
e22b47f140
|
@ -15,8 +15,14 @@ package org.shredzone.acme4j.challenge;
|
|||
|
||||
import static org.shredzone.acme4j.toolbox.AcmeUtils.sha256hash;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.shredzone.acme4j.Identifier;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.toolbox.JSON;
|
||||
import org.shredzone.acme4j.util.CertificateUtils;
|
||||
|
||||
/**
|
||||
* Implements the {@value TYPE} challenge. It requires a specific certificate that can be
|
||||
|
@ -63,6 +69,25 @@ public class TlsAlpn01Challenge extends TokenChallenge {
|
|||
return sha256hash(getAuthorization());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a self-signed {@link X509Certificate} for this challenge. The certificate
|
||||
* is valid for 7 days.
|
||||
*
|
||||
* @param keypair
|
||||
* A domain {@link KeyPair} to be used for the challenge
|
||||
* @param id
|
||||
* The {@link Identifier} that is to be validated
|
||||
* @return Created certificate
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public X509Certificate createCertificate(KeyPair keypair, Identifier id) {
|
||||
try {
|
||||
return CertificateUtils.createTlsAlpn01Certificate(keypair, id, getAcmeValidation());
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Bad certificate parameters", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptable(String type) {
|
||||
return TYPE.equals(type);
|
||||
|
|
|
@ -15,14 +15,19 @@ package org.shredzone.acme4j.challenge;
|
|||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.shredzone.acme4j.toolbox.TestUtils.getJSON;
|
||||
|
||||
import java.security.cert.CertificateParsingException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.shredzone.acme4j.Identifier;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.Status;
|
||||
import org.shredzone.acme4j.toolbox.AcmeUtils;
|
||||
import org.shredzone.acme4j.toolbox.JSONBuilder;
|
||||
import org.shredzone.acme4j.toolbox.TestUtils;
|
||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TlsAlpn01ChallengeTest}.
|
||||
|
@ -54,4 +59,25 @@ public class TlsAlpn01ChallengeTest {
|
|||
assertThatJson(response.toString()).isEqualTo("{}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that {@link TlsAlpn01Challenge} generates a correct test certificate
|
||||
*/
|
||||
@Test
|
||||
public void testTlsAlpn01Certificate() throws CertificateParsingException {
|
||||
var challenge = new TlsAlpn01Challenge(login, getJSON("tlsAlpnChallenge"));
|
||||
var keypair = KeyPairUtils.createKeyPair(2048);
|
||||
var subject = Identifier.dns("example.com");
|
||||
|
||||
var certificate = challenge.createCertificate(keypair, subject);
|
||||
|
||||
// Only check the main requirements. Cert generation is fully tested in CertificateUtilsTest.
|
||||
assertThat(certificate).isNotNull();
|
||||
assertThat(certificate.getSubjectX500Principal().getName()).isEqualTo("CN=acme.invalid");
|
||||
assertThat(certificate.getSubjectAlternativeNames().stream()
|
||||
.map(l -> l.get(1))
|
||||
.map(Object::toString)).contains(subject.getDomain());
|
||||
assertThat(certificate.getCriticalExtensionOIDs()).contains(TlsAlpn01Challenge.ACME_VALIDATION_OID);
|
||||
assertThatNoException().isThrownBy(() -> certificate.verify(keypair.getPublic()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,30 +2,22 @@
|
|||
|
||||
With the `tls-alpn-01` challenge, you prove to the CA that you are able to control the web server of the domain to be authorized, by letting it respond to a request with a specific self-signed cert utilizing the ALPN extension. This challenge is specified in [RFC 8737](https://tools.ietf.org/html/rfc8737).
|
||||
|
||||
`TlsAlpn01Challenge` provides a byte array called `acmeValidation`:
|
||||
You need to create a self-signed certificate with the domain to be validated set as the only _Subject Alternative Name_. The byte array provided by `challenge.getAcmeValidation()` must be set as DER encoded `OCTET STRING` extension with the object id `1.3.6.1.5.5.7.1.31`. It is required to set this extension as critical.
|
||||
|
||||
_acme4j_ does the heavy lifting for you though, and provides a certificate that is ready to use. It is valid for 7 days, which is ample of time to perform the validation.
|
||||
|
||||
```java
|
||||
TlsAlpn01Challenge challenge = auth.findChallenge(TlsAlpn01Challenge.class);
|
||||
Identifier identifier = auth.getIdentifier();
|
||||
|
||||
byte[] acmeValidation = challenge.getAcmeValidation();
|
||||
```
|
||||
|
||||
You need to create a self-signed certificate with the domain to be validated set as the only _Subject Alternative Name_. The `acmeValidation` must be set as DER encoded `OCTET STRING` extension with the object id `1.3.6.1.5.5.7.1.31`. It is required to set this extension as critical.
|
||||
|
||||
After that, configure your web server so it will use this certificate on an incoming TLS request having the SNI `identifier` and the ALPN protocol `acme-tls/1`.
|
||||
|
||||
The `TlsAlpn01Challenge` class does not generate a self-signed certificate for you, as it would require _Bouncy Castle_. However, there is a utility method for this use case:
|
||||
|
||||
```java
|
||||
KeyPair certKeyPair = KeyPairUtils.createKeyPair(2048);
|
||||
|
||||
X509Certificate cert = CertificateUtils.
|
||||
createTlsAlpn01Certificate(certKeyPair, identifier, acmeValidation);
|
||||
X509Certificate cert = challenge.createCertificate(certKeyPair, identifier);
|
||||
```
|
||||
|
||||
Now use `cert` and `certKeyPair` to let your web server respond to TLS requests containing an ALPN extension with the value `acme-tls/1` and a SNI extension containing your subject (`identifier`).
|
||||
|
||||
When the validation is completed, the `cert` and `certKeyPair` are not used anymore and can be disposed.
|
||||
|
||||
!!! note
|
||||
The request is sent to port 443 only. If your domain has multiple IP addresses, the CA randomly selects some of them. There is no way to choose a different port or a fixed IP address.
|
||||
|
||||
|
|
Loading…
Reference in New Issue