mirror of https://github.com/shred/acme4j
Add support for certificate revocation
parent
6b1797c453
commit
9e93327818
|
@ -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).
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 = \
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue