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}