Add support for certificate revocation

pull/17/merge
Richard Körber 2015-12-20 13:07:28 +01:00
parent 6b1797c453
commit 9e93327818
9 changed files with 95 additions and 4 deletions

View File

@ -36,7 +36,7 @@ See the [online documentation](http://www.shredzone.org/maven/acme4j/) for how t
The following features are planned to be completed for the first beta release, but are still missing:
* Support of account recovery and certificate revocation.
* Support of account recovery.
* `proofOfPossession-01` and `tls-sni-01` challenge support.
* Better error handling.
* Some hardening (like plausibility checks).

View File

@ -109,4 +109,14 @@ public interface AcmeClient {
*/
X509Certificate downloadCertificate(URI certUri) throws AcmeException;
/**
* Revokes a certificate.
*
* @param account
* {@link Account} to be used for conversation
* @param certificate
* Certificate to revoke
*/
void revokeCertificate(Account account, X509Certificate certificate) throws AcmeException;
}

View File

@ -20,7 +20,10 @@ package org.shredzone.acme4j.connector;
*/
public enum Resource {
NEW_REG("new-reg"), NEW_AUTHZ("new-authz"), NEW_CERT("new-cert");
NEW_REG("new-reg"),
NEW_AUTHZ("new-authz"),
NEW_CERT("new-cert"),
REVOKE_CERT("revoke-cert");
/**
* Parses the string and returns a matching {@link Resource} instance.

View File

@ -15,6 +15,7 @@ package org.shredzone.acme4j.impl;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
@ -280,4 +281,26 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
}
@Override
public void revokeCertificate(Account account, X509Certificate certificate) throws AcmeException {
LOG.debug("revokeCertificate");
URI resUri = resourceUri(Resource.REVOKE_CERT);
if (resUri == null) {
throw new AcmeException("CA does not support certificate revocation");
}
try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource(Resource.REVOKE_CERT);
claims.putBase64("certificate", certificate.getEncoded());
int rc = conn.sendSignedRequest(resUri, claims, session, account);
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}
} catch (CertificateEncodingException ex) {
throw new IllegalArgumentException("Invalid certificate", ex);
}
}
}

View File

@ -33,9 +33,10 @@ public class ResourceTest {
assertThat(Resource.NEW_AUTHZ.path(), is("new-authz"));
assertThat(Resource.NEW_CERT.path(), is("new-cert"));
assertThat(Resource.NEW_REG.path(), is("new-reg"));
assertThat(Resource.REVOKE_CERT.path(), is("revoke-cert"));
// fails if there are untested future Resource values
assertThat(Resource.values().length, is(3));
assertThat(Resource.values().length, is(4));
}
/**

View File

@ -336,6 +336,30 @@ public class AbstractAcmeClientTest {
assertThat(downloadedCert, is(sameInstance(originalCert)));
}
/**
* Test that a certificate can be revoked.
*/
@Test
public void testRevokeCertificate() throws AcmeException, IOException {
final X509Certificate cert = TestUtils.createCertificate();
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
return HttpURLConnection.HTTP_OK;
}
};
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestResource(Resource.REVOKE_CERT, resourceUri);
client.revokeCertificate(testAccount, cert);
}
/**
* Extends the {@link AbstractAcmeClient} to be tested, and implements the abstract
* methods with a simple implementation specially made for testing purposes.

View File

@ -89,6 +89,12 @@ requestCertificateRequest = \
"resource":"new-cert"\
}
revokeCertificateRequest = \
{\
"certificate": "MIIDVzCCAj-gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0g3w4C8xbj_5lzJiDxk0HkEJeZeyruq-0AzOPMigJZ7zxZtX_KUxOIHrQ4qjcFhl0DmQImoM0wESU-kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD-9F9mVIP6liC_6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3-NTxjuHON1rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S-Im9by7751UeW5a7DznmuMEM-WY640ffJDQ4-I64H403uAgvvSu-BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqFdGosAfm1u-_MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA_HuhW1b_CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA_HuhW1b_CHjzEvj34swDAYDVR0TBAUwAwEB_zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAkqXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr-lVYTwFXStrLih1Wh3tWvksMxnvocgd7l6USRb5_AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVIGUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl2Ci7nLdE-3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd_l3aluSR-f_VZlk8KGQ4As4uTQi89j-J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A",\
"resource":"revoke-cert"\
}
dnsChallenge = \

View File

@ -188,6 +188,9 @@ public class ClientTest {
try (FileWriter fw = new FileWriter(DOMAIN_CERT_FILE)) {
CertificateUtils.writeX509Certificate(cert, fw);
}
// Revoke the certificate (uncomment if needed...)
// client.revokeCertificate(account, cert);
}
public static void main(String... args) {

View File

@ -1,7 +1,9 @@
# Request a Certificate
# Certificates
Once you completed all the previous steps, it's time to request the signed certificate.
## Request a Certificate
To do so, prepare a PKCS#10 CSR file. A single domain may be set as _Common Name_. Multiple domains must be provided as _Subject Alternative Name_. Other properties (_Organization_, _Organization Unit_ etc.) depend on the CA. Some may require these properties to be set, while others may ignore them when generating the certificate.
CSR files can be generated with command line tools like `openssl`. Unfortunately the standard Java does not offer classes for that, so you'd have to resort to [Bouncy Castle](http://www.bouncycastle.org/java.html) if you want to create a CSR programmatically. In the `acme4j-utils` module, there is also a [`CSRBuilder`](../apidocs/org/shredzone/acme4j/util/CSRBuilder.html) for your convenience:
@ -36,3 +38,22 @@ Congratulations! You have just created your first certificate via _acme4j_.
## Renewal
Renewing your certificate depends on the CA. Some may require you to go through the authorization process again, while others may just provide an updated certificate for download at the `certUri` above.
## Revocation
To revoke a certificate, just pass the it to the respective method:
```java
X509Certificate cert = ... // certificate to be revoked
client.revokeCertificate(account, cert);
```
As an exception, ACME servers also accept the domain's key pair for revoking a certificate. _acme4j_ does not directly support this way of revocation. However, you can do so with this tiny hack:
```java
KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption
X509Certificate cert = ... // certificate to be revoked
client.revokeCertificate(new Account(domainKeyPair), cert);
```
If you have the choice, you should always prefer to use your account key.