From 42e94125d81897f16cea9bedfec432476fb7eba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Thu, 7 Jul 2016 00:12:06 +0200 Subject: [PATCH] Set a RevocationReason on certificate revocation --- .../org/shredzone/acme4j/Certificate.java | 15 +++++ .../shredzone/acme4j/RevocationReason.java | 65 +++++++++++++++++++ .../org/shredzone/acme4j/CertificateTest.java | 25 +++++++ .../src/test/resources/json.properties | 7 ++ src/site/markdown/usage/certificate.md | 6 ++ 5 files changed, 118 insertions(+) create mode 100644 acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java index ba7ea9b2..aa30be22 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java @@ -148,6 +148,18 @@ public class Certificate extends AcmeResource { * Revokes this certificate. */ public void revoke() throws AcmeException { + revoke(null); + } + + /** + * Revokes this certificate. + * + * @param reason + * {@link RevocationReason} stating the reason of the revocation that is + * used when generating OCSP responses and CRLs. {@code null} to give no + * reason. + */ + public void revoke(RevocationReason reason) throws AcmeException { LOG.debug("revoke"); URI resUri = getSession().resourceUri(Resource.REVOKE_CERT); if (resUri == null) { @@ -162,6 +174,9 @@ public class Certificate extends AcmeResource { ClaimBuilder claims = new ClaimBuilder(); claims.putResource(Resource.REVOKE_CERT); claims.putBase64("certificate", cert.getEncoded()); + if (reason != null) { + claims.put("reason", reason.getReasonCode()); + } int rc = conn.sendSignedRequest(resUri, claims, getSession()); if (rc != HttpURLConnection.HTTP_OK) { diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java b/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java new file mode 100644 index 00000000..ff48f2e5 --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java @@ -0,0 +1,65 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2016 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; + +/** + * Enumeration of revocation reasons. + * + * @see RFC 5280 Section + * 5.3.1 + * @author Richard "Shred" Körber + */ +public enum RevocationReason { + + UNSPECIFIED(0), + KEY_COMPROMISE(1), + CA_COMPROMISE(2), + AFFILIATION_CHANGED(3), + SUPERSEDED(4), + CESSATION_OF_OPERATION(5), + CERTIFICATE_HOLD(6), + REMOVE_FROM_CRL(8), + PRIVILEGE_WITHDRAWN(9), + AA_COMPROMISE(10); + + /** + * Returns the {@link RevocationReason} that matches the reason code. + * + * @param reasonCode + * Reason code as defined in RFC 5280 + * @return Matching {@link RevocationReason}, or {@code null} if not known + */ + public static RevocationReason code(int reasonCode) { + for (RevocationReason rr : values()) { + if (rr.reasonCode == reasonCode) { + return rr; + } + } + return null; + } + + private final int reasonCode; + + private RevocationReason(int reasonCode) { + this.reasonCode = reasonCode; + } + + /** + * Returns the reason code as defined in RFC 5280. + */ + public int getReasonCode() { + return reasonCode; + } + +} diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java index a5832c05..182a4e5c 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java @@ -109,4 +109,29 @@ public class CertificateTest { provider.close(); } + /** + * Test that a certificate can be revoked with reason. + */ + @Test + public void testRevokeCertificateWithReason() throws AcmeException, IOException { + final X509Certificate originalCert = TestUtils.createCertificate(); + + TestableConnectionProvider provider = new TestableConnectionProvider() { + @Override + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) { + assertThat(uri, is(resourceUri)); + assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateWithReasonRequest"))); + assertThat(session, is(notNullValue())); + return HttpURLConnection.HTTP_OK; + } + }; + + provider.putTestResource(Resource.REVOKE_CERT, resourceUri); + + Certificate cert = new Certificate(provider.createSession(), locationUri, null, originalCert); + cert.revoke(RevocationReason.KEY_COMPROMISE); + + provider.close(); + } + } diff --git a/acme4j-client/src/test/resources/json.properties b/acme4j-client/src/test/resources/json.properties index c5b160dd..a2d4311d 100644 --- a/acme4j-client/src/test/resources/json.properties +++ b/acme4j-client/src/test/resources/json.properties @@ -138,6 +138,13 @@ revokeCertificateRequest = \ "resource":"revoke-cert"\ } +revokeCertificateWithReasonRequest = \ + {\ + "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",\ + "reason": 1\ + } + authorizationChallenges = \ {\ "challenges": [\ diff --git a/src/site/markdown/usage/certificate.md b/src/site/markdown/usage/certificate.md index 979af2f0..c6e36a17 100644 --- a/src/site/markdown/usage/certificate.md +++ b/src/site/markdown/usage/certificate.md @@ -102,3 +102,9 @@ To revoke a certificate, just invoke the respective method: ```java cert.revoke(); ``` + +Optionally, you can provide a revocation reason that the ACME server may use when generating OCSP responses and CRLs. + +```java +cert.revoke(RevocationReason.KEY_COMPROMISE); +```