Move jwkThumbprint() to SignatureUtils, add unit test

pull/18/head
Richard Körber 2016-06-22 00:45:57 +02:00
parent ddac0c45d1
commit c48febda62
5 changed files with 54 additions and 36 deletions

View File

@ -16,19 +16,13 @@ package org.shredzone.acme4j.challenge;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.jose4j.json.JsonUtil; import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKey.OutputControlLevel;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
@ -132,34 +126,6 @@ public class GenericChallenge implements Challenge {
return (T) data.get(key); return (T) data.get(key);
} }
/**
* Computes a JWK Thumbprint. It is frequently used in responses.
*
* @param key
* {@link PublicKey} to create a thumbprint of
* @return Thumbprint, SHA-256 hashed
* @see <a href="https://tools.ietf.org/html/rfc7638">RFC 7638</a>
*/
public static byte[] jwkThumbprint(PublicKey key) {
if (key == null) {
throw new NullPointerException("key must not be null");
}
try {
final JsonWebKey jwk = JsonWebKey.Factory.newJwk(key);
// We need to use ClaimBuilder to bring the keys in lexicographical order.
ClaimBuilder cb = new ClaimBuilder();
cb.putAll(jwk.toParams(OutputControlLevel.PUBLIC_ONLY));
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(cb.toString().getBytes("UTF-8"));
return md.digest();
} catch (JoseException | NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Cannot compute key thumbprint", ex);
}
}
/** /**
* Serialize the data map in JSON. * Serialize the data map in JSON.
*/ */

View File

@ -16,6 +16,7 @@ package org.shredzone.acme4j.challenge;
import org.jose4j.base64url.Base64Url; import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.SignatureUtils;
/** /**
* An extension of {@link GenericChallenge} that handles challenges with a {@code token} * An extension of {@link GenericChallenge} that handles challenges with a {@code token}
@ -94,7 +95,7 @@ public class GenericTokenChallenge extends GenericChallenge {
protected String computeAuthorization(Registration registration) { protected String computeAuthorization(Registration registration) {
return getToken() return getToken()
+ '.' + '.'
+ Base64Url.encode(jwkThumbprint(registration.getKeyPair().getPublic())); + Base64Url.encode(SignatureUtils.jwkThumbprint(registration.getKeyPair().getPublic()));
} }
} }

View File

@ -13,11 +13,19 @@
*/ */
package org.shredzone.acme4j.util; package org.shredzone.acme4j.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import org.jose4j.jwk.EllipticCurveJsonWebKey; import org.jose4j.jwk.EllipticCurveJsonWebKey;
import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKey.OutputControlLevel;
import org.jose4j.jwk.RsaJsonWebKey; import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature; import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Utility class for signatures. * Utility class for signatures.
@ -67,4 +75,32 @@ public final class SignatureUtils {
} }
} }
/**
* Computes a JWK Thumbprint. It is frequently used in responses.
*
* @param key
* {@link PublicKey} to create a thumbprint of
* @return Thumbprint, SHA-256 hashed
* @see <a href="https://tools.ietf.org/html/rfc7638">RFC 7638</a>
*/
public static byte[] jwkThumbprint(PublicKey key) {
if (key == null) {
throw new NullPointerException("key must not be null");
}
try {
final JsonWebKey jwk = JsonWebKey.Factory.newJwk(key);
// We need to use ClaimBuilder to bring the keys in lexicographical order.
ClaimBuilder cb = new ClaimBuilder();
cb.putAll(jwk.toParams(OutputControlLevel.PUBLIC_ONLY));
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(cb.toString().getBytes("UTF-8"));
return md.digest();
} catch (JoseException | NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new AcmeProtocolException("Cannot compute key thumbprint", ex);
}
}
} }

View File

@ -35,6 +35,7 @@ import org.junit.Test;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.SignatureUtils;
import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TestUtils;
import org.shredzone.acme4j.util.TimestampParser; import org.shredzone.acme4j.util.TimestampParser;
@ -115,7 +116,7 @@ public class GenericChallengeTest {
assertThat(cb.toString(), is(json.toString())); assertThat(cb.toString(), is(json.toString()));
// Make sure the returned thumbprint is correct // Make sure the returned thumbprint is correct
byte[] thumbprint = GenericChallenge.jwkThumbprint(keypair.getPublic()); byte[] thumbprint = SignatureUtils.jwkThumbprint(keypair.getPublic());
assertThat(thumbprint, is(Base64Url.decode(TestUtils.THUMBPRINT))); assertThat(thumbprint, is(Base64Url.decode(TestUtils.THUMBPRINT)));
} }

View File

@ -20,6 +20,7 @@ import java.security.KeyPair;
import java.security.Security; import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jose4j.base64url.Base64Url;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -88,4 +89,17 @@ public class SignatureUtilsTest {
assertThat(type, is("ES512")); assertThat(type, is("ES512"));
} }
/**
* Test if {@link SignatureUtils#jwkThumbprint(java.security.PublicKey)} returns the
* correct thumb print.
*/
@Test
public void testJwkThumbprint() throws Exception {
KeyPair keyPair = TestUtils.createKeyPair();
byte[] thumbprint = SignatureUtils.jwkThumbprint(keyPair.getPublic());
assertThat(Base64Url.encode(thumbprint), is(TestUtils.THUMBPRINT));
}
} }