mirror of https://github.com/shred/acme4j
Merge utility classes
parent
0a288fa290
commit
25b00313b2
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
package org.shredzone.acme4j;
|
||||
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
|
@ -28,7 +30,6 @@ import org.shredzone.acme4j.exception.AcmeException;
|
|||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.TimestampParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -236,7 +237,7 @@ public class Authorization extends AcmeResource {
|
|||
|
||||
String jsonExpires = (String) json.get("expires");
|
||||
if (jsonExpires != null) {
|
||||
expires = TimestampParser.parse(jsonExpires);
|
||||
expires = parseTimestamp(jsonExpires);
|
||||
}
|
||||
|
||||
Map<String, Object> jsonIdentifier = (Map<String, Object>) json.get("identifier");
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
package org.shredzone.acme4j;
|
||||
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.*;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -38,8 +40,6 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
|||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||
import org.shredzone.acme4j.util.AcmeUtils;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.DomainUtils;
|
||||
import org.shredzone.acme4j.util.SignatureUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -188,7 +188,7 @@ public class Registration extends AcmeResource {
|
|||
claims.putResource(Resource.NEW_AUTHZ);
|
||||
claims.object("identifier")
|
||||
.put("type", "dns")
|
||||
.put("value", DomainUtils.toAce(domain));
|
||||
.put("value", toAce(domain));
|
||||
|
||||
conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_AUTHZ), claims, getSession());
|
||||
conn.accept(HttpURLConnection.HTTP_CREATED);
|
||||
|
@ -289,7 +289,7 @@ public class Registration extends AcmeResource {
|
|||
innerJws.setPayload(payloadClaim.toString());
|
||||
innerJws.getHeaders().setObjectHeaderValue("url", keyChangeUri);
|
||||
innerJws.getHeaders().setJwkHeaderValue("jwk", newKeyJwk);
|
||||
innerJws.setAlgorithmHeaderValue(SignatureUtils.keyAlgorithm(newKeyJwk));
|
||||
innerJws.setAlgorithmHeaderValue(keyAlgorithm(newKeyJwk));
|
||||
innerJws.setKey(newKeyPair.getPrivate());
|
||||
innerJws.sign();
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.challenge;
|
||||
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
@ -35,7 +37,6 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
|||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||
import org.shredzone.acme4j.util.AcmeUtils;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.TimestampParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -130,7 +131,7 @@ public class Challenge extends AcmeResource {
|
|||
public Date getValidated() {
|
||||
String valStr = get(KEY_VALIDATED);
|
||||
if (valStr != null) {
|
||||
return TimestampParser.parse(valStr);
|
||||
return parseTimestamp(valStr);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.connector;
|
||||
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.keyAlgorithm;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -51,7 +53,6 @@ import org.shredzone.acme4j.exception.AcmeServerException;
|
|||
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
||||
import org.shredzone.acme4j.util.AcmeUtils;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.SignatureUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -140,7 +141,7 @@ public class DefaultConnection implements Connection {
|
|||
jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce()));
|
||||
jws.getHeaders().setObjectHeaderValue("url", uri);
|
||||
jws.getHeaders().setJwkHeaderValue("jwk", jwk);
|
||||
jws.setAlgorithmHeaderValue(SignatureUtils.keyAlgorithm(jwk));
|
||||
jws.setAlgorithmHeaderValue(keyAlgorithm(jwk));
|
||||
jws.setKey(keypair.getPrivate());
|
||||
byte[] outputData = jws.getCompactSerialization().getBytes("utf-8");
|
||||
|
||||
|
|
|
@ -14,10 +14,22 @@
|
|||
package org.shredzone.acme4j.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.IDN;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jose4j.base64url.Base64Url;
|
||||
import org.jose4j.jwk.EllipticCurveJsonWebKey;
|
||||
import org.jose4j.jwk.JsonWebKey;
|
||||
import org.jose4j.jwk.RsaJsonWebKey;
|
||||
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||
import org.jose4j.jws.JsonWebSignature;
|
||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||
|
||||
/**
|
||||
|
@ -29,6 +41,15 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
|||
public final class AcmeUtils {
|
||||
private static final char[] HEX = "0123456789abcdef".toCharArray();
|
||||
|
||||
private static final Pattern DATE_PATTERN = Pattern.compile(
|
||||
"^(\\d{4})-(\\d{2})-(\\d{2})T"
|
||||
+ "(\\d{2}):(\\d{2}):(\\d{2})"
|
||||
+ "(?:\\.(\\d{1,3})\\d*)?"
|
||||
+ "(Z|[+-]\\d{2}:?\\d{2})$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final Pattern TZ_PATTERN = Pattern.compile(
|
||||
"([+-])(\\d{2}):?(\\d{2})$");
|
||||
|
||||
private AcmeUtils() {
|
||||
// Utility class without constructor
|
||||
}
|
||||
|
@ -93,4 +114,108 @@ public final class AcmeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ASCII encodes a domain name.
|
||||
* <p>
|
||||
* The conversion is done as described in
|
||||
* <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>. Additionally, all
|
||||
* leading and trailing white spaces are trimmed, and the result is lowercased.
|
||||
* <p>
|
||||
* It is safe to pass in ACE encoded domains, they will be returned unchanged.
|
||||
*
|
||||
* @param domain
|
||||
* Domain name to encode
|
||||
* @return Encoded domain name, white space trimmed and lower cased. {@code null} if
|
||||
* {@code null} was passed in.
|
||||
*/
|
||||
public static String toAce(String domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
return IDN.toASCII(domain.trim()).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the key used in the {@link JsonWebKey}, and returns the key algorithm
|
||||
* identifier for {@link JsonWebSignature}.
|
||||
*
|
||||
* @param jwk
|
||||
* {@link JsonWebKey} to analyze
|
||||
* @return algorithm identifier
|
||||
* @throws IllegalArgumentException
|
||||
* there is no corresponding algorithm identifier for the key
|
||||
*/
|
||||
public static String keyAlgorithm(JsonWebKey jwk) {
|
||||
if (jwk instanceof EllipticCurveJsonWebKey) {
|
||||
EllipticCurveJsonWebKey ecjwk = (EllipticCurveJsonWebKey) jwk;
|
||||
|
||||
switch (ecjwk.getCurveName()) {
|
||||
case "P-256":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256;
|
||||
|
||||
case "P-384":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384;
|
||||
|
||||
case "P-521":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown EC name "
|
||||
+ ecjwk.getCurveName());
|
||||
}
|
||||
|
||||
} else if (jwk instanceof RsaJsonWebKey) {
|
||||
return AlgorithmIdentifiers.RSA_USING_SHA256;
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown algorithm " + jwk.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a RFC 3339 formatted date.
|
||||
*
|
||||
* @param str
|
||||
* Date string
|
||||
* @return {@link Date} that was parsed
|
||||
* @throws IllegalArgumentException
|
||||
* if the date string was not RFC 3339 formatted
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc3339.txt">RFC 3339</a>
|
||||
*/
|
||||
public static Date parseTimestamp(String str) {
|
||||
Matcher m = DATE_PATTERN.matcher(str);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Illegal date: " + str);
|
||||
}
|
||||
|
||||
int year = Integer.parseInt(m.group(1));
|
||||
int month = Integer.parseInt(m.group(2));
|
||||
int dom = Integer.parseInt(m.group(3));
|
||||
int hour = Integer.parseInt(m.group(4));
|
||||
int minute = Integer.parseInt(m.group(5));
|
||||
int second = Integer.parseInt(m.group(6));
|
||||
|
||||
StringBuilder msStr = new StringBuilder();
|
||||
if (m.group(7) != null) {
|
||||
msStr.append(m.group(7));
|
||||
}
|
||||
while (msStr.length() < 3) {
|
||||
msStr.append('0');
|
||||
}
|
||||
int ms = Integer.parseInt(msStr.toString());
|
||||
|
||||
String tz = m.group(8);
|
||||
if ("Z".equalsIgnoreCase(tz)) {
|
||||
tz = "GMT";
|
||||
} else {
|
||||
tz = TZ_PATTERN.matcher(tz).replaceAll("GMT$1$2:$3");
|
||||
}
|
||||
|
||||
Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone(tz));
|
||||
cal.clear();
|
||||
cal.set(year, month - 1, dom, hour, minute, second);
|
||||
cal.set(Calendar.MILLISECOND, ms);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2016 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import java.net.IDN;
|
||||
|
||||
/**
|
||||
* Utility class for domains and domain names.
|
||||
*/
|
||||
public final class DomainUtils {
|
||||
|
||||
private DomainUtils() {
|
||||
// Utility class without constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* ASCII encodes a domain name.
|
||||
* <p>
|
||||
* The conversion is done as described in
|
||||
* <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>. Additionally, all
|
||||
* leading and trailing white spaces are trimmed, and the result is lowercased.
|
||||
* <p>
|
||||
* It is safe to pass in ACE encoded domains, they will be returned unchanged.
|
||||
*
|
||||
* @param domain
|
||||
* Domain name to encode
|
||||
* @return Encoded domain name, white space trimmed and lower cased. {@code null} if
|
||||
* {@code null} was passed in.
|
||||
*/
|
||||
public static String toAce(String domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
return IDN.toASCII(domain.trim()).toLowerCase();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2016 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import org.jose4j.jwk.EllipticCurveJsonWebKey;
|
||||
import org.jose4j.jwk.JsonWebKey;
|
||||
import org.jose4j.jwk.RsaJsonWebKey;
|
||||
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||
import org.jose4j.jws.JsonWebSignature;
|
||||
|
||||
/**
|
||||
* Utility class for signatures.
|
||||
*/
|
||||
public final class SignatureUtils {
|
||||
|
||||
private SignatureUtils() {
|
||||
// Utility class without constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the key used in the {@link JsonWebKey}, and returns the key algorithm
|
||||
* identifier for {@link JsonWebSignature}.
|
||||
*
|
||||
* @param jwk
|
||||
* {@link JsonWebKey} to analyze
|
||||
* @return algorithm identifier
|
||||
* @throws IllegalArgumentException
|
||||
* there is no corresponding algorithm identifier for the key
|
||||
*/
|
||||
public static String keyAlgorithm(JsonWebKey jwk) {
|
||||
if (jwk instanceof EllipticCurveJsonWebKey) {
|
||||
EllipticCurveJsonWebKey ecjwk = (EllipticCurveJsonWebKey) jwk;
|
||||
|
||||
switch (ecjwk.getCurveName()) {
|
||||
case "P-256":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256;
|
||||
|
||||
case "P-384":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384;
|
||||
|
||||
case "P-521":
|
||||
return AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown EC name "
|
||||
+ ecjwk.getCurveName());
|
||||
}
|
||||
|
||||
} else if (jwk instanceof RsaJsonWebKey) {
|
||||
return AlgorithmIdentifiers.RSA_USING_SHA256;
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown algorithm " + jwk.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2015 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parses a timestamp as defined in RFC 3339.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc3339.txt">RFC 3339</a>
|
||||
*/
|
||||
public final class TimestampParser {
|
||||
|
||||
private static final Pattern DATE_PATTERN = Pattern.compile(
|
||||
"^(\\d{4})-(\\d{2})-(\\d{2})T"
|
||||
+ "(\\d{2}):(\\d{2}):(\\d{2})"
|
||||
+ "(?:\\.(\\d{1,3})\\d*)?"
|
||||
+ "(Z|[+-]\\d{2}:?\\d{2})$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final Pattern TZ_PATTERN = Pattern.compile(
|
||||
"([+-])(\\d{2}):?(\\d{2})$");
|
||||
|
||||
private TimestampParser() {
|
||||
// Utility class without constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a RFC 3339 formatted date.
|
||||
*
|
||||
* @param str
|
||||
* Date string
|
||||
* @return {@link Date} that was parsed
|
||||
* @throws IllegalArgumentException
|
||||
* if the date string was not RFC 3339 formatted
|
||||
*/
|
||||
public static Date parse(String str) {
|
||||
Matcher m = DATE_PATTERN.matcher(str);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Illegal date: " + str);
|
||||
}
|
||||
|
||||
int year = Integer.parseInt(m.group(1));
|
||||
int month = Integer.parseInt(m.group(2));
|
||||
int dom = Integer.parseInt(m.group(3));
|
||||
int hour = Integer.parseInt(m.group(4));
|
||||
int minute = Integer.parseInt(m.group(5));
|
||||
int second = Integer.parseInt(m.group(6));
|
||||
|
||||
StringBuilder msStr = new StringBuilder();
|
||||
if (m.group(7) != null) {
|
||||
msStr.append(m.group(7));
|
||||
}
|
||||
while (msStr.length() < 3) {
|
||||
msStr.append('0');
|
||||
}
|
||||
int ms = Integer.parseInt(msStr.toString());
|
||||
|
||||
String tz = m.group(8);
|
||||
if ("Z".equalsIgnoreCase(tz)) {
|
||||
tz = "GMT";
|
||||
} else {
|
||||
tz = TZ_PATTERN.matcher(tz).replaceAll("GMT$1$2:$3");
|
||||
}
|
||||
|
||||
Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone(tz));
|
||||
cal.clear();
|
||||
cal.set(year, month - 1, dom, hour, minute, second);
|
||||
cal.set(Calendar.MILLISECOND, ms);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@ package org.shredzone.acme4j;
|
|||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||
import static org.shredzone.acme4j.util.TestUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -34,7 +35,6 @@ import org.shredzone.acme4j.exception.AcmeException;
|
|||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.TimestampParser;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Authorization}.
|
||||
|
@ -149,7 +149,7 @@ public class AuthorizationTest {
|
|||
|
||||
assertThat(auth.getDomain(), is("example.org"));
|
||||
assertThat(auth.getStatus(), is(Status.VALID));
|
||||
assertThat(auth.getExpires(), is(TimestampParser.parse("2016-01-02T17:12:40Z")));
|
||||
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
|
||||
assertThat(auth.getLocation(), is(locationUri));
|
||||
|
||||
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||
|
@ -207,7 +207,7 @@ public class AuthorizationTest {
|
|||
requestWasSent.set(false);
|
||||
assertThat(auth.getDomain(), is("example.org"));
|
||||
assertThat(auth.getStatus(), is(Status.VALID));
|
||||
assertThat(auth.getExpires(), is(TimestampParser.parse("2016-01-02T17:12:40Z")));
|
||||
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
|
||||
assertThat(requestWasSent.get(), is(false));
|
||||
|
||||
provider.close();
|
||||
|
@ -262,7 +262,7 @@ public class AuthorizationTest {
|
|||
|
||||
assertThat(auth.getDomain(), is("example.org"));
|
||||
assertThat(auth.getStatus(), is(Status.VALID));
|
||||
assertThat(auth.getExpires(), is(TimestampParser.parse("2016-01-02T17:12:40Z")));
|
||||
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
|
||||
assertThat(auth.getLocation(), is(locationUri));
|
||||
|
||||
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.shredzone.acme4j.challenge;
|
|||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||
import static org.shredzone.acme4j.util.TestUtils.*;
|
||||
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
||||
|
||||
|
@ -43,7 +44,6 @@ import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
|||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||
import org.shredzone.acme4j.util.TestUtils;
|
||||
import org.shredzone.acme4j.util.TimestampParser;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Challenge}.
|
||||
|
@ -116,7 +116,7 @@ public class ChallengeTest {
|
|||
assertThat(challenge.getType(), is("generic-01"));
|
||||
assertThat(challenge.getStatus(), is(Status.VALID));
|
||||
assertThat(challenge.getLocation(), is(new URI("http://example.com/challenge/123")));
|
||||
assertThat(challenge.getValidated(), is(TimestampParser.parse("2015-12-12T17:19:36.336785823Z")));
|
||||
assertThat(challenge.getValidated(), is(parseTimestamp("2015-12-12T17:19:36.336785823Z")));
|
||||
assertThat((String) challenge.get("type"), is("generic-01"));
|
||||
assertThat(challenge.getUrl("uri"), is(new URL("http://example.com/challenge/123")));
|
||||
assertThat(challenge.get("not-present"), is(nullValue()));
|
||||
|
|
|
@ -13,12 +13,27 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.*;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.jose4j.jwk.PublicJsonWebKey;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
|
@ -26,6 +41,22 @@ import org.junit.Test;
|
|||
*/
|
||||
public class AcmeUtilsTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that constructor is private.
|
||||
*/
|
||||
@Test
|
||||
public void testPrivateConstructor() throws Exception {
|
||||
Constructor<AcmeUtils> constructor = AcmeUtils.class.getDeclaredConstructor();
|
||||
assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
|
||||
constructor.setAccessible(true);
|
||||
constructor.newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sha-256 hash.
|
||||
*/
|
||||
|
@ -69,4 +100,209 @@ public class AcmeUtilsTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ACE conversion.
|
||||
*/
|
||||
@Test
|
||||
public void testToAce() {
|
||||
// Test ASCII domains in different notations
|
||||
assertThat(toAce("example.com"), is("example.com"));
|
||||
assertThat(toAce(" example.com "), is("example.com"));
|
||||
assertThat(toAce("ExAmPlE.CoM"), is("example.com"));
|
||||
assertThat(toAce("foo.example.com"), is("foo.example.com"));
|
||||
assertThat(toAce("bar.foo.example.com"), is("bar.foo.example.com"));
|
||||
|
||||
// Test IDN domains
|
||||
assertThat(toAce("ExÄmþle.¢öM"), is("xn--exmle-hra7p.xn--m-7ba6w"));
|
||||
|
||||
// Test alternate separators
|
||||
assertThat(toAce("example\u3002com"), is("example.com"));
|
||||
assertThat(toAce("example\uff0ecom"), is("example.com"));
|
||||
assertThat(toAce("example\uff61com"), is("example.com"));
|
||||
|
||||
// Test ACE encoded domains, they must not change
|
||||
assertThat(toAce("xn--exmle-hra7p.xn--m-7ba6w"),
|
||||
is("xn--exmle-hra7p.xn--m-7ba6w"));
|
||||
|
||||
// Test null
|
||||
assertThat(toAce(null), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if RSA using SHA-256 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testRsaKey() throws Exception {
|
||||
KeyPair rsaKeyPair = TestUtils.createKeyPair();
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(rsaKeyPair.getPublic());
|
||||
|
||||
String type = keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("RS256"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-256 curve and SHA-256 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP256ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp256r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES256"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-384 curve and SHA-384 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP384ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp384r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES384"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-521 curve and SHA-512 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP521ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp521r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES512"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test valid strings.
|
||||
*/
|
||||
@Test
|
||||
public void testParser() {
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006769519Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.00676951Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.0067695Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006769Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.00676Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.0067Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.01Z"), isDate(2015, 12, 27, 22, 58, 35, 10));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.2Z"), isDate(2015, 12, 27, 22, 58, 35, 200));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35Z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||
assertThat(parseTimestamp("2015-12-27t22:58:35z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006769519+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35+02:00"), isDate(2015, 12, 27, 20, 58, 35));
|
||||
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35.006769519-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35.006-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35-02:00"), isDate(2015, 12, 27, 23, 58, 35));
|
||||
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006769519+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35.006+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T22:58:35+0200"), isDate(2015, 12, 27, 20, 58, 35));
|
||||
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35.006769519-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35.006-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parseTimestamp("2015-12-27T21:58:35-0200"), isDate(2015, 12, 27, 23, 58, 35));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid strings.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalid() {
|
||||
try {
|
||||
parseTimestamp("");
|
||||
fail("accepted empty string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parseTimestamp("abc");
|
||||
fail("accepted nonsense string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parseTimestamp("2015-12-27");
|
||||
fail("accepted year only string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parseTimestamp("2015-12-27T");
|
||||
fail("accepted year only string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given time.
|
||||
*/
|
||||
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second) {
|
||||
return isDate(year, month, dom, hour, minute, second, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given time and milliseconds.
|
||||
*/
|
||||
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second, int ms) {
|
||||
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
cal.clear();
|
||||
cal.set(year, month - 1, dom, hour, minute, second);
|
||||
cal.set(Calendar.MILLISECOND, ms);
|
||||
return new DateMatcher(cal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Date matcher that gives a readable output on mismatch.
|
||||
*/
|
||||
private static class DateMatcher extends BaseMatcher<Date> {
|
||||
private final Calendar cal;
|
||||
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH);
|
||||
|
||||
public DateMatcher(Calendar cal) {
|
||||
this.cal = cal;
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
if (!(item instanceof Date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Date date = (Date) item;
|
||||
return date.equals(cal.getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendValue(sdf.format(cal.getTime()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
if (!(item instanceof Date)) {
|
||||
description.appendText("is not a Date");
|
||||
return;
|
||||
}
|
||||
|
||||
Date date = (Date) item;
|
||||
description.appendText("was ").appendValue(sdf.format(date));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2016 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DomainUtils}.
|
||||
*/
|
||||
public class DomainUtilsTest {
|
||||
|
||||
/**
|
||||
* Test ACE conversion.
|
||||
*/
|
||||
@Test
|
||||
public void testToAce() {
|
||||
// Test ASCII domains in different notations
|
||||
assertThat(DomainUtils.toAce("example.com"), is("example.com"));
|
||||
assertThat(DomainUtils.toAce(" example.com "), is("example.com"));
|
||||
assertThat(DomainUtils.toAce("ExAmPlE.CoM"), is("example.com"));
|
||||
assertThat(DomainUtils.toAce("foo.example.com"), is("foo.example.com"));
|
||||
assertThat(DomainUtils.toAce("bar.foo.example.com"), is("bar.foo.example.com"));
|
||||
|
||||
// Test IDN domains
|
||||
assertThat(DomainUtils.toAce("ExÄmþle.¢öM"), is("xn--exmle-hra7p.xn--m-7ba6w"));
|
||||
|
||||
// Test alternate separators
|
||||
assertThat(DomainUtils.toAce("example\u3002com"), is("example.com"));
|
||||
assertThat(DomainUtils.toAce("example\uff0ecom"), is("example.com"));
|
||||
assertThat(DomainUtils.toAce("example\uff61com"), is("example.com"));
|
||||
|
||||
// Test ACE encoded domains, they must not change
|
||||
assertThat(DomainUtils.toAce("xn--exmle-hra7p.xn--m-7ba6w"),
|
||||
is("xn--exmle-hra7p.xn--m-7ba6w"));
|
||||
|
||||
// Test null
|
||||
assertThat(DomainUtils.toAce(null), is(nullValue()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2016 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.jose4j.jwk.PublicJsonWebKey;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SignatureUtils}.
|
||||
*/
|
||||
public class SignatureUtilsTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if RSA using SHA-256 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testRsaKey() throws Exception {
|
||||
KeyPair rsaKeyPair = TestUtils.createKeyPair();
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(rsaKeyPair.getPublic());
|
||||
|
||||
String type = SignatureUtils.keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("RS256"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-256 curve and SHA-256 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP256ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp256r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = SignatureUtils.keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES256"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-384 curve and SHA-384 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP384ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp384r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = SignatureUtils.keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES384"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if ECDSA using NIST P-521 curve and SHA-512 keys are properly detected.
|
||||
*/
|
||||
@Test
|
||||
public void testP521ECKey() throws Exception {
|
||||
KeyPair ecKeyPair = TestUtils.createECKeyPair("secp521r1");
|
||||
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(ecKeyPair.getPublic());
|
||||
|
||||
String type = SignatureUtils.keyAlgorithm(jwk);
|
||||
|
||||
assertThat(type, is("ES512"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2015 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.shredzone.acme4j.util.TimestampParser.parse;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TimestampParser}.
|
||||
*/
|
||||
public class TimestampParserTest {
|
||||
|
||||
/**
|
||||
* Test valid strings.
|
||||
*/
|
||||
@Test
|
||||
public void testParser() {
|
||||
assertThat(parse("2015-12-27T22:58:35.006769519Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.00676951Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.0067695Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.006769Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.00676Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.0067Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.006Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.01Z"), isDate(2015, 12, 27, 22, 58, 35, 10));
|
||||
assertThat(parse("2015-12-27T22:58:35.2Z"), isDate(2015, 12, 27, 22, 58, 35, 200));
|
||||
assertThat(parse("2015-12-27T22:58:35Z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||
assertThat(parse("2015-12-27t22:58:35z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||
|
||||
assertThat(parse("2015-12-27T22:58:35.006769519+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.006+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35+02:00"), isDate(2015, 12, 27, 20, 58, 35));
|
||||
|
||||
assertThat(parse("2015-12-27T21:58:35.006769519-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T21:58:35.006-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T21:58:35-02:00"), isDate(2015, 12, 27, 23, 58, 35));
|
||||
|
||||
assertThat(parse("2015-12-27T22:58:35.006769519+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35.006+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T22:58:35+0200"), isDate(2015, 12, 27, 20, 58, 35));
|
||||
|
||||
assertThat(parse("2015-12-27T21:58:35.006769519-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T21:58:35.006-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||
assertThat(parse("2015-12-27T21:58:35-0200"), isDate(2015, 12, 27, 23, 58, 35));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid strings.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalid() {
|
||||
try {
|
||||
parse("");
|
||||
fail("accepted empty string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parse("abc");
|
||||
fail("accepted nonsense string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parse("2015-12-27");
|
||||
fail("accepted year only string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
parse("2015-12-27T");
|
||||
fail("accepted year only string");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that constructor is private.
|
||||
*/
|
||||
@Test
|
||||
public void testPrivateConstructor() throws Exception {
|
||||
Constructor<TimestampParser> constructor = TimestampParser.class.getDeclaredConstructor();
|
||||
assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
|
||||
constructor.setAccessible(true);
|
||||
constructor.newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given time.
|
||||
*/
|
||||
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second) {
|
||||
return isDate(year, month, dom, hour, minute, second, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given time and milliseconds.
|
||||
*/
|
||||
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second, int ms) {
|
||||
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
cal.clear();
|
||||
cal.set(year, month - 1, dom, hour, minute, second);
|
||||
cal.set(Calendar.MILLISECOND, ms);
|
||||
return new DateMatcher(cal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Date matcher that gives a readable output on mismatch.
|
||||
*/
|
||||
private static class DateMatcher extends BaseMatcher<Date> {
|
||||
|
||||
private final Calendar cal;
|
||||
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH);
|
||||
|
||||
public DateMatcher(Calendar cal) {
|
||||
this.cal = cal;
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
if (!(item instanceof Date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Date date = (Date) item;
|
||||
return date.equals(cal.getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendValue(sdf.format(cal.getTime()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
if (!(item instanceof Date)) {
|
||||
description.appendText("is not a Date");
|
||||
return;
|
||||
}
|
||||
|
||||
Date date = (Date) item;
|
||||
description.appendText("was ").appendValue(sdf.format(date));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.util;
|
||||
|
||||
import static org.shredzone.acme4j.util.AcmeUtils.toAce;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
@ -64,7 +66,7 @@ public class CSRBuilder {
|
|||
* Note that ACME servers may not accept wildcard domains!
|
||||
*/
|
||||
public void addDomain(String domain) {
|
||||
String ace = DomainUtils.toAce(domain);
|
||||
String ace = toAce(domain);
|
||||
if (namelist.isEmpty()) {
|
||||
namebuilder.addRDN(BCStyle.CN, ace);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue