001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2016 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.URI;
019import java.net.URL;
020import java.time.Duration;
021import java.util.Collection;
022import java.util.Optional;
023import java.util.Set;
024
025import org.shredzone.acme4j.exception.AcmeNotSupportedException;
026import org.shredzone.acme4j.toolbox.JSON;
027import org.shredzone.acme4j.toolbox.JSON.Value;
028
029/**
030 * A collection of metadata related to the CA provider.
031 */
032public class Metadata {
033
034    private final JSON meta;
035
036    /**
037     * Creates a new {@link Metadata} instance.
038     *
039     * @param meta
040     *            JSON map of metadata
041     */
042    public Metadata(JSON meta) {
043        this.meta = meta;
044    }
045
046    /**
047     * Returns an {@link URI} of the current terms of service, or empty if not available.
048     */
049    public Optional<URI> getTermsOfService() {
050        return meta.get("termsOfService").map(Value::asURI);
051    }
052
053    /**
054     * Returns an {@link URL} of a website providing more information about the ACME
055     * server. Empty if not available.
056     */
057    public Optional<URL> getWebsite() {
058        return meta.get("website").map(Value::asURL);
059    }
060
061    /**
062     * Returns a collection of hostnames, which the ACME server recognises as referring to
063     * itself for the purposes of CAA record validation. Empty if not available.
064     */
065    public Collection<String> getCaaIdentities() {
066        return meta.get("caaIdentities")
067                .asArray()
068                .stream()
069                .map(Value::asString)
070                .collect(toList());
071    }
072
073    /**
074     * Returns whether an external account is required by this CA.
075     */
076    public boolean isExternalAccountRequired() {
077        return meta.get("externalAccountRequired").map(Value::asBoolean).orElse(false);
078    }
079
080    /**
081     * Returns whether the CA supports short-term auto-renewal of certificates.
082     *
083     * @since 2.3
084     */
085    public boolean isAutoRenewalEnabled() {
086        return meta.get("auto-renewal").isPresent();
087    }
088
089    /**
090     * Returns the minimum acceptable value for the maximum validity of a certificate
091     * before auto-renewal.
092     *
093     * @since 2.3
094     * @throws AcmeNotSupportedException if the server does not support auto-renewal.
095     */
096    public Duration getAutoRenewalMinLifetime() {
097        return meta.getFeature("auto-renewal")
098                .map(Value::asObject)
099                .orElseGet(JSON::empty)
100                .get("min-lifetime")
101                .asDuration();
102    }
103
104    /**
105     * Returns the maximum delta between auto-renewal end date and auto-renewal start
106     * date.
107     *
108     * @since 2.3
109     * @throws AcmeNotSupportedException if the server does not support auto-renewal.
110     */
111    public Duration getAutoRenewalMaxDuration() {
112        return meta.getFeature("auto-renewal")
113                .map(Value::asObject)
114                .orElseGet(JSON::empty)
115                .get("max-duration")
116                .asDuration();
117    }
118
119    /**
120     * Returns whether the CA also allows to fetch STAR certificates via GET request.
121     *
122     * @since 2.6
123     * @throws AcmeNotSupportedException if the server does not support auto-renewal.
124     */
125    public boolean isAutoRenewalGetAllowed() {
126        return meta.getFeature("auto-renewal").optional()
127                .map(Value::asObject)
128                .orElseGet(JSON::empty)
129                .get("allow-certificate-get")
130                .optional()
131                .map(Value::asBoolean)
132                .orElse(false);
133    }
134
135    /**
136     * Returns whether the CA supports the profile feature.
137     *
138     * @since 3.5.0
139     * @draft This method is currently based on RFC draft draft-aaron-acme-profiles. It
140     * may be changed or removed without notice to reflect future changes to the draft.
141     * SemVer rules do not apply here.
142     */
143    public boolean isProfileAllowed() {
144        return meta.get("profiles").isPresent();
145    }
146
147    /**
148     * Returns whether the CA supports the requested profile.
149     * <p>
150     * Also returns {@code false} if profiles are not allowed in general.
151     *
152     * @since 3.5.0
153     * @draft This method is currently based on RFC draft draft-aaron-acme-profiles. It
154     * may be changed or removed without notice to reflect future changes to the draft.
155     * SemVer rules do not apply here.
156     */
157    public boolean isProfileAllowed(String profile) {
158        return getProfiles().contains(profile);
159    }
160
161    /**
162     * Returns all profiles supported by the CA. May be empty if the CA does not support
163     * profiles.
164     *
165     * @since 3.5.0
166     * @draft This method is currently based on RFC draft draft-aaron-acme-profiles. It
167     * may be changed or removed without notice to reflect future changes to the draft.
168     * SemVer rules do not apply here.
169     */
170    public Set<String> getProfiles() {
171        return meta.get("profiles")
172                .optional()
173                .map(Value::asObject)
174                .orElseGet(JSON::empty)
175                .keySet();
176    }
177
178    /**
179     * Returns a description of the requested profile. This can be a human-readable string
180     * or a URL linking to a documentation.
181     * <p>
182     * Empty if the profile is not allowed.
183     *
184     * @since 3.5.0
185     * @draft This method is currently based on RFC draft draft-aaron-acme-profiles. It
186     * may be changed or removed without notice to reflect future changes to the draft.
187     * SemVer rules do not apply here.
188     */
189    public Optional<String> getProfileDescription(String profile) {
190        return meta.get("profiles").optional()
191                .map(Value::asObject)
192                .orElseGet(JSON::empty)
193                .get(profile)
194                .optional()
195                .map(Value::asString);
196    }
197
198    /**
199     * Returns whether the CA supports subdomain auth according to RFC9444.
200     *
201     * @since 3.3.0
202     */
203    public boolean isSubdomainAuthAllowed() {
204        return meta.get("subdomainAuthAllowed").map(Value::asBoolean).orElse(false);
205    }
206
207    /**
208     * Returns the JSON representation of the metadata. This is useful for reading
209     * proprietary metadata properties.
210     */
211    public JSON getJSON() {
212        return meta;
213    }
214
215}