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.Objects.requireNonNull; 017import static java.util.stream.Collectors.toList; 018import static org.shredzone.acme4j.toolbox.AcmeUtils.getRenewalUniqueIdentifier; 019 020import java.security.cert.X509Certificate; 021import java.time.Duration; 022import java.time.Instant; 023import java.util.Collection; 024import java.util.LinkedHashSet; 025import java.util.Objects; 026import java.util.Set; 027 028import edu.umd.cs.findbugs.annotations.Nullable; 029import org.shredzone.acme4j.connector.Resource; 030import org.shredzone.acme4j.exception.AcmeException; 031import org.shredzone.acme4j.exception.AcmeNotSupportedException; 032import org.shredzone.acme4j.toolbox.JSONBuilder; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * Start a new certificate {@link Order}. 038 * <p> 039 * Use {@link Login#newOrder()} or {@link Account#newOrder()} to create a new 040 * {@link OrderBuilder} instance. Both methods are identical. 041 */ 042public class OrderBuilder { 043 private static final Logger LOG = LoggerFactory.getLogger(OrderBuilder.class); 044 045 private final Login login; 046 047 private final Set<Identifier> identifierSet = new LinkedHashSet<>(); 048 private @Nullable Instant notBefore; 049 private @Nullable Instant notAfter; 050 private @Nullable String replaces; 051 private boolean autoRenewal; 052 private @Nullable Instant autoRenewalStart; 053 private @Nullable Instant autoRenewalEnd; 054 private @Nullable Duration autoRenewalLifetime; 055 private @Nullable Duration autoRenewalLifetimeAdjust; 056 private boolean autoRenewalGet; 057 058 /** 059 * Create a new {@link OrderBuilder}. 060 * 061 * @param login 062 * {@link Login} to bind with 063 */ 064 protected OrderBuilder(Login login) { 065 this.login = login; 066 } 067 068 /** 069 * Adds a domain name to the order. 070 * 071 * @param domain 072 * Name of a domain to be ordered. May be a wildcard domain if supported by 073 * the CA. IDN names are accepted and will be ACE encoded automatically. 074 * @return itself 075 */ 076 public OrderBuilder domain(String domain) { 077 return identifier(Identifier.dns(domain)); 078 } 079 080 /** 081 * Adds domain names to the order. 082 * 083 * @param domains 084 * Collection of domain names to be ordered. May be wildcard domains if 085 * supported by the CA. IDN names are accepted and will be ACE encoded 086 * automatically. 087 * @return itself 088 */ 089 public OrderBuilder domains(String... domains) { 090 for (var domain : requireNonNull(domains, "domains")) { 091 domain(domain); 092 } 093 return this; 094 } 095 096 /** 097 * Adds a collection of domain names to the order. 098 * 099 * @param domains 100 * Collection of domain names to be ordered. May be wildcard domains if 101 * supported by the CA. IDN names are accepted and will be ACE encoded 102 * automatically. 103 * @return itself 104 */ 105 public OrderBuilder domains(Collection<String> domains) { 106 requireNonNull(domains, "domains").forEach(this::domain); 107 return this; 108 } 109 110 /** 111 * Adds an {@link Identifier} to the order. 112 * 113 * @param identifier 114 * {@link Identifier} to be added to the order. 115 * @return itself 116 * @since 2.3 117 */ 118 public OrderBuilder identifier(Identifier identifier) { 119 identifierSet.add(requireNonNull(identifier, "identifier")); 120 return this; 121 } 122 123 /** 124 * Adds a collection of {@link Identifier} to the order. 125 * 126 * @param identifiers 127 * Collection of {@link Identifier} to be added to the order. 128 * @return itself 129 * @since 2.3 130 */ 131 public OrderBuilder identifiers(Collection<Identifier> identifiers) { 132 requireNonNull(identifiers, "identifiers").forEach(this::identifier); 133 return this; 134 } 135 136 /** 137 * Sets a "not before" date in the certificate. May be ignored by the CA. 138 * 139 * @param notBefore "not before" date 140 * @return itself 141 */ 142 public OrderBuilder notBefore(Instant notBefore) { 143 if (autoRenewal) { 144 throw new IllegalArgumentException("cannot combine notBefore with autoRenew"); 145 } 146 this.notBefore = requireNonNull(notBefore, "notBefore"); 147 return this; 148 } 149 150 /** 151 * Sets a "not after" date in the certificate. May be ignored by the CA. 152 * 153 * @param notAfter "not after" date 154 * @return itself 155 */ 156 public OrderBuilder notAfter(Instant notAfter) { 157 if (autoRenewal) { 158 throw new IllegalArgumentException("cannot combine notAfter with autoRenew"); 159 } 160 this.notAfter = requireNonNull(notAfter, "notAfter"); 161 return this; 162 } 163 164 /** 165 * Enables short-term automatic renewal of the certificate, if supported by the CA. 166 * <p> 167 * Automatic renewals cannot be combined with {@link #notBefore(Instant)} or 168 * {@link #notAfter(Instant)}. 169 * 170 * @return itself 171 * @since 2.3 172 */ 173 public OrderBuilder autoRenewal() { 174 if (notBefore != null || notAfter != null) { 175 throw new IllegalArgumentException("cannot combine notBefore/notAfter with autoRenewal"); 176 } 177 this.autoRenewal = true; 178 return this; 179 } 180 181 /** 182 * Notifies the CA that the ordered certificate will replace a previously issued 183 * certificate. The certificate is identified by its ARI unique identifier. 184 * <p> 185 * Optional, only supported if the CA provides renewal information. However, in this 186 * case the client <em>should</em> include this field. 187 * 188 * @param uniqueId 189 * Certificate's renewal unique identifier. 190 * @return itself 191 * @draft This method is currently based on an RFC draft. It may be changed or removed 192 * without notice to reflect future changes to the draft. SemVer rules do not apply 193 * here. 194 * @since 3.2.0 195 */ 196 public OrderBuilder replaces(String uniqueId) { 197 autoRenewal(); 198 this.replaces = Objects.requireNonNull(uniqueId); 199 return this; 200 } 201 202 /** 203 * Notifies the CA that the ordered certificate will replace a previously issued 204 * certificate. 205 * <p> 206 * Optional, only supported if the CA provides renewal information. However, in this 207 * case the client <em>should</em> include this field. 208 * 209 * @param certificate 210 * Certificate to be replaced 211 * @return itself 212 * @draft This method is currently based on an RFC draft. It may be changed or removed 213 * without notice to reflect future changes to the draft. SemVer rules do not apply 214 * here. 215 * @since 3.2.0 216 */ 217 public OrderBuilder replaces(X509Certificate certificate) { 218 return replaces(getRenewalUniqueIdentifier(certificate)); 219 } 220 221 /** 222 * Notifies the CA that the ordered certificate will replace a previously issued 223 * certificate. 224 * <p> 225 * Optional, only supported if the CA provides renewal information. However, in this 226 * case the client <em>should</em> include this field. 227 * 228 * @param certificate 229 * Certificate to be replaced 230 * @return itself 231 * @draft This method is currently based on an RFC draft. It may be changed or removed 232 * without notice to reflect future changes to the draft. SemVer rules do not apply 233 * here. 234 * @since 3.2.0 235 */ 236 public OrderBuilder replaces(Certificate certificate) { 237 return replaces(certificate.getCertificate()); 238 } 239 240 /** 241 * Sets the earliest date of validity of the first issued certificate. If not set, 242 * the start date is the earliest possible date. 243 * <p> 244 * Implies {@link #autoRenewal()}. 245 * 246 * @param start 247 * Start date of validity 248 * @return itself 249 * @since 2.3 250 */ 251 public OrderBuilder autoRenewalStart(Instant start) { 252 autoRenewal(); 253 this.autoRenewalStart = requireNonNull(start, "start"); 254 return this; 255 } 256 257 /** 258 * Sets the latest date of validity of the last issued certificate. If not set, the 259 * CA's default is used. 260 * <p> 261 * Implies {@link #autoRenewal()}. 262 * 263 * @param end 264 * End date of validity 265 * @return itself 266 * @see Metadata#getAutoRenewalMaxDuration() 267 * @since 2.3 268 */ 269 public OrderBuilder autoRenewalEnd(Instant end) { 270 autoRenewal(); 271 this.autoRenewalEnd = requireNonNull(end, "end"); 272 return this; 273 } 274 275 /** 276 * Sets the maximum validity period of each certificate. If not set, the CA's 277 * default is used. 278 * <p> 279 * Implies {@link #autoRenewal()}. 280 * 281 * @param duration 282 * Duration of validity of each certificate 283 * @return itself 284 * @see Metadata#getAutoRenewalMinLifetime() 285 * @since 2.3 286 */ 287 public OrderBuilder autoRenewalLifetime(Duration duration) { 288 autoRenewal(); 289 this.autoRenewalLifetime = requireNonNull(duration, "duration"); 290 return this; 291 } 292 293 /** 294 * Sets the amount of pre-dating each certificate. If not set, the CA's 295 * default (0) is used. 296 * <p> 297 * Implies {@link #autoRenewal()}. 298 * 299 * @param duration 300 * Duration of certificate pre-dating 301 * @return itself 302 * @since 2.7 303 */ 304 public OrderBuilder autoRenewalLifetimeAdjust(Duration duration) { 305 autoRenewal(); 306 this.autoRenewalLifetimeAdjust = requireNonNull(duration, "duration"); 307 return this; 308 } 309 310 /** 311 * Announces that the client wishes to fetch the auto-renewed certificate via GET 312 * request. If not used, the STAR certificate can only be fetched via POST-as-GET 313 * request. {@link Metadata#isAutoRenewalGetAllowed()} must return {@code true} in 314 * order for this option to work. 315 * <p> 316 * This option is only needed if you plan to fetch the STAR certificate via other 317 * means than by using acme4j. acme4j is fetching certificates via POST-as-GET 318 * request. 319 * <p> 320 * Implies {@link #autoRenewal()}. 321 * 322 * @return itself 323 * @since 2.6 324 */ 325 public OrderBuilder autoRenewalEnableGet() { 326 autoRenewal(); 327 this.autoRenewalGet = true; 328 return this; 329 } 330 331 /** 332 * Sends a new order to the server, and returns an {@link Order} object. 333 * 334 * @return {@link Order} that was created 335 */ 336 public Order create() throws AcmeException { 337 if (identifierSet.isEmpty()) { 338 throw new IllegalArgumentException("At least one identifer is required"); 339 } 340 341 var session = login.getSession(); 342 343 if (autoRenewal && !session.getMetadata().isAutoRenewalEnabled()) { 344 throw new AcmeNotSupportedException("auto-renewal"); 345 } 346 347 LOG.debug("create"); 348 try (var conn = session.connect()) { 349 var claims = new JSONBuilder(); 350 claims.array("identifiers", identifierSet.stream().map(Identifier::toMap).collect(toList())); 351 352 if (notBefore != null) { 353 claims.put("notBefore", notBefore); 354 } 355 if (notAfter != null) { 356 claims.put("notAfter", notAfter); 357 } 358 359 if (autoRenewal) { 360 var arClaims = claims.object("auto-renewal"); 361 if (autoRenewalStart != null) { 362 arClaims.put("start-date", autoRenewalStart); 363 } 364 if (autoRenewalStart != null) { 365 arClaims.put("end-date", autoRenewalEnd); 366 } 367 if (autoRenewalLifetime != null) { 368 arClaims.put("lifetime", autoRenewalLifetime); 369 } 370 if (autoRenewalLifetimeAdjust != null) { 371 arClaims.put("lifetime-adjust", autoRenewalLifetimeAdjust); 372 } 373 if (autoRenewalGet) { 374 arClaims.put("allow-certificate-get", autoRenewalGet); 375 } 376 } 377 378 if (replaces != null) { 379 claims.put("replaces", replaces); 380 } 381 382 conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, login); 383 384 var order = new Order(login, conn.getLocation()); 385 order.setJSON(conn.readJsonResponse()); 386 return order; 387 } 388 } 389 390}