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}