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