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.toList; 017 018import java.net.URL; 019import java.time.Duration; 020import java.time.Instant; 021import java.util.Collections; 022import java.util.List; 023 024import edu.umd.cs.findbugs.annotations.Nullable; 025import org.shredzone.acme4j.connector.Connection; 026import org.shredzone.acme4j.exception.AcmeException; 027import org.shredzone.acme4j.toolbox.JSON; 028import org.shredzone.acme4j.toolbox.JSON.Value; 029import org.shredzone.acme4j.toolbox.JSONBuilder; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Represents a certificate order. 035 */ 036public class Order extends AcmeJsonResource { 037 private static final long serialVersionUID = 5435808648658292177L; 038 private static final Logger LOG = LoggerFactory.getLogger(Order.class); 039 040 protected Order(Login login, URL location) { 041 super(login, location); 042 } 043 044 /** 045 * Returns the current status of the order. 046 * <p> 047 * Possible values are: {@link Status#PENDING}, {@link Status#READY}, 048 * {@link Status#PROCESSING}, {@link Status#VALID}, {@link Status#INVALID}. 049 */ 050 public Status getStatus() { 051 return getJSON().get("status").asStatus(); 052 } 053 054 /** 055 * Returns a {@link Problem} document if the order failed. 056 */ 057 @Nullable 058 public Problem getError() { 059 return getJSON().get("error").map(v -> v.asProblem(getLocation())).orElse(null); 060 } 061 062 /** 063 * Gets the expiry date of the authorization, if set by the server. 064 */ 065 @Nullable 066 public Instant getExpires() { 067 return getJSON().get("expires").map(Value::asInstant).orElse(null); 068 } 069 070 /** 071 * Gets the list of {@link Identifier} to be ordered. 072 * 073 * @since 2.3 074 */ 075 public List<Identifier> getIdentifiers() { 076 return Collections.unmodifiableList(getJSON().get("identifiers") 077 .asArray() 078 .stream() 079 .map(Value::asIdentifier) 080 .collect(toList())); 081 } 082 083 /** 084 * Gets the "not before" date that was used for the order, or {@code null}. 085 */ 086 @Nullable 087 public Instant getNotBefore() { 088 return getJSON().get("notBefore").map(Value::asInstant).orElse(null); 089 } 090 091 /** 092 * Gets the "not after" date that was used for the order, or {@code null}. 093 */ 094 @Nullable 095 public Instant getNotAfter() { 096 return getJSON().get("notAfter").map(Value::asInstant).orElse(null); 097 } 098 099 /** 100 * Gets the {@link Authorization} required for this order, in no specific order. 101 */ 102 public List<Authorization> getAuthorizations() { 103 Login login = getLogin(); 104 return Collections.unmodifiableList(getJSON().get("authorizations") 105 .asArray() 106 .stream() 107 .map(Value::asURL) 108 .map(login::bindAuthorization) 109 .collect(toList())); 110 } 111 112 /** 113 * Gets the location {@link URL} of where to send the finalization call to. 114 * <p> 115 * For internal purposes. Use {@link #execute(byte[])} to finalize an order. 116 */ 117 public URL getFinalizeLocation() { 118 return getJSON().get("finalize").asURL(); 119 } 120 121 /** 122 * Gets the {@link Certificate} if it is available. {@code null} otherwise. 123 */ 124 @Nullable 125 public Certificate getCertificate() { 126 return getJSON().get("certificate") 127 .map(Value::asURL) 128 .map(getLogin()::bindCertificate) 129 .orElse(null); 130 } 131 132 /** 133 * Gets the STAR extension's {@link Certificate} if it is available. {@code null} 134 * otherwise. 135 * 136 * @since 2.6 137 */ 138 @Nullable 139 public Certificate getAutoRenewalCertificate() { 140 return getJSON().get("star-certificate") 141 .map(Value::asURL) 142 .map(getLogin()::bindCertificate) 143 .orElse(null); 144 } 145 146 /** 147 * Finalizes the order, by providing a CSR. 148 * <p> 149 * After a successful finalization, the certificate is available at 150 * {@link #getCertificate()}. 151 * <p> 152 * Even though the ACME protocol uses the term "finalize an order", this method is 153 * called {@link #execute(byte[])} to avoid confusion with the general 154 * {@link Object#finalize()} method. 155 * 156 * @param csr 157 * CSR containing the parameters for the certificate being requested, in 158 * DER format 159 */ 160 public void execute(byte[] csr) throws AcmeException { 161 LOG.debug("finalize"); 162 try (Connection conn = getSession().connect()) { 163 JSONBuilder claims = new JSONBuilder(); 164 claims.putBase64("csr", csr); 165 166 conn.sendSignedRequest(getFinalizeLocation(), claims, getLogin()); 167 } 168 invalidate(); 169 } 170 171 /** 172 * Checks if this order is auto-renewing, according to the ACME STAR specifications. 173 * 174 * @since 2.3 175 */ 176 public boolean isAutoRenewing() { 177 return getJSON().get("auto-renewal") 178 .optional() 179 .isPresent(); 180 } 181 182 /** 183 * Returns the earliest date of validity of the first certificate issued, or 184 * {@code null}. 185 * 186 * @since 2.3 187 */ 188 @Nullable 189 public Instant getAutoRenewalStartDate() { 190 return getJSON().get("auto-renewal") 191 .optional() 192 .map(Value::asObject) 193 .orElseGet(JSON::empty) 194 .get("start-date") 195 .optional() 196 .map(Value::asInstant) 197 .orElse(null); 198 } 199 200 /** 201 * Returns the latest date of validity of the last certificate issued, or 202 * {@code null}. 203 * 204 * @since 2.3 205 */ 206 @Nullable 207 public Instant getAutoRenewalEndDate() { 208 return getJSON().get("auto-renewal") 209 .optional() 210 .map(Value::asObject) 211 .orElseGet(JSON::empty) 212 .get("end-date") 213 .optional() 214 .map(Value::asInstant) 215 .orElse(null); 216 } 217 218 /** 219 * Returns the maximum lifetime of each certificate, or {@code null}. 220 * 221 * @since 2.3 222 */ 223 @Nullable 224 public Duration getAutoRenewalLifetime() { 225 return getJSON().get("auto-renewal") 226 .optional() 227 .map(Value::asObject) 228 .orElseGet(JSON::empty) 229 .get("lifetime") 230 .optional() 231 .map(Value::asDuration) 232 .orElse(null); 233 } 234 235 /** 236 * Returns the predate period of each certificate, or {@code null}. 237 * 238 * @since 2.7 239 */ 240 @Nullable 241 public Duration getAutoRenewalLifetimeAdjust() { 242 return getJSON().get("auto-renewal") 243 .optional() 244 .map(Value::asObject) 245 .orElseGet(JSON::empty) 246 .get("lifetime-adjust") 247 .optional() 248 .map(Value::asDuration) 249 .orElse(null); 250 } 251 252 /** 253 * Returns {@code true} if STAR certificates from this order can also be fetched via 254 * GET requests. 255 * 256 * @since 2.6 257 */ 258 public boolean isAutoRenewalGetEnabled() { 259 return getJSON().get("auto-renewal") 260 .optional() 261 .map(Value::asObject) 262 .orElseGet(JSON::empty) 263 .get("allow-certificate-get") 264 .optional() 265 .map(Value::asBoolean) 266 .orElse(false); 267 } 268 269 /** 270 * Cancels an auto-renewing order. 271 * 272 * @since 2.3 273 */ 274 public void cancelAutoRenewal() throws AcmeException { 275 if (!getSession().getMetadata().isAutoRenewalEnabled()) { 276 throw new AcmeException("CA does not support short-term automatic renewals"); 277 } 278 279 LOG.debug("cancel"); 280 try (Connection conn = getSession().connect()) { 281 JSONBuilder claims = new JSONBuilder(); 282 claims.put("status", "canceled"); 283 284 conn.sendSignedRequest(getLocation(), claims, getLogin()); 285 setJSON(conn.readJsonResponse()); 286 } 287 } 288 289}