mirror of https://github.com/shred/acme4j
Support IP identifiers for tls-alpn-01
parent
62d2e9c1c0
commit
dc17433634
|
@ -42,6 +42,8 @@ import org.bouncycastle.openssl.PEMParser;
|
|||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.shredzone.acme4j.Authorization;
|
||||
import org.shredzone.acme4j.Identifier;
|
||||
import org.shredzone.acme4j.challenge.TlsAlpn01Challenge;
|
||||
|
||||
/**
|
||||
|
@ -95,11 +97,36 @@ public final class CertificateUtils {
|
|||
* {@link TlsAlpn01Challenge#getAcmeValidation()}
|
||||
* @return Created certificate
|
||||
* @since 2.1
|
||||
* @deprecated Use {@link #createTlsAlpn01Certificate(KeyPair, Identifier, byte[])}
|
||||
* and {@link Identifier#dns(String)}. If an {@link Authorization}
|
||||
* instance is at hand, you can also use
|
||||
* {@link Authorization#getIdentifier()}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static X509Certificate createTlsAlpn01Certificate(KeyPair keypair, String subject, byte[] acmeValidation)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(keypair, "keypair");
|
||||
Objects.requireNonNull(subject, "subject");
|
||||
return createTlsAlpn01Certificate(keypair, Identifier.dns(subject), acmeValidation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a self-signed {@link X509Certificate} that can be used for the
|
||||
* {@link TlsAlpn01Challenge}. 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
|
||||
* @param acmeValidation
|
||||
* The value that is returned by
|
||||
* {@link TlsAlpn01Challenge#getAcmeValidation()}
|
||||
* @return Created certificate
|
||||
* @since 2.6
|
||||
*/
|
||||
public static X509Certificate createTlsAlpn01Certificate(KeyPair keypair, Identifier id, byte[] acmeValidation)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(keypair, "keypair");
|
||||
Objects.requireNonNull(id, "id");
|
||||
if (acmeValidation == null || acmeValidation.length != 32) {
|
||||
throw new IllegalArgumentException("Bad acmeValidation parameter");
|
||||
}
|
||||
|
@ -118,7 +145,19 @@ public final class CertificateUtils {
|
|||
issuer, keypair.getPublic());
|
||||
|
||||
GeneralName[] gns = new GeneralName[1];
|
||||
gns[0] = new GeneralName(GeneralName.dNSName, subject);
|
||||
|
||||
switch (id.getType()) {
|
||||
case Identifier.TYPE_DNS:
|
||||
gns[0] = new GeneralName(GeneralName.dNSName, id.getDomain());
|
||||
break;
|
||||
|
||||
case Identifier.TYPE_IP:
|
||||
gns[0] = new GeneralName(GeneralName.iPAddress, id.getIP().getHostAddress());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported Identifier type " + id.getType());
|
||||
}
|
||||
certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gns));
|
||||
|
||||
certBuilder.addExtension(ACME_VALIDATION, true, new DEROctetString(acmeValidation));
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
@ -38,6 +40,7 @@ import org.bouncycastle.asn1.DEROctetString;
|
|||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.junit.Test;
|
||||
import org.shredzone.acme4j.Identifier;
|
||||
import org.shredzone.acme4j.challenge.TlsAlpn01Challenge;
|
||||
import org.shredzone.acme4j.toolbox.AcmeUtils;
|
||||
|
||||
|
@ -83,8 +86,8 @@ public class CertificateUtilsTest {
|
|||
|
||||
/**
|
||||
* Test if
|
||||
* {@link CertificateUtils#createTlsAlpn01Certificate(KeyPair, String, byte[])}
|
||||
* creates a good certificate.
|
||||
* {@link CertificateUtils#createTlsAlpn01Certificate(KeyPair, Identifier, byte[])}
|
||||
* with domain name creates a good certificate.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTlsAlpn01Certificate() throws IOException, CertificateParsingException {
|
||||
|
@ -92,7 +95,7 @@ public class CertificateUtilsTest {
|
|||
String subject = "example.com";
|
||||
byte[] acmeValidationV1 = AcmeUtils.sha256hash("rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ");
|
||||
|
||||
X509Certificate cert = CertificateUtils.createTlsAlpn01Certificate(keypair, subject, acmeValidationV1);
|
||||
X509Certificate cert = CertificateUtils.createTlsAlpn01Certificate(keypair, Identifier.dns(subject), acmeValidationV1);
|
||||
|
||||
Instant now = Instant.now();
|
||||
Instant end = now.plus(Duration.ofDays(8));
|
||||
|
@ -122,6 +125,23 @@ public class CertificateUtilsTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if
|
||||
* {@link CertificateUtils#createTlsAlpn01Certificate(KeyPair, Identifier, byte[])}
|
||||
* with IP creates a good certificate.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTlsAlpn01CertificateWithIp() throws IOException, CertificateParsingException {
|
||||
KeyPair keypair = KeyPairUtils.createKeyPair(2048);
|
||||
InetAddress subject = InetAddress.getLocalHost();
|
||||
byte[] acmeValidationV1 = AcmeUtils.sha256hash("rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ");
|
||||
|
||||
X509Certificate cert = CertificateUtils.createTlsAlpn01Certificate(keypair, Identifier.ip(subject), acmeValidationV1);
|
||||
|
||||
assertThat(cert.getSubjectX500Principal().getName(), is("CN=acme.invalid"));
|
||||
assertThat(getIpSANs(cert), contains(subject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all DNSName SANs from a certificate.
|
||||
*
|
||||
|
@ -141,4 +161,23 @@ public class CertificateUtilsTest {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all IPAddress SANs from a certificate.
|
||||
*
|
||||
* @param cert
|
||||
* {@link X509Certificate}
|
||||
* @return Set of IPAddresses
|
||||
*/
|
||||
private Set<InetAddress> getIpSANs(X509Certificate cert) throws CertificateParsingException, UnknownHostException {
|
||||
Set<InetAddress> result = new HashSet<>();
|
||||
|
||||
for (List<?> list : cert.getSubjectAlternativeNames()) {
|
||||
if (((Number) list.get(0)).intValue() == GeneralName.iPAddress) {
|
||||
result.add(InetAddress.getByName(list.get(1).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,14 +22,14 @@ After that, configure your web server so it will use this certificate on an inco
|
|||
The `TlsAlpn01Challenge` class does not generate a self-signed certificate, as it would require _Bouncy Castle_. However, there is a utility method in the _acme4j-utils_ module for this use case:
|
||||
|
||||
```java
|
||||
String subject = auth.getDomain();
|
||||
Identifier identifier = auth.getIdentifier();
|
||||
KeyPair certKeyPair = KeyPairUtils.createKeyPair(2048);
|
||||
|
||||
X509Certificate cert = CertificateUtils.
|
||||
createTlsAlpn01Certificate(certKeyPair, subject, acmeValidation);
|
||||
createTlsAlpn01Certificate(certKeyPair, identifier, acmeValidation);
|
||||
```
|
||||
|
||||
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 `subject`.
|
||||
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.
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
The request is sent to port 443 only. If your domain has multiple IP addresses, the CA randomly selects one of them. There is no way to choose a different port or a fixed IP address.
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
This document will help you migrate your code to the latest _acme4j_ version.
|
||||
|
||||
## Migration to Version 2.6
|
||||
|
||||
- If you use the `tls-alpn-01` challenge and `CertificateUtils.createTlsAlpn01Certificate()` for generating its test certificate, you need to pass the domain name as an `Identifier` object instead of a `String` now. You can use `Identifier.dns(subject)` for conversion. You can also use `Authorization.getIdentifier()` to get the `Identifier` object immediately.
|
||||
|
||||
## Migration to Version 2.5
|
||||
|
||||
- The GET compatibility mode has been removed. It also means that the `postasget=false` parameter is ignored from now on. If you need it to connect to your ACME server, do not update to this version until your ACME server has been fixed to support ACME draft 15.
|
||||
|
|
Loading…
Reference in New Issue