mirror of https://github.com/shred/acme4j
Support draft-ietf-acme-star-04
parent
df0af217b3
commit
62d2e9c1c0
|
@ -113,6 +113,15 @@ public class Metadata {
|
|||
return meta.get("star-max-renewal").map(Value::asDuration).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the CA also allows to fetch STAR certificates via GET request.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
public boolean isStarCertificateGetAllowed() {
|
||||
return meta.get("star-allow-certificate-get").map(Value::asBoolean).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON representation of the metadata. This is useful for reading
|
||||
* proprietary metadata properties.
|
||||
|
|
|
@ -132,6 +132,20 @@ public class Order extends AcmeJsonResource {
|
|||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the STAR extension's {@link Certificate} if it is available. {@code null}
|
||||
* otherwise.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
@CheckForNull
|
||||
public Certificate getStarCertificate() {
|
||||
return getJSON().get("star-certificate")
|
||||
.map(Value::asURL)
|
||||
.map(getLogin()::bindCertificate)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the order, by providing a CSR.
|
||||
* <p>
|
||||
|
@ -210,6 +224,19 @@ public class Order extends AcmeJsonResource {
|
|||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if STAR certificates from this order can also be fetched via
|
||||
* GET requests.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
public boolean isRecurrentGetEnabled() {
|
||||
return getJSON().get("recurrent-certificate-get")
|
||||
.optional()
|
||||
.map(Value::asBoolean)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a recurrent order.
|
||||
*
|
||||
|
|
|
@ -50,6 +50,7 @@ public class OrderBuilder {
|
|||
private Instant recurrentStart;
|
||||
private Instant recurrentEnd;
|
||||
private Duration recurrentValidity;
|
||||
private boolean recurrentGet;
|
||||
|
||||
/**
|
||||
* Create a new {@link OrderBuilder}.
|
||||
|
@ -228,6 +229,26 @@ public class OrderBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces that the client wishes to fetch the recurring certificate via GET
|
||||
* request. If not used, the STAR certificate can only be fetched via POST-as-GET
|
||||
* request. {@link Metadata#isStarCertificateGetAllowed()} must return {@code true} in
|
||||
* order for this option to work.
|
||||
* <p>
|
||||
* This option is only needed if you plan to fetch the STAR certificate via other
|
||||
* means than by using acme4j.
|
||||
* <p>
|
||||
* Implies {@link #recurrent()}.
|
||||
*
|
||||
* @return itself
|
||||
* @since 2.6
|
||||
*/
|
||||
public OrderBuilder recurrentEnableGet() {
|
||||
recurrent();
|
||||
this.recurrentGet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a new order to the server, and returns an {@link Order} object.
|
||||
*
|
||||
|
@ -267,6 +288,9 @@ public class OrderBuilder {
|
|||
if (recurrentValidity != null) {
|
||||
claims.put("recurrent-certificate-validity", recurrentValidity);
|
||||
}
|
||||
if (recurrentGet) {
|
||||
claims.put("recurrent-certificate-get", recurrentGet);
|
||||
}
|
||||
}
|
||||
|
||||
conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, login);
|
||||
|
|
|
@ -148,6 +148,7 @@ public class OrderBuilderTest {
|
|||
.recurrentStart(recurrentStart)
|
||||
.recurrentEnd(recurrentEnd)
|
||||
.recurrentCertificateValidity(validity)
|
||||
.recurrentEnableGet()
|
||||
.create();
|
||||
|
||||
assertThat(order.getIdentifiers(), containsInAnyOrder(Identifier.dns("example.org")));
|
||||
|
@ -157,6 +158,7 @@ public class OrderBuilderTest {
|
|||
assertThat(order.getRecurrentStart(), is(recurrentStart));
|
||||
assertThat(order.getRecurrentEnd(), is(recurrentEnd));
|
||||
assertThat(order.getRecurrentCertificateValidity(), is(validity));
|
||||
assertThat(order.isRecurrentGetEnabled(), is(true));
|
||||
assertThat(order.getLocation(), is(locationUrl));
|
||||
|
||||
provider.close();
|
||||
|
|
|
@ -84,6 +84,7 @@ public class OrderTest {
|
|||
assertThat(order.getRecurrentStart(), is(nullValue()));
|
||||
assertThat(order.getRecurrentEnd(), is(nullValue()));
|
||||
assertThat(order.getRecurrentCertificateValidity(), is(nullValue()));
|
||||
assertThat(order.isRecurrentGetEnabled(), is(false));
|
||||
|
||||
assertThat(order.getError(), is(notNullValue()));
|
||||
assertThat(order.getError().getType(), is(URI.create("urn:ietf:params:acme:error:connection")));
|
||||
|
@ -195,6 +196,7 @@ public class OrderTest {
|
|||
assertThat(order.getNotBefore(), is(parseTimestamp("2016-01-01T00:00:00Z")));
|
||||
assertThat(order.getNotAfter(), is(parseTimestamp("2016-01-08T00:00:00Z")));
|
||||
assertThat(order.getCertificate().getLocation(), is(url("https://example.com/acme/cert/1234")));
|
||||
assertThat(order.getStarCertificate(), is(nullValue()));
|
||||
assertThat(order.getFinalizeLocation(), is(finalizeUrl));
|
||||
|
||||
List<Authorization> auths = order.getAuthorizations();
|
||||
|
@ -243,6 +245,46 @@ public class OrderTest {
|
|||
assertThat(order.getRecurrentCertificateValidity(), is(Duration.ofHours(168)));
|
||||
assertThat(order.getNotBefore(), is(nullValue()));
|
||||
assertThat(order.getNotAfter(), is(nullValue()));
|
||||
assertThat(order.isRecurrentGetEnabled(), is(true));
|
||||
|
||||
provider.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that recurrent order is properly finalized.
|
||||
*/
|
||||
@Test
|
||||
public void testRecurrentFinalize() throws Exception {
|
||||
TestableConnectionProvider provider = new TestableConnectionProvider() {
|
||||
@Override
|
||||
public int sendSignedPostAsGetRequest(URL url, Login login) {
|
||||
assertThat(url, is(locationUrl));
|
||||
return HttpURLConnection.HTTP_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSON readJsonResponse() {
|
||||
return getJSON("finalizeRecurrentResponse");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRetryAfter(String message) {
|
||||
assertThat(message, not(nullValue()));
|
||||
}
|
||||
};
|
||||
|
||||
Login login = provider.createLogin();
|
||||
Order order = login.bindOrder(locationUrl);
|
||||
|
||||
assertThat(order.getCertificate(), is(nullValue()));
|
||||
assertThat(order.getStarCertificate().getLocation(), is(url("https://example.com/acme/cert/1234")));
|
||||
assertThat(order.isRecurrent(), is(true));
|
||||
assertThat(order.getRecurrentStart(), is(parseTimestamp("2018-01-01T00:00:00Z")));
|
||||
assertThat(order.getRecurrentEnd(), is(parseTimestamp("2019-01-01T00:00:00Z")));
|
||||
assertThat(order.getRecurrentCertificateValidity(), is(Duration.ofHours(168)));
|
||||
assertThat(order.getNotBefore(), is(nullValue()));
|
||||
assertThat(order.getNotAfter(), is(nullValue()));
|
||||
assertThat(order.isRecurrentGetEnabled(), is(true));
|
||||
|
||||
provider.close();
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ public class SessionTest {
|
|||
assertThat(meta.isStarEnabled(), is(false));
|
||||
assertThat(meta.getStarMaxRenewal(), is(nullValue()));
|
||||
assertThat(meta.getStarMinCertValidity(), is(nullValue()));
|
||||
assertThat(meta.isStarCertificateGetAllowed(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,6 +216,7 @@ public class SessionTest {
|
|||
assertThat(meta.isStarEnabled(), is(true));
|
||||
assertThat(meta.getStarMaxRenewal(), is(Duration.ofDays(365)));
|
||||
assertThat(meta.getStarMinCertValidity(), is(Duration.ofHours(24)));
|
||||
assertThat(meta.isStarCertificateGetAllowed(), is(true));
|
||||
assertThat(meta.isExternalAccountRequired(), is(true));
|
||||
assertThat(meta.getJSON(), is(notNullValue()));
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"star-enabled": true,
|
||||
"star-min-cert-validity": 86400,
|
||||
"star-max-renewal": 31536000,
|
||||
"star-allow-certificate-get": true,
|
||||
"xTestString": "foobar",
|
||||
"xTestUri": "https://www.example.org",
|
||||
"xTestArray": [
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"status": "valid",
|
||||
"expires": "2015-03-01T14:09:00Z",
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "example.com"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "www.example.com"
|
||||
}
|
||||
],
|
||||
"recurrent": true,
|
||||
"recurrent-start-date": "2018-01-01T00:00:00Z",
|
||||
"recurrent-end-date": "2019-01-01T00:00:00Z",
|
||||
"recurrent-certificate-validity": 604800,
|
||||
"recurrent-certificate-get": true,
|
||||
"authorizations": [
|
||||
"https://example.com/acme/authz/1234",
|
||||
"https://example.com/acme/authz/2345"
|
||||
],
|
||||
"finalize": "https://example.com/acme/acct/1/order/1/finalize",
|
||||
"star-certificate": "https://example.com/acme/cert/1234"
|
||||
}
|
|
@ -8,5 +8,6 @@
|
|||
"recurrent": true,
|
||||
"recurrent-start-date": "2018-01-01T00:00:00Z",
|
||||
"recurrent-end-date": "2019-01-01T00:00:00Z",
|
||||
"recurrent-certificate-validity": 604800
|
||||
"recurrent-certificate-validity": 604800,
|
||||
"recurrent-certificate-get": true
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"recurrent-start-date": "2018-01-01T00:00:00Z",
|
||||
"recurrent-end-date": "2019-01-01T00:00:00Z",
|
||||
"recurrent-certificate-validity": 604800,
|
||||
"recurrent-certificate-get": true,
|
||||
"authorizations": [
|
||||
"https://example.com/acme/authz/1234",
|
||||
"https://example.com/acme/authz/2345"
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
"recurrent": true,
|
||||
"recurrent-start-date": "2016-01-01T00:00:00Z",
|
||||
"recurrent-end-date": "2017-01-01T00:00:00Z",
|
||||
"recurrent-certificate-validity": 604800
|
||||
"recurrent-certificate-validity": 604800,
|
||||
"recurrent-certificate-get": true
|
||||
}
|
||||
|
|
|
@ -218,7 +218,29 @@ You can use `recurrentStart()`, `recurrentEnd()` and `recurrentCertificateValidi
|
|||
|
||||
The `Metadata` object also holds the accepted renewal limits (see `Metadata.getStarMinCertValidity()` and `Metadata.getStarMaxRenewal()`).
|
||||
|
||||
After the validation process is completed and the order is finalized, the STAR certificate is available via `Order.getStarCertificate()` (_not_ `Order.getCertificate()`)!
|
||||
|
||||
Use `Certificate.getLocation()` to retrieve the URL of your certificate. It is renewed automatically, so you will always be able to download the latest issue of the certificate from this URL.
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
STAR based certificates cannot be revoked. However, as it is the nature of these certs to be short-lived, this does not pose an actual security issue.
|
||||
</div>
|
||||
|
||||
To download the latest certificate issue, you can bind the certificate URL to your `Login` and then use the `Certificate` object.
|
||||
|
||||
```java
|
||||
URL certificateUrl = ... // URL of the certificate
|
||||
|
||||
Certificate cert = login.bindCertificate(certificateUrl);
|
||||
X509Certificate latestCertificate = cert.getCertificate();
|
||||
|
||||
```
|
||||
|
||||
If supported by the CA, it is possible to negotiate that the certificate can also be downloaded via `GET` request. First use `Metadata.isStarCertificateGetAllowed()` to check if this option is supported by the CA. If it is, add `recurrentEnableGet()` to the order parameters to enable it. After the order was finalized, you can use any HTTP client to download the latest certificate from the certificate URL by a `GET` request.
|
||||
|
||||
Use `Order.cancelRecurrent()` to terminate automatical certificate renewals.
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
|
||||
The _ACME STAR_ support is experimental. There is currently no known ACME server implementing this extension.
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue