Accept IP addresses in CSRBuilder

pull/81/head
Richard Körber 2018-09-26 22:00:27 +02:00
parent a8a9bb4ebf
commit dec4561dff
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
3 changed files with 126 additions and 17 deletions

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.interfaces.ECKey;
@ -62,6 +63,7 @@ public class CSRBuilder {
private final X500NameBuilder namebuilder = new X500NameBuilder(X500Name.getDefaultStyle());
private final List<String> namelist = new ArrayList<>();
private final List<InetAddress> iplist = new ArrayList<>();
private PKCS10CertificationRequest csr = null;
/**
@ -108,6 +110,40 @@ public class CSRBuilder {
Arrays.stream(domains).forEach(this::addDomain);
}
/**
* Adds an {@link InetAddress}. All IP addresses will be set as iPAddress <em>Subject
* Alternative Name</em>.
*
* @param address
* {@link InetAddress} to add
* @since 2.4
*/
public void addIP(InetAddress address) {
iplist.add(requireNonNull(address));
}
/**
* Adds a {@link Collection} of IP addresses.
*
* @param ips
* Collection of IP addresses to add
* @since 2.4
*/
public void addIPs(Collection<InetAddress> ips) {
ips.forEach(this::addIP);
}
/**
* Adds multiple IP addresses.
*
* @param ips
* IP addresses to add
* @since 2.4
*/
public void addIPs(InetAddress... ips) {
Arrays.stream(ips).forEach(this::addIP);
}
/**
* Sets the organization.
* <p>
@ -161,14 +197,18 @@ public class CSRBuilder {
*/
public void sign(KeyPair keypair) throws IOException {
Objects.requireNonNull(keypair, "keypair");
if (namelist.isEmpty()) {
throw new IllegalStateException("No domain was set");
if (namelist.isEmpty() && iplist.isEmpty()) {
throw new IllegalStateException("No domain or IP address was set");
}
try {
GeneralName[] gns = new GeneralName[namelist.size()];
for (int ix = 0; ix < namelist.size(); ix++) {
gns[ix] = new GeneralName(GeneralName.dNSName, namelist.get(ix));
int ix = 0;
GeneralName[] gns = new GeneralName[namelist.size() + iplist.size()];
for (String name : namelist) {
gns[ix++] = new GeneralName(GeneralName.dNSName, name);
}
for (InetAddress ip : iplist) {
gns[ix++] = new GeneralName(GeneralName.iPAddress, ip.getHostAddress());
}
GeneralNames subjectAltName = new GeneralNames(gns);
@ -241,6 +281,9 @@ public class CSRBuilder {
StringBuilder sb = new StringBuilder();
sb.append(namebuilder.build());
sb.append(namelist.stream().collect(joining(",DNS=", ",DNS=", "")));
sb.append(iplist.stream()
.map(InetAddress::getHostAddress)
.collect(joining(",IP=", ",IP=", "")));
return sb.toString();
}

View File

@ -20,12 +20,15 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyPair;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.RDN;
@ -72,6 +75,10 @@ public class CSRBuilderTest {
builder.addDomains("jklm.no", "pqr.st");
builder.addDomains(Arrays.asList("uv.wx", "y.z"));
builder.addDomain("*.wild.card");
builder.addIP(InetAddress.getByName("192.168.0.1"));
builder.addIP(InetAddress.getByName("192.168.0.2"));
builder.addIPs(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"));
builder.addIPs(Arrays.asList(InetAddress.getByName("fd00::1"), InetAddress.getByName("fd00::2")));
builder.setCountry("XX");
builder.setLocality("Testville");
@ -81,7 +88,9 @@ public class CSRBuilderTest {
assertThat(builder.toString(), is("CN=abc.de,C=XX,L=Testville,O=Testing Co,"
+ "OU=Testunit,ST=ABC,"
+ "DNS=abc.de,DNS=fg.hi,DNS=jklm.no,DNS=pqr.st,DNS=uv.wx,DNS=y.z,DNS=*.wild.card"));
+ "DNS=abc.de,DNS=fg.hi,DNS=jklm.no,DNS=pqr.st,DNS=uv.wx,DNS=y.z,DNS=*.wild.card,"
+ "IP=192.168.0.1,IP=192.168.0.2,IP=10.0.0.1,IP=10.0.0.2,"
+ "IP=fd00:0:0:0:0:0:0:1,IP=fd00:0:0:0:0:0:0:2"));
builder.sign(testKey);
@ -104,6 +113,10 @@ public class CSRBuilderTest {
builder.addDomains("jklm.no", "pqr.st");
builder.addDomains(Arrays.asList("uv.wx", "y.z"));
builder.addDomain("*.wild.card");
builder.addIP(InetAddress.getByName("192.168.0.1"));
builder.addIP(InetAddress.getByName("192.168.0.2"));
builder.addIPs(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"));
builder.addIPs(Arrays.asList(InetAddress.getByName("fd00::1"), InetAddress.getByName("fd00::2")));
builder.setCountry("XX");
builder.setLocality("Testville");
@ -113,7 +126,9 @@ public class CSRBuilderTest {
assertThat(builder.toString(), is("CN=abc.de,C=XX,L=Testville,O=Testing Co,"
+ "OU=Testunit,ST=ABC,"
+ "DNS=abc.de,DNS=fg.hi,DNS=jklm.no,DNS=pqr.st,DNS=uv.wx,DNS=y.z,DNS=*.wild.card"));
+ "DNS=abc.de,DNS=fg.hi,DNS=jklm.no,DNS=pqr.st,DNS=uv.wx,DNS=y.z,DNS=*.wild.card,"
+ "IP=192.168.0.1,IP=192.168.0.2,IP=10.0.0.1,IP=10.0.0.2,"
+ "IP=fd00:0:0:0:0:0:0:1,IP=fd00:0:0:0:0:0:0:2"));
builder.sign(testEcKey);
@ -147,10 +162,20 @@ public class CSRBuilderTest {
ASN1Encodable[] extensions = attr[0].getAttrValues().toArray();
assertThat(extensions.length, is(1));
GeneralNames names = GeneralNames.fromExtensions((Extensions) extensions[0], Extension.subjectAlternativeName);
assertThat(names.getNames(), arrayContaining(new GeneralNameMatcher("abc.de"),
new GeneralNameMatcher("fg.hi"), new GeneralNameMatcher("jklm.no"),
new GeneralNameMatcher("pqr.st"), new GeneralNameMatcher("uv.wx"),
new GeneralNameMatcher("y.z"), new GeneralNameMatcher("*.wild.card")));
assertThat(names.getNames(), arrayContaining(
new GeneralNameMatcher("abc.de", GeneralName.dNSName),
new GeneralNameMatcher("fg.hi", GeneralName.dNSName),
new GeneralNameMatcher("jklm.no", GeneralName.dNSName),
new GeneralNameMatcher("pqr.st", GeneralName.dNSName),
new GeneralNameMatcher("uv.wx", GeneralName.dNSName),
new GeneralNameMatcher("y.z", GeneralName.dNSName),
new GeneralNameMatcher("*.wild.card", GeneralName.dNSName),
new GeneralNameMatcher("192.168.0.1", GeneralName.iPAddress),
new GeneralNameMatcher("192.168.0.2", GeneralName.iPAddress),
new GeneralNameMatcher("10.0.0.1", GeneralName.iPAddress),
new GeneralNameMatcher("10.0.0.2", GeneralName.iPAddress),
new GeneralNameMatcher("fd00:0:0:0:0:0:0:1", GeneralName.iPAddress),
new GeneralNameMatcher("fd00:0:0:0:0:0:0:2", GeneralName.iPAddress)));
}
/**
@ -266,8 +291,10 @@ public class CSRBuilderTest {
*/
private static class GeneralNameMatcher extends BaseMatcher<GeneralName> {
private final String expectedValue;
private final int expectedTag;
public GeneralNameMatcher(String expectedValue) {
public GeneralNameMatcher(String expectedValue, int expectedTag) {
this.expectedTag = expectedTag;
this.expectedValue = expectedValue;
}
@ -279,8 +306,19 @@ public class CSRBuilderTest {
GeneralName gn = (GeneralName) item;
return gn.getTagNo() == GeneralName.dNSName
&& expectedValue.equals(DERIA5String.getInstance(gn.getName()).getString());
if (gn.getTagNo() != expectedTag) {
return false;
}
if (gn.getTagNo() == GeneralName.dNSName) {
return expectedValue.equals(DERIA5String.getInstance(gn.getName()).getString());
}
if (gn.getTagNo() == GeneralName.iPAddress) {
return expectedValue.equals(getIP(gn.getName()).getHostAddress());
}
return false;
}
@Override
@ -296,10 +334,29 @@ public class CSRBuilderTest {
}
GeneralName gn = (GeneralName) item;
if (gn.getTagNo() != GeneralName.dNSName) {
description.appendText("is not DNS");
if (gn.getTagNo() == GeneralName.dNSName) {
description.appendText("was DNS ").appendValue(DERIA5String.getInstance(gn.getName()).getString());
} else if (gn.getTagNo() == GeneralName.iPAddress) {
description.appendText("was IP ").appendValue(getIP(gn.getName()).getHostAddress());
} else {
description.appendText("was ").appendValue(DERIA5String.getInstance(gn.getName()).getString());
description.appendText("is neither DNS nor IP, but has tag " + gn.getTagNo());
}
}
/**
* Fetches the {@link InetAddress} from the given iPAddress record.
*
* @param name
* Name to convert
* @return {@link InetAddress}
* @throws IllegalArgumentException
* if the IP address could not be read
*/
private InetAddress getIP(ASN1Encodable name) {
try {
return InetAddress.getByAddress(DEROctetString.getInstance(name).getOctets());
} catch (UnknownHostException ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@ -184,6 +184,15 @@ Order order = account.newOrder()
The example also shows how to add domain names as DNS `Identifier` objects. Adding domain names via `domain()` is just a shortcut notation for it.
The `CSRBuilder` also accepts IP addresses for generating the CSR:
```java
CSRBuilder csrb = new CSRBuilder();
csrb.addIP(InetAddress.getByName("192.168.1.2"));
csrb.sign(domainKeyPair);
byte[] csr = csrb.getEncoded();
```
## Short-Term Automatic Renewal
_acme4j_ supports the [ACME STAR](https://tools.ietf.org/html/draft-ietf-acme-star) extension for short-term automatic renewal of certificates.