mirror of https://github.com/shred/acme4j
Clean up code
parent
584452b079
commit
2ce40ec971
|
@ -47,23 +47,25 @@ public class Authorization extends AcmeResource {
|
|||
private List<List<Challenge>> combinations;
|
||||
private boolean loaded = false;
|
||||
|
||||
protected Authorization(Session session, URI location) {
|
||||
super(session);
|
||||
setLocation(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Authorization} and binds it to the {@link Session}.
|
||||
* Creates a new instance of {@link Authorization} and binds it to the
|
||||
* {@link Session}.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Location of the Authorization
|
||||
* @return {@link Authorization} bound to the session and location
|
||||
*/
|
||||
public static Authorization bind(Session session, URI location) {
|
||||
return new Authorization(session, location);
|
||||
}
|
||||
|
||||
protected Authorization(Session session, URI location) {
|
||||
super(session);
|
||||
setLocation(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the domain name to be authorized.
|
||||
*/
|
||||
|
@ -240,6 +242,20 @@ public class Authorization extends AcmeResource {
|
|||
domain = jsonIdentifier.get("value").asString();
|
||||
}
|
||||
|
||||
challenges = fetchChallenges(json);
|
||||
combinations = fetchCombinations(json, challenges);
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all {@link Challenge} that are defined in the JSON.
|
||||
*
|
||||
* @param json
|
||||
* {@link JSON} to read
|
||||
* @return List of {@link Challenge}
|
||||
*/
|
||||
private List<Challenge> fetchChallenges(JSON json) {
|
||||
JSON.Array jsonChallenges = json.get("challenges").asArray();
|
||||
List<Challenge> cr = new ArrayList<>();
|
||||
for (JSON.Value c : jsonChallenges) {
|
||||
|
@ -248,28 +264,34 @@ public class Authorization extends AcmeResource {
|
|||
cr.add(ch);
|
||||
}
|
||||
}
|
||||
challenges = cr;
|
||||
return cr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all possible combination of {@link Challenge} that are defined in the JSON.
|
||||
*
|
||||
* @param json
|
||||
* {@link JSON} to read
|
||||
* @param challenges
|
||||
* List of available {@link Challenge}
|
||||
* @return List of {@link Challenge} combinations
|
||||
*/
|
||||
private List<List<Challenge>> fetchCombinations(JSON json, List<Challenge> challenges) {
|
||||
JSON.Array jsonCombinations = json.get("combinations").asArray();
|
||||
if (jsonCombinations != null) {
|
||||
List<List<Challenge>> cmb = new ArrayList<>(jsonCombinations.size());
|
||||
|
||||
for (int ix = 0; ix < jsonCombinations.size(); ix++) {
|
||||
JSON.Array c = jsonCombinations.get(ix).asArray();
|
||||
List<Challenge> clist = new ArrayList<>(c.size());
|
||||
for (JSON.Value n : c) {
|
||||
clist.add(cr.get(n.asInt()));
|
||||
}
|
||||
cmb.add(clist);
|
||||
}
|
||||
combinations = cmb;
|
||||
} else {
|
||||
List<List<Challenge>> cmb = new ArrayList<>(1);
|
||||
cmb.add(cr);
|
||||
combinations = cmb;
|
||||
if (jsonCombinations == null) {
|
||||
return Arrays.asList(challenges);
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
List<List<Challenge>> cmb = new ArrayList<>(jsonCombinations.size());
|
||||
for (JSON.Value v : jsonCombinations) {
|
||||
JSON.Array c = v.asArray();
|
||||
List<Challenge> clist = new ArrayList<>(c.size());
|
||||
for (JSON.Value n : c) {
|
||||
clist.add(challenges.get(n.asInt()));
|
||||
}
|
||||
cmb.add(clist);
|
||||
}
|
||||
return cmb;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,18 +41,6 @@ public class Certificate extends AcmeResource {
|
|||
private X509Certificate cert = null;
|
||||
private X509Certificate[] chain = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Certificate} and binds it to the {@link Session}.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Location of the Certificate
|
||||
*/
|
||||
public static Certificate bind(Session session, URI location) {
|
||||
return new Certificate(session, location);
|
||||
}
|
||||
|
||||
protected Certificate(Session session, URI certUri) {
|
||||
super(session);
|
||||
setLocation(certUri);
|
||||
|
@ -65,6 +53,19 @@ public class Certificate extends AcmeResource {
|
|||
this.cert = cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Certificate} and binds it to the {@link Session}.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Location of the Certificate
|
||||
* @return {@link Certificate} bound to the session and location
|
||||
*/
|
||||
public static Certificate bind(Session session, URI location) {
|
||||
return new Certificate(session, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the certificate chain. {@code null} if not known or not
|
||||
* available.
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.shredzone.acme4j;
|
|||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.shredzone.acme4j.util.JSON;
|
||||
|
@ -57,12 +58,12 @@ public class Metadata {
|
|||
|
||||
/**
|
||||
* Returns a collection of hostnames, which the ACME server recognises as referring to
|
||||
* itself for the purposes of CAA record validation. {@code null} if not available.
|
||||
* itself for the purposes of CAA record validation. Empty if not available.
|
||||
*/
|
||||
public Collection<String> getCaaIdentities() {
|
||||
Array array = meta.get("caa-identities").asArray();
|
||||
if (array == null) {
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>(array.size());
|
||||
|
|
|
@ -48,6 +48,12 @@ public class Registration extends AcmeResource {
|
|||
private static final long serialVersionUID = -8177333806740391140L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Registration.class);
|
||||
|
||||
private static final String KEY_AGREEMENT = "agreement";
|
||||
private static final String KEY_AUTHORIZATIONS = "authorizations";
|
||||
private static final String KEY_CERTIFICATES = "certificates";
|
||||
private static final String KEY_CONTACT = "contact";
|
||||
private static final String KEY_STATUS = "status";
|
||||
|
||||
private final List<URI> contacts = new ArrayList<>();
|
||||
private URI agreement;
|
||||
private URI authorizations;
|
||||
|
@ -55,18 +61,6 @@ public class Registration extends AcmeResource {
|
|||
private Status status;
|
||||
private boolean loaded = false;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Registration} and binds it to the {@link Session}.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Location URI of the registration
|
||||
*/
|
||||
public static Registration bind(Session session, URI location) {
|
||||
return new Registration(session, location);
|
||||
}
|
||||
|
||||
protected Registration(Session session, URI location) {
|
||||
super(session);
|
||||
setLocation(location);
|
||||
|
@ -78,6 +72,19 @@ public class Registration extends AcmeResource {
|
|||
this.agreement = agreement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Registration} and binds it to the {@link Session}.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Location URI of the registration
|
||||
* @return {@link Registration} bound to the session and location
|
||||
*/
|
||||
public static Registration bind(Session session, URI location) {
|
||||
return new Registration(session, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the agreement document the user is required to accept.
|
||||
*/
|
||||
|
@ -118,7 +125,7 @@ public class Registration extends AcmeResource {
|
|||
public Iterator<Authorization> getAuthorizations() throws AcmeException {
|
||||
LOG.debug("getAuthorizations");
|
||||
load();
|
||||
return new ResourceIterator<Authorization>(getSession(), "authorizations", authorizations) {
|
||||
return new ResourceIterator<Authorization>(getSession(), KEY_AUTHORIZATIONS, authorizations) {
|
||||
@Override
|
||||
protected Authorization create(Session session, URI uri) {
|
||||
return Authorization.bind(session, uri);
|
||||
|
@ -140,7 +147,7 @@ public class Registration extends AcmeResource {
|
|||
public Iterator<Certificate> getCertificates() throws AcmeException {
|
||||
LOG.debug("getCertificates");
|
||||
load();
|
||||
return new ResourceIterator<Certificate>(getSession(), "certificates", certificates) {
|
||||
return new ResourceIterator<Certificate>(getSession(), KEY_CERTIFICATES, certificates) {
|
||||
@Override
|
||||
protected Certificate create(Session session, URI uri) {
|
||||
return Certificate.bind(session, uri);
|
||||
|
@ -317,7 +324,7 @@ public class Registration extends AcmeResource {
|
|||
try (Connection conn = getSession().provider().connect()) {
|
||||
JSONBuilder claims = new JSONBuilder();
|
||||
claims.putResource("reg");
|
||||
claims.put("status", "deactivated");
|
||||
claims.put(KEY_STATUS, "deactivated");
|
||||
|
||||
conn.sendSignedRequest(getLocation(), claims, getSession());
|
||||
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
|
||||
|
@ -349,22 +356,22 @@ public class Registration extends AcmeResource {
|
|||
* {@link Connection} with headers to be evaluated
|
||||
*/
|
||||
private void unmarshal(JSON json, Connection conn) {
|
||||
if (json.contains("agreement")) {
|
||||
this.agreement = json.get("agreement").asURI();
|
||||
if (json.contains(KEY_AGREEMENT)) {
|
||||
this.agreement = json.get(KEY_AGREEMENT).asURI();
|
||||
}
|
||||
|
||||
if (json.contains("contact")) {
|
||||
if (json.contains(KEY_CONTACT)) {
|
||||
contacts.clear();
|
||||
for (JSON.Value v : json.get("contact").asArray()) {
|
||||
for (JSON.Value v : json.get(KEY_CONTACT).asArray()) {
|
||||
contacts.add(v.asURI());
|
||||
}
|
||||
}
|
||||
|
||||
this.authorizations = json.get("authorizations").asURI();
|
||||
this.certificates = json.get("certificates").asURI();
|
||||
this.authorizations = json.get(KEY_AUTHORIZATIONS).asURI();
|
||||
this.certificates = json.get(KEY_CERTIFICATES).asURI();
|
||||
|
||||
if (json.contains("status")) {
|
||||
this.status = Status.parse(json.get("status").asString());
|
||||
if (json.contains(KEY_STATUS)) {
|
||||
this.status = Status.parse(json.get(KEY_STATUS).asString());
|
||||
}
|
||||
|
||||
URI location = conn.getLocation();
|
||||
|
@ -396,7 +403,7 @@ public class Registration extends AcmeResource {
|
|||
private final List<URI> editContacts = new ArrayList<>();
|
||||
private URI editAgreement;
|
||||
|
||||
public EditableRegistration() {
|
||||
private EditableRegistration() {
|
||||
editContacts.addAll(Registration.this.contacts);
|
||||
editAgreement = Registration.this.agreement;
|
||||
}
|
||||
|
@ -414,6 +421,7 @@ public class Registration extends AcmeResource {
|
|||
*
|
||||
* @param contact
|
||||
* Contact URI
|
||||
* @return itself
|
||||
*/
|
||||
public EditableRegistration addContact(URI contact) {
|
||||
editContacts.add(contact);
|
||||
|
@ -427,6 +435,7 @@ public class Registration extends AcmeResource {
|
|||
*
|
||||
* @param contact
|
||||
* Contact URI as string
|
||||
* @return itself
|
||||
*/
|
||||
public EditableRegistration addContact(String contact) {
|
||||
addContact(URI.create(contact));
|
||||
|
@ -438,6 +447,7 @@ public class Registration extends AcmeResource {
|
|||
*
|
||||
* @param agreement
|
||||
* New agreement URI
|
||||
* @return itself
|
||||
*/
|
||||
public EditableRegistration setAgreement(URI agreement) {
|
||||
this.editAgreement = agreement;
|
||||
|
@ -453,10 +463,10 @@ public class Registration extends AcmeResource {
|
|||
JSONBuilder claims = new JSONBuilder();
|
||||
claims.putResource("reg");
|
||||
if (!editContacts.isEmpty()) {
|
||||
claims.put("contact", editContacts);
|
||||
claims.put(KEY_CONTACT, editContacts);
|
||||
}
|
||||
if (editAgreement != null) {
|
||||
claims.put("agreement", editAgreement);
|
||||
claims.put(KEY_AGREEMENT, editAgreement);
|
||||
}
|
||||
|
||||
conn.sendSignedRequest(getLocation(), claims, getSession());
|
||||
|
|
|
@ -39,6 +39,7 @@ public class RegistrationBuilder {
|
|||
*
|
||||
* @param contact
|
||||
* Contact URI
|
||||
* @return itself
|
||||
*/
|
||||
public RegistrationBuilder addContact(URI contact) {
|
||||
contacts.add(contact);
|
||||
|
@ -54,6 +55,7 @@ public class RegistrationBuilder {
|
|||
* Contact URI as string
|
||||
* @throws IllegalArgumentException
|
||||
* if there is a syntax error in the URI string
|
||||
* @return itself
|
||||
*/
|
||||
public RegistrationBuilder addContact(String contact) {
|
||||
addContact(URI.create(contact));
|
||||
|
|
|
@ -32,6 +32,19 @@ public enum RevocationReason {
|
|||
PRIVILEGE_WITHDRAWN(9),
|
||||
AA_COMPROMISE(10);
|
||||
|
||||
private final int reasonCode;
|
||||
|
||||
private RevocationReason(int reasonCode) {
|
||||
this.reasonCode = reasonCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason code as defined in RFC 5280.
|
||||
*/
|
||||
public int getReasonCode() {
|
||||
return reasonCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RevocationReason} that matches the reason code.
|
||||
*
|
||||
|
@ -48,17 +61,4 @@ public enum RevocationReason {
|
|||
return null;
|
||||
}
|
||||
|
||||
private final int reasonCode;
|
||||
|
||||
private RevocationReason(int reasonCode) {
|
||||
this.reasonCode = reasonCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason code as defined in RFC 5280.
|
||||
*/
|
||||
public int getReasonCode() {
|
||||
return reasonCode;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,6 +129,8 @@ public class Session {
|
|||
* Returns the {@link AcmeProvider} that is used for this session.
|
||||
* <p>
|
||||
* The {@link AcmeProvider} instance is lazily created and cached.
|
||||
*
|
||||
* @return {@link AcmeProvider}
|
||||
*/
|
||||
public AcmeProvider provider() {
|
||||
synchronized (this) {
|
||||
|
|
|
@ -50,6 +50,16 @@ public class Challenge extends AcmeResource {
|
|||
|
||||
private JSON data = JSON.empty();
|
||||
|
||||
/**
|
||||
* Creates a new generic {@link Challenge} object.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to bind to.
|
||||
*/
|
||||
public Challenge(Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Challenge} object of an existing challenge.
|
||||
*
|
||||
|
@ -57,7 +67,7 @@ public class Challenge extends AcmeResource {
|
|||
* {@link Session} to be used
|
||||
* @param location
|
||||
* Challenge location
|
||||
* @return {@link Challenge}
|
||||
* @return {@link Challenge} bound to this session and location
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Challenge> T bind(Session session, URI location) throws AcmeException {
|
||||
|
@ -78,16 +88,6 @@ public class Challenge extends AcmeResource {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new generic {@link Challenge} object.
|
||||
*
|
||||
* @param session
|
||||
* {@link Session} to bind to.
|
||||
*/
|
||||
public Challenge(Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the challenge type by name (e.g. "http-01").
|
||||
*/
|
||||
|
@ -142,7 +142,7 @@ public class Challenge extends AcmeResource {
|
|||
* @return {@code true} if acceptable, {@code false} if not
|
||||
*/
|
||||
protected boolean acceptable(String type) {
|
||||
return true;
|
||||
return type != null && !type.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.shredzone.acme4j.challenge;
|
||||
|
||||
import org.shredzone.acme4j.Session;
|
||||
import org.shredzone.acme4j.util.JSONBuilder;
|
||||
|
||||
/**
|
||||
* Implements the {@value TYPE} challenge.
|
||||
|
@ -57,11 +56,6 @@ public class Http01Challenge extends TokenChallenge {
|
|||
return super.getAuthorization();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void respond(JSONBuilder cb) {
|
||||
super.respond(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptable(String type) {
|
||||
return TYPE.equals(type);
|
||||
|
|
|
@ -61,16 +61,32 @@ import org.slf4j.LoggerFactory;
|
|||
public class DefaultConnection implements Connection {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultConnection.class);
|
||||
|
||||
public static final String ACME_ERROR_PREFIX = "urn:ietf:params:acme:error:";
|
||||
|
||||
@Deprecated
|
||||
public static final String ACME_ERROR_PREFIX_DEPRECATED = "urn:acme:error:";
|
||||
public static final String ACME_ERROR_PREFIX = "urn:ietf:params:acme:error:";
|
||||
private static final String ACME_ERROR_PREFIX_DEPRECATED = "urn:acme:error:";
|
||||
|
||||
private static final String ACCEPT_HEADER = "Accept";
|
||||
private static final String ACCEPT_CHARSET_HEADER = "Accept-Charset";
|
||||
private static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
|
||||
private static final String CONTENT_TYPE_HEADER = "Content-Type";
|
||||
private static final String DATE_HEADER = "Date";
|
||||
private static final String LINK_HEADER = "Link";
|
||||
private static final String LOCATION_HEADER = "Location";
|
||||
private static final String REPLAY_NONCE_HEADER = "Replay-Nonce";
|
||||
private static final String RETRY_AFTER_HEADER = "Retry-After";
|
||||
private static final String DEFAULT_CHARSET = "utf-8";
|
||||
|
||||
private static final Pattern BASE64URL_PATTERN = Pattern.compile("[0-9A-Za-z_-]+");
|
||||
|
||||
protected final HttpConnector httpConnector;
|
||||
protected HttpURLConnection conn;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultConnection}.
|
||||
*
|
||||
* @param httpConnector
|
||||
* {@link HttpConnector} to be used for HTTP connections
|
||||
*/
|
||||
public DefaultConnection(HttpConnector httpConnector) {
|
||||
this.httpConnector = Objects.requireNonNull(httpConnector, "httpConnector");
|
||||
}
|
||||
|
@ -86,8 +102,8 @@ public class DefaultConnection implements Connection {
|
|||
try {
|
||||
conn = httpConnector.openConnection(uri);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag());
|
||||
conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET);
|
||||
conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag());
|
||||
conn.setDoOutput(false);
|
||||
|
||||
conn.connect();
|
||||
|
@ -112,7 +128,7 @@ public class DefaultConnection implements Connection {
|
|||
LOG.debug("Getting initial nonce, HEAD {}", uri);
|
||||
conn = httpConnector.openConnection(uri);
|
||||
conn.setRequestMethod("HEAD");
|
||||
conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag());
|
||||
conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag());
|
||||
conn.connect();
|
||||
updateSession(session);
|
||||
conn = null;
|
||||
|
@ -126,10 +142,10 @@ public class DefaultConnection implements Connection {
|
|||
|
||||
conn = httpConnector.openConnection(uri);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag());
|
||||
conn.setRequestProperty("Content-Type", "application/jose+json");
|
||||
conn.setRequestProperty(ACCEPT_HEADER, "application/json");
|
||||
conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET);
|
||||
conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag());
|
||||
conn.setRequestProperty(CONTENT_TYPE_HEADER, "application/jose+json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(keypair.getPublic());
|
||||
|
@ -141,7 +157,7 @@ public class DefaultConnection implements Connection {
|
|||
jws.getHeaders().setJwkHeaderValue("jwk", jwk);
|
||||
jws.setAlgorithmHeaderValue(keyAlgorithm(jwk));
|
||||
jws.setKey(keypair.getPrivate());
|
||||
byte[] outputData = jws.getCompactSerialization().getBytes("utf-8");
|
||||
byte[] outputData = jws.getCompactSerialization().getBytes(DEFAULT_CHARSET);
|
||||
|
||||
conn.setFixedLengthStreamingMode(outputData.length);
|
||||
conn.connect();
|
||||
|
@ -172,7 +188,7 @@ public class DefaultConnection implements Connection {
|
|||
}
|
||||
}
|
||||
|
||||
if (!"application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
|
||||
if (!"application/problem+json".equals(conn.getHeaderField(CONTENT_TYPE_HEADER))) {
|
||||
throw new AcmeException("HTTP " + rc + ": " + conn.getResponseMessage());
|
||||
}
|
||||
|
||||
|
@ -187,7 +203,7 @@ public class DefaultConnection implements Connection {
|
|||
public JSON readJsonResponse() throws AcmeException {
|
||||
assertConnectionIsOpen();
|
||||
|
||||
String contentType = conn.getHeaderField("Content-Type");
|
||||
String contentType = conn.getHeaderField(CONTENT_TYPE_HEADER);
|
||||
if (!("application/json".equals(contentType)
|
||||
|| "application/problem+json".equals(contentType))) {
|
||||
throw new AcmeProtocolException("Unexpected content type: " + contentType);
|
||||
|
@ -214,7 +230,7 @@ public class DefaultConnection implements Connection {
|
|||
public X509Certificate readCertificate() throws AcmeException {
|
||||
assertConnectionIsOpen();
|
||||
|
||||
String contentType = conn.getHeaderField("Content-Type");
|
||||
String contentType = conn.getHeaderField(CONTENT_TYPE_HEADER);
|
||||
if (!("application/pkix-cert".equals(contentType))) {
|
||||
throw new AcmeProtocolException("Unexpected content type: " + contentType);
|
||||
}
|
||||
|
@ -249,7 +265,7 @@ public class DefaultConnection implements Connection {
|
|||
public void updateSession(Session session) {
|
||||
assertConnectionIsOpen();
|
||||
|
||||
String nonceHeader = conn.getHeaderField("Replay-Nonce");
|
||||
String nonceHeader = conn.getHeaderField(REPLAY_NONCE_HEADER);
|
||||
if (nonceHeader == null || nonceHeader.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -267,7 +283,7 @@ public class DefaultConnection implements Connection {
|
|||
public URI getLocation() {
|
||||
assertConnectionIsOpen();
|
||||
|
||||
String location = conn.getHeaderField("Location");
|
||||
String location = conn.getHeaderField(LOCATION_HEADER);
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -296,7 +312,7 @@ public class DefaultConnection implements Connection {
|
|||
|
||||
List<URI> result = new ArrayList<>();
|
||||
|
||||
List<String> links = conn.getHeaderFields().get("Link");
|
||||
List<String> links = conn.getHeaderFields().get(LINK_HEADER);
|
||||
if (links != null) {
|
||||
Pattern p = Pattern.compile("<(.*?)>\\s*;\\s*rel=\"?"+ Pattern.quote(relation) + "\"?");
|
||||
for (String link : links) {
|
||||
|
@ -322,7 +338,7 @@ public class DefaultConnection implements Connection {
|
|||
*/
|
||||
private Date getRetryAfterHeader() {
|
||||
// See RFC 2616 section 14.37
|
||||
String header = conn.getHeaderField("Retry-After");
|
||||
String header = conn.getHeaderField(RETRY_AFTER_HEADER);
|
||||
if (header == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -331,12 +347,12 @@ public class DefaultConnection implements Connection {
|
|||
// delta-seconds
|
||||
if (header.matches("^\\d+$")) {
|
||||
int delta = Integer.parseInt(header);
|
||||
long date = conn.getHeaderFieldDate("Date", System.currentTimeMillis());
|
||||
long date = conn.getHeaderFieldDate(DATE_HEADER, System.currentTimeMillis());
|
||||
return new Date(date + delta * 1000L);
|
||||
}
|
||||
|
||||
// HTTP-date
|
||||
long date = conn.getHeaderFieldDate("Retry-After", 0L);
|
||||
long date = conn.getHeaderFieldDate(RETRY_AFTER_HEADER, 0L);
|
||||
return date != 0 ? new Date(date) : null;
|
||||
} catch (Exception ex) {
|
||||
throw new AcmeProtocolException("Bad retry-after header value: " + header, ex);
|
||||
|
|
|
@ -51,6 +51,8 @@ public class HttpConnector {
|
|||
|
||||
/**
|
||||
* Returns the default User-Agent to be used.
|
||||
*
|
||||
* @return User-Agent
|
||||
*/
|
||||
public static String defaultUserAgent() {
|
||||
return USER_AGENT;
|
||||
|
|
|
@ -24,23 +24,6 @@ public enum Resource {
|
|||
NEW_CERT("new-cert"),
|
||||
REVOKE_CERT("revoke-cert");
|
||||
|
||||
/**
|
||||
* Parses the string and returns a matching {@link Resource} instance.
|
||||
*
|
||||
* @param str
|
||||
* String to parse
|
||||
* @return {@link Resource} instance, or {@code null} if the resource is unknown
|
||||
*/
|
||||
public static Resource parse(String str) {
|
||||
for (Resource r : values()) {
|
||||
if (r.path().equals(str)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String path;
|
||||
|
||||
private Resource(String path) {
|
||||
|
@ -49,6 +32,8 @@ public enum Resource {
|
|||
|
||||
/**
|
||||
* Returns the resource path.
|
||||
*
|
||||
* @return resource path
|
||||
*/
|
||||
public String path() {
|
||||
return path;
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.util.ArrayDeque;
|
|||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.shredzone.acme4j.AcmeResource;
|
||||
import org.shredzone.acme4j.Session;
|
||||
|
@ -27,8 +28,11 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
|||
import org.shredzone.acme4j.util.JSON;
|
||||
|
||||
/**
|
||||
* An {@link Iterator} that fetches a batch of URIs from the ACME server, and
|
||||
* generates {@link AcmeResource} instances.
|
||||
* An {@link Iterator} that fetches a batch of URIs from the ACME server, and generates
|
||||
* {@link AcmeResource} instances.
|
||||
*
|
||||
* @param <T>
|
||||
* {@link AcmeResource} type to iterate over
|
||||
*/
|
||||
public abstract class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
|
||||
|
||||
|
@ -49,8 +53,8 @@ public abstract class ResourceIterator<T extends AcmeResource> implements Iterat
|
|||
* URI of the first JSON array, may be {@code null} for an empty iterator
|
||||
*/
|
||||
public ResourceIterator(Session session, String field, URI start) {
|
||||
this.session = session;
|
||||
this.field = field;
|
||||
this.session = Objects.requireNonNull(session, "session");
|
||||
this.field = Objects.requireNonNull(field, "field");
|
||||
this.nextUri = start;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.shredzone.acme4j.exception;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An exception that is thrown when there is a conflict with the request. For example,
|
||||
|
@ -24,9 +25,17 @@ public class AcmeConflictException extends AcmeException {
|
|||
|
||||
private final URI location;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AcmeConflictException}.
|
||||
*
|
||||
* @param msg
|
||||
* Details about the conflicting resource
|
||||
* @param location
|
||||
* {@link URI} of the conflicting resource
|
||||
*/
|
||||
public AcmeConflictException(String msg, URI location) {
|
||||
super(msg);
|
||||
this.location = location;
|
||||
this.location = Objects.requireNonNull(location, "location");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,14 +19,31 @@ package org.shredzone.acme4j.exception;
|
|||
public class AcmeException extends Exception {
|
||||
private static final long serialVersionUID = -2935088954705632025L;
|
||||
|
||||
/**
|
||||
* Creates a generic {@link AcmeException}.
|
||||
*/
|
||||
public AcmeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic {@link AcmeException}.
|
||||
*
|
||||
* @param msg
|
||||
* Description
|
||||
*/
|
||||
public AcmeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic {@link AcmeException}.
|
||||
*
|
||||
* @param msg
|
||||
* Description
|
||||
* @param cause
|
||||
* {@link Throwable} that caused this exception
|
||||
*/
|
||||
public AcmeException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.shredzone.acme4j.exception;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a server side process has not been completed yet, and the
|
||||
|
@ -24,16 +25,24 @@ public class AcmeRetryAfterException extends AcmeException {
|
|||
|
||||
private final Date retryAfter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AcmeRetryAfterException}.
|
||||
*
|
||||
* @param msg
|
||||
* Error details
|
||||
* @param retryAfter
|
||||
* retry-after date returned by the server
|
||||
*/
|
||||
public AcmeRetryAfterException(String msg, Date retryAfter) {
|
||||
super(msg);
|
||||
this.retryAfter = retryAfter;
|
||||
this.retryAfter = Objects.requireNonNull(retryAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the retry-after date returned by the server.
|
||||
*/
|
||||
public Date getRetryAfter() {
|
||||
return retryAfter != null ? new Date(retryAfter.getTime()) : null;
|
||||
return new Date(retryAfter.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.shredzone.acme4j.connector.DefaultConnection;
|
|||
public class AcmeServerException extends AcmeException {
|
||||
private static final long serialVersionUID = 5971622508467042792L;
|
||||
|
||||
private static final String ACME_ERROR_PREFIX_DEPRECATED = "urn:acme:error:";
|
||||
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
|
@ -54,12 +56,11 @@ public class AcmeServerException extends AcmeException {
|
|||
* @return ACME error type, or {@code null} if this is not an
|
||||
* {@code "urn:ietf:params:acme:error"}
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public String getAcmeErrorType() {
|
||||
if (type.startsWith(DefaultConnection.ACME_ERROR_PREFIX)) {
|
||||
return type.substring(DefaultConnection.ACME_ERROR_PREFIX.length());
|
||||
} else if (type.startsWith(DefaultConnection.ACME_ERROR_PREFIX_DEPRECATED)) {
|
||||
return type.substring(DefaultConnection.ACME_ERROR_PREFIX_DEPRECATED.length());
|
||||
} else if (type.startsWith(ACME_ERROR_PREFIX_DEPRECATED)) {
|
||||
return type.substring(ACME_ERROR_PREFIX_DEPRECATED.length());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.provider;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.shredzone.acme4j.Session;
|
||||
|
@ -38,6 +43,8 @@ import org.shredzone.acme4j.util.JSON;
|
|||
*/
|
||||
public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||
|
||||
private static final Map<String, Constructor<? extends Challenge>> CHALLENGES = challengeMap();
|
||||
|
||||
@Override
|
||||
public Connection connect() {
|
||||
return new DefaultConnection(createHttpConnector());
|
||||
|
@ -56,22 +63,48 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation") // must still provide deprecated challenges
|
||||
private static Map<String, Constructor<? extends Challenge>> challengeMap() {
|
||||
Map<String, Constructor<? extends Challenge>> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
map.put(Dns01Challenge.TYPE,
|
||||
Dns01Challenge.class.getConstructor(Session.class));
|
||||
|
||||
map.put(org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE,
|
||||
org.shredzone.acme4j.challenge.TlsSni01Challenge.class.getConstructor(Session.class));
|
||||
|
||||
map.put(TlsSni02Challenge.TYPE,
|
||||
TlsSni02Challenge.class.getConstructor(Session.class));
|
||||
|
||||
map.put(Http01Challenge.TYPE,
|
||||
Http01Challenge.class.getConstructor(Session.class));
|
||||
|
||||
map.put(OutOfBand01Challenge.TYPE,
|
||||
OutOfBand01Challenge.class.getConstructor(Session.class));
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Could not find Challenge constructor", ex);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Challenge createChallenge(Session session, String type) {
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(type, "type");
|
||||
if (type.isEmpty()) {
|
||||
throw new IllegalArgumentException("no type given");
|
||||
|
||||
Constructor<? extends Challenge> constructor = CHALLENGES.get(type);
|
||||
if (constructor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Dns01Challenge.TYPE: return new Dns01Challenge(session);
|
||||
case org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE: return new org.shredzone.acme4j.challenge.TlsSni01Challenge(session);
|
||||
case TlsSni02Challenge.TYPE: return new TlsSni02Challenge(session);
|
||||
case Http01Challenge.TYPE: return new Http01Challenge(session);
|
||||
case OutOfBand01Challenge.TYPE: return new OutOfBand01Challenge(session);
|
||||
default: return null;
|
||||
try {
|
||||
return constructor.newInstance(session);
|
||||
} catch (InvocationTargetException | IllegalAccessException
|
||||
| InstantiationException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Could not instantiate a Challenge for type " + type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ public interface AcmeProvider {
|
|||
Connection connect();
|
||||
|
||||
/**
|
||||
* Returns the provider's directory. The map must contain resource URIs, and may
|
||||
* Returns the provider's directory. The structure must contain resource URIs, and may
|
||||
* optionally contain metadata.
|
||||
* <p>
|
||||
* The default implementation resolves the server URI and fetches the directory via
|
||||
|
|
|
@ -113,13 +113,17 @@ public final class JSON implements Serializable {
|
|||
|
||||
/**
|
||||
* Returns a {@link JSON} of an empty document.
|
||||
*
|
||||
* @return Empty {@link JSON}
|
||||
*/
|
||||
public static JSON empty() {
|
||||
return EMPTY_JSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Set} of all keys of this object.
|
||||
* Returns a set of all keys of this object.
|
||||
*
|
||||
* @return {@link Set} of keys
|
||||
*/
|
||||
public Set<String> keySet() {
|
||||
return Collections.unmodifiableSet(data.keySet());
|
||||
|
@ -199,6 +203,8 @@ public final class JSON implements Serializable {
|
|||
|
||||
/**
|
||||
* Returns the array size.
|
||||
*
|
||||
* @return Size of the array
|
||||
*/
|
||||
public int size() {
|
||||
return data.size();
|
||||
|
@ -245,8 +251,10 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is present. An {@link AcmeProtocolException} is thrown
|
||||
* if the value is {@code null}.
|
||||
* Checks if the value is present. An {@link AcmeProtocolException} is thrown if
|
||||
* the value is {@code null}.
|
||||
*
|
||||
* @return itself
|
||||
*/
|
||||
public Value required() {
|
||||
if (val == null) {
|
||||
|
@ -256,14 +264,18 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@link String}. May be {@code null}.
|
||||
* Returns the value as {@link String}.
|
||||
*
|
||||
* @return {@link String}, or {@code null} if the value was not set.
|
||||
*/
|
||||
public String asString() {
|
||||
return val != null ? val.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@link JSON} object. May be {@code null}.
|
||||
* Returns the value as JSON object.
|
||||
*
|
||||
* @return {@link JSON}, or {@code null} if the value was not set.
|
||||
*/
|
||||
public JSON asObject() {
|
||||
if (val == null) {
|
||||
|
@ -278,7 +290,9 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the value as JSON {@link Array}. May be {@code null}.
|
||||
* Returns the value as JSON array.
|
||||
*
|
||||
* @return {@link JSON.Array}, or {@code null} if the value was not set.
|
||||
*/
|
||||
public Array asArray() {
|
||||
if (val == null) {
|
||||
|
@ -294,6 +308,8 @@ public final class JSON implements Serializable {
|
|||
|
||||
/**
|
||||
* Returns the value as int.
|
||||
*
|
||||
* @return integer value
|
||||
*/
|
||||
public int asInt() {
|
||||
required();
|
||||
|
@ -306,7 +322,9 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@link URI}. May be {@code null}.
|
||||
* Returns the value as {@link URI}.
|
||||
*
|
||||
* @return {@link URI}, or {@code null} if the value was not set.
|
||||
*/
|
||||
public URI asURI() {
|
||||
if (val == null) {
|
||||
|
@ -321,7 +339,9 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@link URL}. May be {@code null}.
|
||||
* Returns the value as {@link URL}.
|
||||
*
|
||||
* @return {@link URL}, or {@code null} if the value was not set.
|
||||
*/
|
||||
public URL asURL() {
|
||||
if (val == null) {
|
||||
|
@ -336,9 +356,10 @@ public final class JSON implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the value as {@link Date}. May be {@code null}. The returned
|
||||
* {@link Date} object is not shared, changes are not reflected in the JSON
|
||||
* object.
|
||||
* Returns the value as {@link Date}.
|
||||
*
|
||||
* @return {@link Date}, or {@code null} if the value was not set. The returned
|
||||
* {@link Date} object is not shared and can be modified safely.
|
||||
*/
|
||||
public Date asDate() {
|
||||
if (val == null) {
|
||||
|
|
|
@ -170,6 +170,8 @@ public class JSONBuilder {
|
|||
|
||||
/**
|
||||
* Returns a {@link Map} representation of the current state.
|
||||
*
|
||||
* @return {@link Map} of the current state
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
return Collections.unmodifiableMap(data);
|
||||
|
@ -177,6 +179,8 @@ public class JSONBuilder {
|
|||
|
||||
/**
|
||||
* Returns a {@link JSON} representation of the current state.
|
||||
*
|
||||
* @return {@link JSON} of the current state
|
||||
*/
|
||||
public JSON toJSON() {
|
||||
return JSON.parse(toString());
|
||||
|
|
|
@ -216,7 +216,7 @@ public class SessionTest {
|
|||
assertThat(meta, not(nullValue()));
|
||||
assertThat(meta.getTermsOfService(), is(nullValue()));
|
||||
assertThat(meta.getWebsite(), is(nullValue()));
|
||||
assertThat(meta.getCaaIdentities(), is(nullValue()));
|
||||
assertThat(meta.getCaaIdentities(), is(empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.connector;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
@ -38,19 +38,4 @@ public class ResourceTest {
|
|||
assertThat(Resource.values().length, is(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that invoking {@link Resource#parse(String)} with a {@link Resource#path()}
|
||||
* gives the same {@link Resource}.
|
||||
*/
|
||||
@Test
|
||||
public void testParse() {
|
||||
for (Resource r : Resource.values()) {
|
||||
Resource parsed = Resource.parse(r.path());
|
||||
assertThat(parsed, is(r));
|
||||
}
|
||||
|
||||
// unknown paths return null
|
||||
assertThat(Resource.parse("foo"), is(nullValue()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ public class AcmeRetryAfterExceptionTest {
|
|||
|
||||
assertThat(ex.getMessage(), is(detail));
|
||||
assertThat(ex.getRetryAfter(), is(retryAfter));
|
||||
|
||||
// make sure we get a copy of the Date object
|
||||
assertThat(ex.getRetryAfter(), not(sameInstance(retryAfter)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,11 +48,24 @@ public class AcmeRetryAfterExceptionTest {
|
|||
*/
|
||||
@Test
|
||||
public void testNullAcmeRetryAfterException() {
|
||||
Date retryAfter = new Date(System.currentTimeMillis() + 60 * 1000L);
|
||||
|
||||
AcmeRetryAfterException ex
|
||||
= new AcmeRetryAfterException(null, null);
|
||||
= new AcmeRetryAfterException(null, retryAfter);
|
||||
|
||||
assertThat(ex.getMessage(), nullValue());
|
||||
assertThat(ex.getRetryAfter(), nullValue());
|
||||
assertThat(ex.getRetryAfter(), is(retryAfter));
|
||||
|
||||
// make sure we get a copy of the Date object
|
||||
assertThat(ex.getRetryAfter(), not(sameInstance(retryAfter)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that date is required.
|
||||
*/
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testRequiredAcmeRetryAfterException() {
|
||||
new AcmeRetryAfterException("null-test", null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -162,19 +162,15 @@ public class AbstractAcmeProviderTest {
|
|||
assertThat(c7, not(nullValue()));
|
||||
assertThat(c7, instanceOf(OutOfBand01Challenge.class));
|
||||
|
||||
Challenge c8 = provider.createChallenge(session, "");
|
||||
assertThat(c8, is(nullValue()));
|
||||
|
||||
try {
|
||||
provider.createChallenge(session, (String) null);
|
||||
fail("null was accepted");
|
||||
} catch (NullPointerException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
provider.createChallenge(session, "");
|
||||
fail("empty string was accepted");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -59,12 +59,15 @@ public class CSRBuilder {
|
|||
|
||||
/**
|
||||
* Adds a domain name to the CSR. The first domain name added will also be the
|
||||
* <em>Common Name</em>. All domain names will be added as <em>Subject
|
||||
* Alternative Name</em>.
|
||||
* <em>Common Name</em>. All domain names will be added as <em>Subject Alternative
|
||||
* Name</em>.
|
||||
* <p>
|
||||
* IDN domain names are ACE encoded automatically.
|
||||
* <p>
|
||||
* Note that ACME servers may not accept wildcard domains!
|
||||
*
|
||||
* @param domain
|
||||
* Domain name to add
|
||||
*/
|
||||
public void addDomain(String domain) {
|
||||
String ace = toAce(domain);
|
||||
|
@ -78,6 +81,9 @@ public class CSRBuilder {
|
|||
* Adds a {@link Collection} of domains.
|
||||
* <p>
|
||||
* IDN domain names are ACE encoded automatically.
|
||||
*
|
||||
* @param domains
|
||||
* Collection of domain names to add
|
||||
*/
|
||||
public void addDomains(Collection<String> domains) {
|
||||
for (String domain : domains) {
|
||||
|
@ -89,6 +95,9 @@ public class CSRBuilder {
|
|||
* Adds multiple domain names.
|
||||
* <p>
|
||||
* IDN domain names are ACE encoded automatically.
|
||||
*
|
||||
* @param domains
|
||||
* Domain names to add
|
||||
*/
|
||||
public void addDomains(String... domains) {
|
||||
for (String domain : domains) {
|
||||
|
@ -229,7 +238,7 @@ public class CSRBuilder {
|
|||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(namebuilder.build());
|
||||
for (String domain : namelist) {
|
||||
sb.append(",DNS=").append(domain.toString());
|
||||
sb.append(",DNS=").append(domain);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ public final class CertificateUtils {
|
|||
*/
|
||||
public static void writeX509Certificate(X509Certificate cert, Writer w) throws IOException {
|
||||
try (JcaPEMWriter jw = new JcaPEMWriter(w)) {
|
||||
jw.writeObject(cert);
|
||||
writeCertIfNotNull(jw, cert);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,19 +110,27 @@ public final class CertificateUtils {
|
|||
public static void writeX509CertificateChain(Writer w, X509Certificate cert, X509Certificate... chain)
|
||||
throws IOException {
|
||||
try (JcaPEMWriter jw = new JcaPEMWriter(w)) {
|
||||
if (cert != null) {
|
||||
jw.writeObject(cert);
|
||||
}
|
||||
if (chain != null) {
|
||||
for (X509Certificate c : chain) {
|
||||
if (c != null) {
|
||||
jw.writeObject(c);
|
||||
}
|
||||
}
|
||||
writeCertIfNotNull(jw, cert);
|
||||
for (X509Certificate c : chain) {
|
||||
writeCertIfNotNull(jw, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an {@link X509Certificate} unless it is {@code null}.
|
||||
*
|
||||
* @param jw
|
||||
* {@link JcaPEMWriter} to write to
|
||||
* @param cert
|
||||
* {@link X509Certificate} to write, or {@code null}
|
||||
*/
|
||||
private static void writeCertIfNotNull(JcaPEMWriter jw, X509Certificate cert) throws IOException {
|
||||
if (cert != null) {
|
||||
jw.writeObject(cert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an X.509 certificate chain PEM file.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue