Add unit tests for utils module

pull/17/merge
Richard Körber 2015-12-10 23:44:44 +01:00
parent 7e07a0e2e4
commit 1d56065495
5 changed files with 452 additions and 0 deletions

View File

@ -0,0 +1,262 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.
*/
package org.shredzone.acme4j.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyPair;
import java.util.Arrays;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.openssl.PEMException;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.BeforeClass;
import org.junit.Test;
import com.jcabi.matchers.RegexMatchers;
/**
* Unit tests for {@link CSRBuilder}.
*
* @author Richard "Shred" Körber
*/
public class CSRBuilderTest {
private static KeyPair testKey;
@BeforeClass
public static void setup() {
testKey = KeyPairUtils.createKeyPair(512);
}
/**
* Test if the generated CSR is plausible.
*/
@Test
public void testGenerate() throws IOException {
CSRBuilder builder = new CSRBuilder();
builder.addDomain("abc.de");
builder.addDomain("fg.hi");
builder.addDomains("jklm.no", "pqr.st");
builder.addDomains(Arrays.asList("uv.wx", "y.z"));
builder.setCountry("XX");
builder.setLocality("Testville");
builder.setOrganization("Testing Co");
builder.setOrganizationalUnit("Testunit");
builder.setState("ABC");
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"));
builder.sign(testKey);
PKCS10CertificationRequest csr = builder.getCSR();
assertThat(csr, is(notNullValue()));
assertThat(csr.getEncoded(), is(equalTo(builder.getEncoded())));
csrTest(csr);
writerTest(builder);
}
/**
* Checks if the CSR contains the right parameters.
* <p>
* This is not supposed to be a Bouncy Castle test. If the
* {@link PKCS10CertificationRequest} contains the right parameters, we assume that
* Bouncy Castle encodes it properly.
*/
@SuppressWarnings("unchecked")
private void csrTest(PKCS10CertificationRequest csr) {
X500Name name = csr.getSubject();
assertThat(name.getRDNs(BCStyle.CN), arrayContaining(new RDNMatcher("abc.de")));
assertThat(name.getRDNs(BCStyle.C), arrayContaining(new RDNMatcher("XX")));
assertThat(name.getRDNs(BCStyle.L), arrayContaining(new RDNMatcher("Testville")));
assertThat(name.getRDNs(BCStyle.O), arrayContaining(new RDNMatcher("Testing Co")));
assertThat(name.getRDNs(BCStyle.OU), arrayContaining(new RDNMatcher("Testunit")));
assertThat(name.getRDNs(BCStyle.ST), arrayContaining(new RDNMatcher("ABC")));
Attribute[] attr = csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
assertThat(attr.length, is(1));
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")));
}
/**
* Checks if the {@link CSRBuilder#write(java.io.Writer)} method generates a correct
* CSR PEM file.
*/
private void writerTest(CSRBuilder builder) throws IOException, PEMException {
// Write CSR to PEM
String pem;
try (StringWriter out = new StringWriter()) {
builder.write(out);
pem = out.toString();
}
// Make sure PEM file is properly formatted
assertThat(pem, RegexMatchers.matchesPattern(
"-----BEGIN CERTIFICATE REQUEST-----[\\r\\n]+"
+ "([a-zA-Z0-9/+=]+[\\r\\n]+)+"
+ "-----END CERTIFICATE REQUEST-----[\\r\\n]*"));
// Read CSR from PEM
PKCS10CertificationRequest readCsr;
try (PEMParser parser = new PEMParser(new StringReader(pem))) {
readCsr = (PKCS10CertificationRequest) parser.readObject();
}
// Verify that both keypairs are the same
assertThat(builder.getCSR(), not(sameInstance(readCsr)));
assertThat(builder.getEncoded(), is(equalTo(readCsr.getEncoded())));
}
/**
* Make sure an exception is thrown when no domain is set.
*/
@Test(expected = IllegalStateException.class)
public void testNoDomain() throws IOException {
CSRBuilder builder = new CSRBuilder();
builder.sign(testKey);
}
/**
* Make sure all getters will fail if the CSR is not signed.
*/
@Test
public void testNoSign() throws IOException {
CSRBuilder builder = new CSRBuilder();
try {
builder.getCSR();
fail("getCSR(): expected exception was not thrown");
} catch (IllegalStateException ex) {
// expected
}
try {
builder.getEncoded();
fail("getEncoded(): expected exception was not thrown");
} catch (IllegalStateException ex) {
// expected
}
try (StringWriter w = new StringWriter()) {
builder.write(w);
fail("write(): expected exception was not thrown");
} catch (IllegalStateException ex) {
// expected
}
}
/**
* Matches {@link RDN} values.
*/
private static class RDNMatcher extends BaseMatcher<RDN> {
private final String expectedValue;
public RDNMatcher(String expectedValue) {
this.expectedValue = expectedValue;
}
@Override
public boolean matches(Object item) {
if (!(item instanceof RDN)) {
return false;
}
return expectedValue.equals(((RDN) item).getFirst().getValue().toString());
}
@Override
public void describeTo(Description description) {
description.appendValue(expectedValue);
}
@Override
public void describeMismatch(Object item, Description description) {
if (!(item instanceof RDN)) {
description.appendText("is a ").appendValue(item.getClass());
} else {
description.appendText("was ").appendValue(((RDN) item).getFirst().getValue());
}
}
}
/**
* Matches {@link GeneralName} DNS tagged values.
*/
private static class GeneralNameMatcher extends BaseMatcher<GeneralName> {
private final String expectedValue;
public GeneralNameMatcher(String expectedValue) {
this.expectedValue = expectedValue;
}
@Override
public boolean matches(Object item) {
if (!(item instanceof GeneralName)) {
return false;
}
GeneralName gn = (GeneralName) item;
return gn.getTagNo() == GeneralName.dNSName
&& expectedValue.equals(DERIA5String.getInstance(gn.getName()).getString());
}
@Override
public void describeTo(Description description) {
description.appendValue(expectedValue);
}
@Override
public void describeMismatch(Object item, Description description) {
if (!(item instanceof GeneralName)) {
description.appendText("is a ").appendValue(item.getClass());
return;
}
GeneralName gn = (GeneralName) item;
if (gn.getTagNo() != GeneralName.dNSName) {
description.appendText("is not DNS");
} else {
description.appendText("was ").appendValue(DERIA5String.getInstance(gn.getName()).getString());
}
}
}
}

