mirror of https://github.com/shred/acme4j
Merge utility classes
parent
0a288fa290
commit
25b00313b2
|
@ -13,6 +13,8 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j;
|
package org.shredzone.acme4j;
|
||||||
|
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
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.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -236,7 +237,7 @@ public class Authorization extends AcmeResource {
|
||||||
|
|
||||||
String jsonExpires = (String) json.get("expires");
|
String jsonExpires = (String) json.get("expires");
|
||||||
if (jsonExpires != null) {
|
if (jsonExpires != null) {
|
||||||
expires = TimestampParser.parse(jsonExpires);
|
expires = parseTimestamp(jsonExpires);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> jsonIdentifier = (Map<String, Object>) json.get("identifier");
|
Map<String, Object> jsonIdentifier = (Map<String, Object>) json.get("identifier");
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j;
|
package org.shredzone.acme4j;
|
||||||
|
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.*;
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
@ -38,8 +40,6 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.AcmeUtils;
|
import org.shredzone.acme4j.util.AcmeUtils;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ public class Registration extends AcmeResource {
|
||||||
claims.putResource(Resource.NEW_AUTHZ);
|
claims.putResource(Resource.NEW_AUTHZ);
|
||||||
claims.object("identifier")
|
claims.object("identifier")
|
||||||
.put("type", "dns")
|
.put("type", "dns")
|
||||||
.put("value", DomainUtils.toAce(domain));
|
.put("value", toAce(domain));
|
||||||
|
|
||||||
conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_AUTHZ), claims, getSession());
|
conn.sendSignedRequest(getSession().resourceUri(Resource.NEW_AUTHZ), claims, getSession());
|
||||||
conn.accept(HttpURLConnection.HTTP_CREATED);
|
conn.accept(HttpURLConnection.HTTP_CREATED);
|
||||||
|
@ -289,7 +289,7 @@ public class Registration extends AcmeResource {
|
||||||
innerJws.setPayload(payloadClaim.toString());
|
innerJws.setPayload(payloadClaim.toString());
|
||||||
innerJws.getHeaders().setObjectHeaderValue("url", keyChangeUri);
|
innerJws.getHeaders().setObjectHeaderValue("url", keyChangeUri);
|
||||||
innerJws.getHeaders().setJwkHeaderValue("jwk", newKeyJwk);
|
innerJws.getHeaders().setJwkHeaderValue("jwk", newKeyJwk);
|
||||||
innerJws.setAlgorithmHeaderValue(SignatureUtils.keyAlgorithm(newKeyJwk));
|
innerJws.setAlgorithmHeaderValue(keyAlgorithm(newKeyJwk));
|
||||||
innerJws.setKey(newKeyPair.getPrivate());
|
innerJws.setKey(newKeyPair.getPrivate());
|
||||||
innerJws.sign();
|
innerJws.sign();
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j.challenge;
|
package org.shredzone.acme4j.challenge;
|
||||||
|
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
@ -35,7 +37,6 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.AcmeUtils;
|
import org.shredzone.acme4j.util.AcmeUtils;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ public class Challenge extends AcmeResource {
|
||||||
public Date getValidated() {
|
public Date getValidated() {
|
||||||
String valStr = get(KEY_VALIDATED);
|
String valStr = get(KEY_VALIDATED);
|
||||||
if (valStr != null) {
|
if (valStr != null) {
|
||||||
return TimestampParser.parse(valStr);
|
return parseTimestamp(valStr);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j.connector;
|
package org.shredzone.acme4j.connector;
|
||||||
|
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.keyAlgorithm;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -51,7 +53,6 @@ import org.shredzone.acme4j.exception.AcmeServerException;
|
||||||
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
||||||
import org.shredzone.acme4j.util.AcmeUtils;
|
import org.shredzone.acme4j.util.AcmeUtils;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.SignatureUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ public class DefaultConnection implements Connection {
|
||||||
jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce()));
|
jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce()));
|
||||||
jws.getHeaders().setObjectHeaderValue("url", uri);
|
jws.getHeaders().setObjectHeaderValue("url", uri);
|
||||||
jws.getHeaders().setJwkHeaderValue("jwk", jwk);
|
jws.getHeaders().setJwkHeaderValue("jwk", jwk);
|
||||||
jws.setAlgorithmHeaderValue(SignatureUtils.keyAlgorithm(jwk));
|
jws.setAlgorithmHeaderValue(keyAlgorithm(jwk));
|
||||||
jws.setKey(keypair.getPrivate());
|
jws.setKey(keypair.getPrivate());
|
||||||
byte[] outputData = jws.getCompactSerialization().getBytes("utf-8");
|
byte[] outputData = jws.getCompactSerialization().getBytes("utf-8");
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,22 @@
|
||||||
package org.shredzone.acme4j.util;
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.IDN;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
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.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;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +41,15 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
public final class AcmeUtils {
|
public final class AcmeUtils {
|
||||||
private static final char[] HEX = "0123456789abcdef".toCharArray();
|
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() {
|
private AcmeUtils() {
|
||||||
// Utility class without constructor
|
// 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.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||||
import static org.shredzone.acme4j.util.TestUtils.*;
|
import static org.shredzone.acme4j.util.TestUtils.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -34,7 +35,6 @@ import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link Authorization}.
|
* Unit tests for {@link Authorization}.
|
||||||
|
@ -149,7 +149,7 @@ public class AuthorizationTest {
|
||||||
|
|
||||||
assertThat(auth.getDomain(), is("example.org"));
|
assertThat(auth.getDomain(), is("example.org"));
|
||||||
assertThat(auth.getStatus(), is(Status.VALID));
|
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.getLocation(), is(locationUri));
|
||||||
|
|
||||||
assertThat(auth.getChallenges(), containsInAnyOrder(
|
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||||
|
@ -207,7 +207,7 @@ public class AuthorizationTest {
|
||||||
requestWasSent.set(false);
|
requestWasSent.set(false);
|
||||||
assertThat(auth.getDomain(), is("example.org"));
|
assertThat(auth.getDomain(), is("example.org"));
|
||||||
assertThat(auth.getStatus(), is(Status.VALID));
|
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));
|
assertThat(requestWasSent.get(), is(false));
|
||||||
|
|
||||||
provider.close();
|
provider.close();
|
||||||
|
@ -262,7 +262,7 @@ public class AuthorizationTest {
|
||||||
|
|
||||||
assertThat(auth.getDomain(), is("example.org"));
|
assertThat(auth.getDomain(), is("example.org"));
|
||||||
assertThat(auth.getStatus(), is(Status.VALID));
|
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.getLocation(), is(locationUri));
|
||||||
|
|
||||||
assertThat(auth.getChallenges(), containsInAnyOrder(
|
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||||
|
|
|
@ -15,6 +15,7 @@ package org.shredzone.acme4j.challenge;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
|
||||||
import static org.shredzone.acme4j.util.TestUtils.*;
|
import static org.shredzone.acme4j.util.TestUtils.*;
|
||||||
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
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.provider.TestableConnectionProvider;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TestUtils;
|
import org.shredzone.acme4j.util.TestUtils;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link Challenge}.
|
* Unit tests for {@link Challenge}.
|
||||||
|
@ -116,7 +116,7 @@ public class ChallengeTest {
|
||||||
assertThat(challenge.getType(), is("generic-01"));
|
assertThat(challenge.getType(), is("generic-01"));
|
||||||
assertThat(challenge.getStatus(), is(Status.VALID));
|
assertThat(challenge.getStatus(), is(Status.VALID));
|
||||||
assertThat(challenge.getLocation(), is(new URI("http://example.com/challenge/123")));
|
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((String) challenge.get("type"), is("generic-01"));
|
||||||
assertThat(challenge.getUrl("uri"), is(new URL("http://example.com/challenge/123")));
|
assertThat(challenge.getUrl("uri"), is(new URL("http://example.com/challenge/123")));
|
||||||
assertThat(challenge.get("not-present"), is(nullValue()));
|
assertThat(challenge.get("not-present"), is(nullValue()));
|
||||||
|
|
|
@ -13,12 +13,27 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j.util;
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.shredzone.acme4j.util.AcmeUtils.*;
|
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 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;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +41,22 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public class AcmeUtilsTest {
|
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.
|
* 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;
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
|
import static org.shredzone.acme4j.util.AcmeUtils.toAce;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
@ -64,7 +66,7 @@ public class CSRBuilder {
|
||||||
* Note that ACME servers may not accept wildcard domains!
|
* Note that ACME servers may not accept wildcard domains!
|
||||||
*/
|
*/
|
||||||
public void addDomain(String domain) {
|
public void addDomain(String domain) {
|
||||||
String ace = DomainUtils.toAce(domain);
|
String ace = toAce(domain);
|
||||||
if (namelist.isEmpty()) {
|
if (namelist.isEmpty()) {
|
||||||
namebuilder.addRDN(BCStyle.CN, ace);
|
namebuilder.addRDN(BCStyle.CN, ace);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue