Add AcmeUtils for commonly used functions

pull/30/head
Richard Körber 2016-12-16 00:54:06 +01:00
parent 53712df034
commit 1cca9e26af
7 changed files with 151 additions and 78 deletions

View File

@ -13,13 +13,9 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import java.io.UnsupportedEncodingException; import static org.shredzone.acme4j.util.AcmeUtils.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Implements the {@value TYPE} challenge. * Implements the {@value TYPE} challenge.
@ -47,14 +43,7 @@ public class Dns01Challenge extends TokenChallenge {
* record. * record.
*/ */
public String getDigest() { public String getDigest() {
try { return base64UrlEncode(sha256hash(getAuthorization()));
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(getAuthorization().getBytes("UTF-8"));
byte[] digest = md.digest();
return Base64Url.encode(digest);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Failed to compute digest", ex);
}
} }
@Override @Override

View File

@ -13,12 +13,9 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import java.io.UnsupportedEncodingException; import static org.shredzone.acme4j.util.AcmeUtils.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Implements the {@value TYPE} challenge. * Implements the {@value TYPE} challenge.
@ -30,7 +27,6 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
@Deprecated @Deprecated
public class TlsSni01Challenge extends TokenChallenge { public class TlsSni01Challenge extends TokenChallenge {
private static final long serialVersionUID = 7370329525205430573L; private static final long serialVersionUID = 7370329525205430573L;
private static final char[] HEX = "0123456789abcdef".toCharArray();
/** /**
* Challenge type name: {@value} * Challenge type name: {@value}
@ -65,32 +61,8 @@ public class TlsSni01Challenge extends TokenChallenge {
protected void authorize() { protected void authorize() {
super.authorize(); super.authorize();
String hash = computeHash(getAuthorization()); String hash = hexEncode(sha256hash(getAuthorization()));
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid"; subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
} }
/**
* Computes a hash according to the specifications.
*
* @param z
* Value to be hashed
* @return Hash
*/
private String computeHash(String z) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(z.getBytes("UTF-8"));
byte[] raw = md.digest();
char[] result = new char[raw.length * 2];
for (int ix = 0; ix < raw.length; ix++) {
int val = raw[ix] & 0xFF;
result[ix * 2] = HEX[val >>> 4];
result[ix * 2 + 1] = HEX[val & 0x0F];
}
return new String(result);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Could not compute hash", ex);
}
}
} }

View File

@ -13,19 +13,15 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import java.io.UnsupportedEncodingException; import static org.shredzone.acme4j.util.AcmeUtils.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Implements the {@value TYPE} challenge. * Implements the {@value TYPE} challenge.
*/ */
public class TlsSni02Challenge extends TokenChallenge { public class TlsSni02Challenge extends TokenChallenge {
private static final long serialVersionUID = 8921833167878544518L; private static final long serialVersionUID = 8921833167878544518L;
private static final char[] HEX = "0123456789abcdef".toCharArray();
/** /**
* Challenge type name: {@value} * Challenge type name: {@value}
@ -70,35 +66,11 @@ public class TlsSni02Challenge extends TokenChallenge {
protected void authorize() { protected void authorize() {
super.authorize(); super.authorize();
String tokenHash = computeHash(getToken()); String tokenHash = hexEncode(sha256hash(getToken()));
subject = tokenHash.substring(0, 32) + '.' + tokenHash.substring(32) + ".token.acme.invalid"; subject = tokenHash.substring(0, 32) + '.' + tokenHash.substring(32) + ".token.acme.invalid";
String kaHash = computeHash(getAuthorization()); String kaHash = hexEncode(sha256hash(getAuthorization()));
sanB = kaHash.substring(0, 32) + '.' + kaHash.substring(32) + ".ka.acme.invalid"; sanB = kaHash.substring(0, 32) + '.' + kaHash.substring(32) + ".ka.acme.invalid";
} }
/**
* Computes a hash according to the specifications.
*
* @param z
* Value to be hashed
* @return Hash
*/
private String computeHash(String z) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(z.getBytes("UTF-8"));
byte[] raw = md.digest();
char[] result = new char[raw.length * 2];
for (int ix = 0; ix < raw.length; ix++) {
int val = raw[ix] & 0xFF;
result[ix * 2] = HEX[val >>> 4];
result[ix * 2 + 1] = HEX[val & 0x0F];
}
return new String(result);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Could not compute hash", ex);
}
}
} }

View File

@ -13,9 +13,10 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import static org.shredzone.acme4j.util.AcmeUtils.base64UrlEncode;
import java.security.PublicKey; import java.security.PublicKey;
import org.jose4j.base64url.Base64Url;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
@ -83,7 +84,7 @@ public class TokenChallenge extends Challenge {
PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(pk); PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(pk);
return getToken() return getToken()
+ '.' + '.'
+ Base64Url.encode(jwk.calculateThumbprint("SHA-256")); + base64UrlEncode(jwk.calculateThumbprint("SHA-256"));
} catch (JoseException ex) { } catch (JoseException ex) {
throw new AcmeProtocolException("Cannot compute key thumbprint", ex); throw new AcmeProtocolException("Cannot compute key thumbprint", ex);
} }

View File

@ -0,0 +1,81 @@
/*
* 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.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/**
* Contains utility methods that are frequently used for the ACME protocol.
* <p>
* This class is internal. You may use it in your own code, but be warned that methods may
* change their signature or disappear without prior announcement.
*/
public final class AcmeUtils {
private static final char[] HEX = "0123456789abcdef".toCharArray();
private AcmeUtils() {
// Utility class without constructor
}
/**
* Computes a SHA-256 hash of the given string.
*
* @param z
* String to hash
* @return Hash
*/
public static byte[] sha256hash(String z) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(z.getBytes("UTF-8"));
return md.digest();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Could not compute hash", ex);
}
}
/**
* Hex encodes the given byte array.
*
* @param data
* byte array to hex encode
* @return Hex encoded string of the data (with lower case characters)
*/
public static String hexEncode(byte[] data) {
char[] result = new char[data.length * 2];
for (int ix = 0; ix < data.length; ix++) {
int val = data[ix] & 0xFF;
result[ix * 2] = HEX[val >>> 4];
result[ix * 2 + 1] = HEX[val & 0x0F];
}
return new String(result);
}
/**
* Base64 encodes the given byte array, using URL style encoding.
*
* @param data
* byte array to base64 encode
* @return base64 encoded string
*/
public static String base64UrlEncode(byte[] data) {
return Base64Url.encode(data);
}
}

View File

@ -13,6 +13,8 @@
*/ */
package org.shredzone.acme4j.util; package org.shredzone.acme4j.util;
import static org.shredzone.acme4j.util.AcmeUtils.base64UrlEncode;
import java.security.Key; import java.security.Key;
import java.security.PublicKey; import java.security.PublicKey;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -22,7 +24,6 @@ import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeMap; import java.util.TreeMap;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil; import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
@ -130,7 +131,7 @@ public class ClaimBuilder {
* @return {@code this} * @return {@code this}
*/ */
public ClaimBuilder putBase64(String key, byte[] data) { public ClaimBuilder putBase64(String key, byte[] data) {
return put(key, Base64Url.encode(data)); return put(key, base64UrlEncode(data));
} }
/** /**

View File

@ -0,0 +1,57 @@
/*
* 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 static org.shredzone.acme4j.util.AcmeUtils.*;
import javax.xml.bind.DatatypeConverter;
import org.junit.Test;
/**
* Unit tests for {@link AcmeUtils}.
*/
public class AcmeUtilsTest {
/**
* Test sha-256 hash.
*/
@Test
public void testSha256Hash() {
byte[] hash = sha256hash("foobar");
byte[] expected = DatatypeConverter.parseHexBinary("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2");
assertThat(hash, is(expected));
}
/**
* Test hex encode.
*/
@Test
public void testHexEncode() {
String hexEncode = hexEncode(sha256hash("foobar"));
assertThat(hexEncode, is("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"));
}
/**
* Test base64 URL encode.
*/
@Test
public void testBase64UrlEncode() {
String base64UrlEncode = base64UrlEncode(sha256hash("foobar"));
assertThat(base64UrlEncode, is("w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI"));
}
}