From c48febda6279f8c08a2082a166d2929dbe46e2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Wed, 22 Jun 2016 00:45:57 +0200 Subject: [PATCH] Move jwkThumbprint() to SignatureUtils, add unit test --- .../acme4j/challenge/GenericChallenge.java | 34 ------------------ .../challenge/GenericTokenChallenge.java | 3 +- .../shredzone/acme4j/util/SignatureUtils.java | 36 +++++++++++++++++++ .../challenge/GenericChallengeTest.java | 3 +- .../acme4j/util/SignatureUtilsTest.java | 14 ++++++++ 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java index 70872ac0..ecc261b8 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java @@ -16,19 +16,13 @@ package org.shredzone.acme4j.challenge; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.jose4j.json.JsonUtil; -import org.jose4j.jwk.JsonWebKey; -import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.lang.JoseException; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.exception.AcmeProtocolException; @@ -132,34 +126,6 @@ public class GenericChallenge implements Challenge { 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 RFC 7638 - */ - 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. */ diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericTokenChallenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericTokenChallenge.java index a8da438f..d485e2f2 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericTokenChallenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericTokenChallenge.java @@ -16,6 +16,7 @@ package org.shredzone.acme4j.challenge; import org.jose4j.base64url.Base64Url; import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.util.ClaimBuilder; +import org.shredzone.acme4j.util.SignatureUtils; /** * 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) { return getToken() + '.' - + Base64Url.encode(jwkThumbprint(registration.getKeyPair().getPublic())); + + Base64Url.encode(SignatureUtils.jwkThumbprint(registration.getKeyPair().getPublic())); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/util/SignatureUtils.java b/acme4j-client/src/main/java/org/shredzone/acme4j/util/SignatureUtils.java index 3d8630d9..5929d748 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/util/SignatureUtils.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/util/SignatureUtils.java @@ -13,11 +13,19 @@ */ 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.JsonWebKey; +import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.jwk.RsaJsonWebKey; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; +import org.jose4j.lang.JoseException; +import org.shredzone.acme4j.exception.AcmeProtocolException; /** * 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 RFC 7638 + */ + 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); + } + } + } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java index 1c4dcc87..2e2b3485 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java @@ -35,6 +35,7 @@ import org.junit.Test; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; +import org.shredzone.acme4j.util.SignatureUtils; import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TimestampParser; @@ -115,7 +116,7 @@ public class GenericChallengeTest { assertThat(cb.toString(), is(json.toString())); // 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))); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/util/SignatureUtilsTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/util/SignatureUtilsTest.java index 885d730b..fbc1afb3 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/util/SignatureUtilsTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/util/SignatureUtilsTest.java @@ -20,6 +20,7 @@ import java.security.KeyPair; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.jose4j.base64url.Base64Url; import org.jose4j.jwk.PublicJsonWebKey; import org.junit.BeforeClass; import org.junit.Test; @@ -88,4 +89,17 @@ public class SignatureUtilsTest { 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)); + } + }