Add method to download full cert and chain

acmev1
Richard Körber 2018-07-07 17:12:19 +02:00
parent ae4ef0f2ab
commit 69fbe81e2d
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
5 changed files with 107 additions and 6 deletions

View File

@ -77,7 +77,10 @@ public class Certificate extends AcmeResource {
}
/**
* Downloads the certificate. The result is cached.
* Downloads the certificate. The result is only the end-entity certificate, without
* the issuer chain.
* <p>
* The result is cached.
*
* @return {@link X509Certificate} that was downloaded
* @throws AcmeRetryAfterException
@ -85,6 +88,7 @@ public class Certificate extends AcmeResource {
* estimated date when it will be ready for download. You should wait for
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
* before trying again.
* @see #downloadFullChain()
*/
public X509Certificate download() throws AcmeException {
if (cert == null) {
@ -102,7 +106,9 @@ public class Certificate extends AcmeResource {
}
/**
* Downloads the certificate chain. The result is cached.
* Downloads the issuer chain. The result does not contain the end-entity certificate.
* <p>
* The result is cached.
*
* @return Chain of {@link X509Certificate}s
* @throws AcmeRetryAfterException
@ -144,6 +150,30 @@ public class Certificate extends AcmeResource {
return Arrays.copyOf(chain, chain.length);
}
/**
* Downloads the certificate and the corresponding issuer chain. The issued
* certificate is always on index 0, followed by the CA certificates, with the root CA
* being at the last index.
* <p>
* The result is cached.
*
* @return Chain of {@link X509Certificate}s
* @throws AcmeRetryAfterException
* the certificate is still being created, and the server returned an
* estimated date when it will be ready for download. You should wait for
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
* before trying again.
* @since 1.1
*/
public X509Certificate[] downloadFullChain() throws AcmeException {
downloadChain();
X509Certificate[] result = new X509Certificate[chain.length + 1];
result[0] = cert;
System.arraycopy(chain, 0, result, 1, chain.length);
return result;
}
/**
* Revokes this certificate.
*/

View File

@ -49,6 +49,7 @@ public class CertificateTest {
@Test
public void testDownload() throws AcmeException, IOException {
final X509Certificate originalCert = TestUtils.createCertificate();
final X509Certificate issuerCert = TestUtils.createIssuerCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
private boolean isLocationUrl;
@ -75,7 +76,11 @@ public class CertificateTest {
@Override
public X509Certificate readCertificate() {
return originalCert;
if (isLocationUrl) {
return originalCert;
} else {
return issuerCert;
}
}
@Override
@ -99,12 +104,23 @@ public class CertificateTest {
X509Certificate[] downloadedChain = cert.downloadChain();
assertThat(downloadedChain.length, is(1));
assertThat(downloadedChain[0], is(sameInstance(originalCert)));
assertThat(downloadedChain[0], is(sameInstance(issuerCert)));
// Make sure the chain array is a local copy
X509Certificate[] downloadedFullChain = cert.downloadFullChain();
assertThat(downloadedFullChain.length, is(2));
assertThat(downloadedFullChain[0], is(sameInstance(originalCert)));
assertThat(downloadedFullChain[1], is(sameInstance(issuerCert)));
// Make sure the chain arrays are a local copy
downloadedChain[0] = null;
X509Certificate[] downloadedChain2 = cert.downloadChain();
assertThat(downloadedChain2[0], is(sameInstance(originalCert)));
assertThat(downloadedChain2[0], is(sameInstance(issuerCert)));
downloadedFullChain[0] = null;
downloadedFullChain[1] = null;
X509Certificate[] downloadedFullChain2 = cert.downloadFullChain();
assertThat(downloadedFullChain2[0], is(sameInstance(originalCert)));
assertThat(downloadedFullChain2[1], is(sameInstance(issuerCert)));
provider.close();
}

View File

@ -241,6 +241,21 @@ public final class TestUtils {
}
}
/**
* Creates the issuer certificate for testing. This certificate is read from a test
* resource and is guaranteed not to change between test runs.
*
* @return {@link X509Certificate} for testing
*/
public static X509Certificate createIssuerCertificate() throws IOException {
try (InputStream cert = TestUtils.class.getResourceAsStream("/issuer.pem")) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certificateFactory.generateCertificate(cert);
} catch (CertificateException ex) {
throw new IOException(ex);
}
}
/**
* Creates a matcher that matches an array of int primitives. The array must contain
* exactly all of the given values, in any order.

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View File

@ -41,8 +41,13 @@ The `Certificate` object offers methods to download the certificate and the cert
```java
X509Certificate cert = cert.download();
X509Certificate[] chain = cert.downloadChain();
X509Certificate[] fullChain = cert.downloadFullChain();
```
* `cert` is the issued certificate.
* `chain` is the issuer chain that corresponds to `cert`.
* `fullChain` is the issued certificate (at index 0), followed by the issuer chain.
Congratulations! You have just created your first certificate via _acme4j_.
`download()` may throw an `AcmeRetryAfterException`, giving an estimated time in `getRetryAfter()` for when the certificate is ready for download. You should then wait until that moment has been reached, before trying again.
@ -64,6 +69,14 @@ try (FileWriter fw = new FileWriter("cert-chain.crt")) {
}
```
Alternatively:
```java
try (FileWriter fw = new FileWriter("cert-chain.crt")) {
CertificateUtils.writeX509CertificateChain(fw, null, fullChain);
}
```
Some older servers may need the leaf certificate and the certificate chain in different files. Use this snippet to write both files:
```java