View File

@ -0,0 +1,82 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.
*/
package org.shredzone.acme4j.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.junit.Before;
import org.junit.Test;
import com.jcabi.matchers.RegexMatchers;
/**
* Unit tests for {@link CertificateUtils}.
*
* @author Richard "Shred" Körber
*/
public class CertificateUtilsTest {
private CertificateFactory certificateFactory;
@Before
public void setup() throws CertificateException {
certificateFactory = CertificateFactory.getInstance("X.509");
}
/**
* Test if {@link CertificateUtilsTest#writeX509CertificateTest()} writes a
* proper X.509 certificate.
*/
@Test
public void testWriteX509Certificate() throws IOException, CertificateException {
// Read a demonstration certificate
X509Certificate original;
try (InputStream cert = getClass().getResourceAsStream("/cert.pem")) {
original = (X509Certificate) certificateFactory.generateCertificate(cert);
}
assertThat(original, is(notNullValue()));
// Write to StringWriter
String pem;
try (StringWriter out = new StringWriter()) {
CertificateUtils.writeX509Certificate(original, out);
pem = out.toString();
}
// Make sure it is a good PEM file
assertThat(pem, RegexMatchers.matchesPattern(
"-----BEGIN CERTIFICATE-----[\\r\\n]+"
+ "([a-zA-Z0-9/+=]+[\\r\\n]+)+"
+ "-----END CERTIFICATE-----[\\r\\n]*"));
// Read it back in
X509Certificate written;
try (InputStream cert = new ByteArrayInputStream(pem.getBytes("utf-8"))) {
written = (X509Certificate) certificateFactory.generateCertificate(cert);
}
// Verify that both certificates are the same
assertThat(original.getEncoded(), is(equalTo(written.getEncoded())));
}
}

View File

@ -0,0 +1,82 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.
*/
package org.shredzone.acme4j.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import org.junit.Test;
import com.jcabi.matchers.RegexMatchers;
/**
* Unit tests for {@link KeyPairUtils}.
*
* @author Richard "Shred" Körber
*/
public class KeyPairUtilsTest {
private static final int KEY_SIZE = 2048;
/**
* Test that RSA keypairs of the correct size are generated.
*/
@Test
public void testCreateKeyPair() {
KeyPair pair = KeyPairUtils.createKeyPair(KEY_SIZE);
assertThat(pair, is(notNullValue()));
assertThat(pair.getPublic(), is(instanceOf(RSAPublicKey.class)));
RSAPublicKey pub = (RSAPublicKey) pair.getPublic();
assertThat(pub.getModulus().bitLength(), is(KEY_SIZE));
}
/**
* Test that reading and writing keypairs work correctly.
*/
@Test
public void testWriteAndRead() throws IOException {
// Generate a test keypair
KeyPair pair = KeyPairUtils.createKeyPair(KEY_SIZE);
// Write keypair to PEM
String pem;
try (StringWriter out = new StringWriter()) {
KeyPairUtils.writeKeyPair(pair, out);
pem = out.toString();
}
// Make sure PEM file is properly formatted
assertThat(pem, RegexMatchers.matchesPattern(
"-----BEGIN RSA PRIVATE KEY-----[\\r\\n]+"
+ "([a-zA-Z0-9/+=]+[\\r\\n]+)+"
+ "-----END RSA PRIVATE KEY-----[\\r\\n]*"));
// Read keypair from PEM
KeyPair readPair;
try (StringReader in = new StringReader(pem)) {
readPair = KeyPairUtils.readKeyPair(in);
}
// Verify that both keypairs are the same
assertThat(pair, not(sameInstance(readPair)));
assertThat(pair.getPrivate().getEncoded(), is(equalTo(readPair.getPrivate().getEncoded())));
}
}

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
r0g3w4C8xbj/5lzJiDxk0HkEJeZeyruq+0AzOPMigJZ7zxZtX/KUxOIHrQ4qjcFh
l0DmQImoM0wESU+kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD+9F9m
VIP6liC/6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3+NTxjuHON1
rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S+Im9by7751UeW5a7Dznm
uMEM+WY640ffJDQ4+I64H403uAgvvSu+BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqF
dGosAfm1u+/MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA/HuhW1b
/CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA/HuhW1b/CHjzEvj34swDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAk
qXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr+lVYTwFXStrLih1Wh3tWvk
sMxnvocgd7l6USRb5/AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVI
GUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl
2Ci7nLdE+3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd/l3aluSR+f/VZl
k8KGQ4As4uTQi89j+J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A==
-----END CERTIFICATE-----

View File

@ -158,6 +158,12 @@
<version>[1.3,)</version> <version>[1.3,)</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-matchers</artifactId>
<version>[1.3,)</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>