001/* 002 * acme4j - Java ACME client 003 * 004 * Copyright (C) 2017 Richard "Shred" Körber 005 * http://acme4j.shredzone.org 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 013 */ 014package org.shredzone.acme4j; 015 016import static java.util.stream.Collectors.toUnmodifiableList; 017 018import java.io.IOException; 019import java.net.URL; 020import java.security.KeyPair; 021import java.time.Duration; 022import java.time.Instant; 023import java.util.List; 024import java.util.Optional; 025import java.util.function.Consumer; 026 027import edu.umd.cs.findbugs.annotations.Nullable; 028import org.bouncycastle.pkcs.PKCS10CertificationRequest; 029import org.shredzone.acme4j.exception.AcmeException; 030import org.shredzone.acme4j.exception.AcmeNotSupportedException; 031import org.shredzone.acme4j.toolbox.JSON; 032import org.shredzone.acme4j.toolbox.JSON.Value; 033import org.shredzone.acme4j.toolbox.JSONBuilder; 034import org.shredzone.acme4j.util.CSRBuilder; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A representation of a certificate order at the CA. 040 */ 041public class Order extends AcmeJsonResource { 042 private static final long serialVersionUID = 5435808648658292177L; 043 private static final Logger LOG = LoggerFactory.getLogger(Order.class); 044 045 private transient @Nullable Certificate certificate = null; 046 private transient @Nullable Certificate autoRenewalCertificate = null; 047 private transient @Nullable List<Authorization> authorizations = null; 048 049 protected Order(Login login, URL location) { 050 super(login, location); 051 } 052 053 /** 054 * Returns the current status of the order. 055 * <p> 056 * Possible values are: {@link Status#PENDING}, {@link Status#READY}, 057 * {@link Status#PROCESSING}, {@link Status#VALID}, {@link Status#INVALID}. 058 * If the server supports STAR, another possible value is {@link Status#CANCELED}. 059 */ 060 public Status getStatus() { 061 return getJSON().get("status").asStatus(); 062 } 063 064 /** 065 * Returns a {@link Problem} document with the reason if the order has failed. 066 */ 067 public Optional<Problem> getError() { 068 return getJSON().get("error").map(v -> v.asProblem(getLocation())); 069 } 070 071 /** 072 * Gets the expiry date of the authorization, if set by the server. 073 */ 074 public Optional<Instant> getExpires() { 075 return getJSON().get("expires").map(Value::asInstant); 076 } 077 078 /** 079 * Gets a list of {@link Identifier} that are connected to this order. 080 * 081 * @since 2.3 082 */ 083 public List<Identifier> getIdentifiers() { 084 return getJSON().get("identifiers") 085 .asArray() 086 .stream() 087 .map(Value::asIdentifier) 088 .collect(toUnmodifiableList()); 089 } 090 091 /** 092 * Gets the "not before" date that was used for the order. 093 */ 094 public Optional<Instant> getNotBefore() { 095 return getJSON().get("notBefore").map(Value::asInstant); 096 } 097 098 /** 099 * Gets the "not after" date that was used for the order. 100 */ 101 public Optional<Instant> getNotAfter() { 102 return getJSON().get("notAfter").map(Value::asInstant); 103 } 104 105 /** 106 * Gets the {@link Authorization} that are required to fulfil this order, in no 107 * specific order. 108 */ 109 public List<Authorization> getAuthorizations() { 110 if (authorizations == null) { 111 var login = getLogin(); 112 authorizations = getJSON().get("authorizations") 113 .asArray() 114 .stream() 115 .map(Value::asURL) 116 .map(login::bindAuthorization) 117 .collect(toUnmodifiableList()); 118 } 119 return authorizations; 120 } 121 122 /** 123 * Gets the location {@link URL} of where to send the finalization call to. 124 * <p> 125 * For internal purposes. Use {@link #execute(byte[])} to finalize an order. 126 */ 127 public URL getFinalizeLocation() { 128 return getJSON().get("finalize").asURL(); 129 } 130 131 /** 132 * Gets the {@link Certificate}. 133 * 134 * @throws IllegalStateException 135 * if the order is not ready yet. You must finalize the order first, and wait 136 * for the status to become {@link Status#VALID}. 137 */ 138 public Certificate getCertificate() { 139 if (certificate == null) { 140 certificate = getJSON().get("certificate") 141 .map(Value::asURL) 142 .map(getLogin()::bindCertificate) 143 .orElseThrow(() -> new IllegalStateException("Order is not completed")); 144 } 145 return certificate; 146 } 147 148 /** 149 * Gets the STAR extension's {@link Certificate} if it is available. 150 * 151 * @since 2.6 152 * @throws IllegalStateException 153 * if the order is not ready yet. You must finalize the order first, and wait 154 * for the status to become {@link Status#VALID}. It is also thrown if the 155 * order has been {@link Status#CANCELED}. 156 */ 157 public Certificate getAutoRenewalCertificate() { 158 if (autoRenewalCertificate == null) { 159 autoRenewalCertificate = getJSON().get("star-certificate") 160 .optional() 161 .map(Value::asURL) 162 .map(getLogin()::bindCertificate) 163 .orElseThrow(() -> new IllegalStateException("Order is in an invalid state")); 164 } 165 return autoRenewalCertificate; 166 } 167 168 /** 169 * Finalizes the order. 170 * <p> 171 * If the finalization was successful, the certificate is provided via 172 * {@link #getCertificate()}. 173 * <p> 174 * Even though the ACME protocol uses the term "finalize an order", this method is 175 * called {@link #execute(KeyPair)} to avoid confusion with the problematic 176 * {@link Object#finalize()} method. 177 * 178 * @param domainKeyPair 179 * The {@link KeyPair} that is going to be certified. This is <em>not</em> 180 * your account's keypair! 181 * @see #execute(KeyPair, Consumer) 182 * @see #execute(PKCS10CertificationRequest) 183 * @see #execute(byte[]) 184 * @since 3.0.0 185 */ 186 public void execute(KeyPair domainKeyPair) throws AcmeException { 187 execute(domainKeyPair, csrBuilder -> {}); 188 } 189 190 /** 191 * Finalizes the order (see {@link #execute(KeyPair)}). 192 * <p> 193 * This method also accepts a builderConsumer that can be used to add further details 194 * to the CSR (e.g. your organization). The identifiers (IPs, domain names, etc.) are 195 * automatically added to the CSR. 196 * 197 * @param domainKeyPair 198 * The {@link KeyPair} that is going to be used together with the certificate. 199 * This is not your account's keypair! 200 * @param builderConsumer 201 * {@link Consumer} that adds further details to the provided 202 * {@link CSRBuilder}. 203 * @see #execute(KeyPair) 204 * @see #execute(PKCS10CertificationRequest) 205 * @see #execute(byte[]) 206 * @since 3.0.0 207 */ 208 public void execute(KeyPair domainKeyPair, Consumer<CSRBuilder> builderConsumer) throws AcmeException { 209 try { 210 var csrBuilder = new CSRBuilder(); 211 csrBuilder.addIdentifiers(getIdentifiers()); 212 builderConsumer.accept(csrBuilder); 213 csrBuilder.sign(domainKeyPair); 214 execute(csrBuilder.getCSR()); 215 } catch (IOException ex) { 216 throw new AcmeException("Failed to create CSR", ex); 217 } 218 } 219 220 /** 221 * Finalizes the order (see {@link #execute(KeyPair)}). 222 * <p> 223 * This method receives a {@link PKCS10CertificationRequest} instance of a CSR that 224 * was generated externally. Use this method to gain full control over the content of 225 * the CSR. The CSR is not checked by acme4j, but just transported to the CA. It is 226 * your responsibility that it matches to the order. 227 * 228 * @param csr 229 * {@link PKCS10CertificationRequest} to be used for this order. 230 * @see #execute(KeyPair) 231 * @see #execute(KeyPair, Consumer) 232 * @see #execute(byte[]) 233 * @since 3.0.0 234 */ 235 public void execute(PKCS10CertificationRequest csr) throws AcmeException { 236 try { 237 execute(csr.getEncoded()); 238 } catch (IOException ex) { 239 throw new AcmeException("Invalid CSR", ex); 240 } 241 } 242 243 /** 244 * Finalizes the order (see {@link #execute(KeyPair)}). 245 * <p> 246 * This method receives a byte array containing an encoded CSR that was generated 247 * externally. Use this method to gain full control over the content of the CSR. The 248 * CSR is not checked by acme4j, but just transported to the CA. It is your 249 * responsibility that it matches to the order. 250 * 251 * @param csr 252 * Binary representation of a CSR containing the parameters for the 253 * certificate being requested, in DER format 254 */ 255 public void execute(byte[] csr) throws AcmeException { 256 LOG.debug("finalize"); 257 try (var conn = getSession().connect()) { 258 var claims = new JSONBuilder(); 259 claims.putBase64("csr", csr); 260 261 conn.sendSignedRequest(getFinalizeLocation(), claims, getLogin()); 262 } 263 invalidate(); 264 } 265 266 /** 267 * Checks if this order is auto-renewing, according to the ACME STAR specifications. 268 * 269 * @since 2.3 270 */ 271 public boolean isAutoRenewing() { 272 return getJSON().get("auto-renewal") 273 .optional() 274 .isPresent(); 275 } 276 277 /** 278 * Returns the earliest date of validity of the first certificate issued. 279 * 280 * @since 2.3 281 * @throws AcmeNotSupportedException if auto-renewal is not supported 282 */ 283 public Optional<Instant> getAutoRenewalStartDate() { 284 return getJSON().getFeature("auto-renewal") 285 .map(Value::asObject) 286 .orElseGet(JSON::empty) 287 .get("start-date") 288 .optional() 289 .map(Value::asInstant); 290 } 291 292 /** 293 * Returns the latest date of validity of the last certificate issued. 294 * 295 * @since 2.3 296 * @throws AcmeNotSupportedException if auto-renewal is not supported 297 */ 298 public Instant getAutoRenewalEndDate() { 299 return getJSON().getFeature("auto-renewal") 300 .map(Value::asObject) 301 .orElseGet(JSON::empty) 302 .get("end-date") 303 .asInstant(); 304 } 305 306 /** 307 * Returns the maximum lifetime of each certificate. 308 * 309 * @since 2.3 310 * @throws AcmeNotSupportedException if auto-renewal is not supported 311 */ 312 public Duration getAutoRenewalLifetime() { 313 return getJSON().getFeature("auto-renewal") 314 .optional() 315 .map(Value::asObject) 316 .orElseGet(JSON::empty) 317 .get("lifetime") 318 .asDuration(); 319 } 320 321 /** 322 * Returns the pre-date period of each certificate. 323 * 324 * @since 2.7 325 * @throws AcmeNotSupportedException if auto-renewal is not supported 326 */ 327 public Optional<Duration> getAutoRenewalLifetimeAdjust() { 328 return getJSON().getFeature("auto-renewal") 329 .optional() 330 .map(Value::asObject) 331 .orElseGet(JSON::empty) 332 .get("lifetime-adjust") 333 .optional() 334 .map(Value::asDuration); 335 } 336 337 /** 338 * Returns {@code true} if STAR certificates from this order can also be fetched via 339 * GET requests. 340 * 341 * @since 2.6 342 */ 343 public boolean isAutoRenewalGetEnabled() { 344 return getJSON().getFeature("auto-renewal") 345 .optional() 346 .map(Value::asObject) 347 .orElseGet(JSON::empty) 348 .get("allow-certificate-get") 349 .optional() 350 .map(Value::asBoolean) 351 .orElse(false); 352 } 353 354 /** 355 * Cancels an auto-renewing order. 356 * 357 * @since 2.3 358 */ 359 public void cancelAutoRenewal() throws AcmeException { 360 if (!getSession().getMetadata().isAutoRenewalEnabled()) { 361 throw new AcmeNotSupportedException("auto-renewal"); 362 } 363 364 LOG.debug("cancel"); 365 try (var conn = getSession().connect()) { 366 var claims = new JSONBuilder(); 367 claims.put("status", "canceled"); 368 369 conn.sendSignedRequest(getLocation(), claims, getLogin()); 370 setJSON(conn.readJsonResponse()); 371 } 372 } 373 374 @Override 375 protected void invalidate() { 376 super.invalidate(); 377 certificate = null; 378 autoRenewalCertificate = null; 379 authorizations = null; 380 } 381}