Skip to content

Commit 383517e

Browse files
committed
- enabled more IANA algorithms in StandardHashAlgorithms
- JavaDoc update
1 parent f3e9721 commit 383517e

14 files changed

+194
-24
lines changed

api/src/main/java/io/jsonwebtoken/lang/Registry.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,39 @@
1717

1818
import java.util.Collection;
1919

20+
/**
21+
* An immutable read-only repository of key-value pairs.
22+
*
23+
* @param <K> key type
24+
* @param <V> value type
25+
* @since JJWT_RELEASE_VERSION
26+
*/
2027
public interface Registry<K, V> {
2128

29+
/**
30+
* Returns all registry values as a read-only collection.
31+
*
32+
* @return all registry values as a read-only collection.
33+
*/
2234
Collection<V> values();
2335

36+
/**
37+
* Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no
38+
* associated value. If a value is not required, consider using the {@link #find(Object)} method instead.
39+
*
40+
* @param key the registry key assigned to the required value
41+
* @return the value assigned the specified key
42+
* @throws IllegalArgumentException if there is no value assigned the specified key
43+
* @see #find(Object)
44+
*/
2445
V get(K key) throws IllegalArgumentException;
2546

47+
/**
48+
* Returns the value assigned the specified key or {@code null} if there is no associated value.
49+
*
50+
* @param key the registry key assigned to the required value
51+
* @return the value assigned the specified key or {@code null} if there is no associated value.
52+
* @see #get(Object)
53+
*/
2654
V find(K key);
2755
}

api/src/main/java/io/jsonwebtoken/security/StandardHashAlgorithms.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @see #find(String)
4646
* @see #get(String)
4747
* @see HashAlgorithm
48+
* @since JJWT_RELEASE_VERSION
4849
*/
4950
public final class StandardHashAlgorithms implements Registry<String, HashAlgorithm> {
5051

@@ -65,12 +66,57 @@ public static StandardHashAlgorithms get() { // named `get` to mimic java.util.f
6566
/**
6667
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
6768
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
68-
* value of {@code sha-256}. Per the IANA registry, this algorithm is defined by
69-
* <a href="https://www.rfc-editor.org/rfc/rfc6920.html">RFC 6920</a> and is the same as the
70-
* native Java JCA {@code SHA-256} {@code MessageDigest} algorithm.
69+
* value of {@code sha-256}. It is a {@code HashAlgorithm} alias for the native
70+
* Java JCA {@code SHA-256} {@code MessageDigest} algorithm.
7171
*/
7272
public final HashAlgorithm SHA256 = get("sha-256");
7373

74+
/**
75+
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
76+
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
77+
* value of {@code sha-384}. It is a {@code HashAlgorithm} alias for the native
78+
* Java JCA {@code SHA-384} {@code MessageDigest} algorithm.
79+
*/
80+
public final HashAlgorithm SHA384 = get("sha-384");
81+
82+
/**
83+
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
84+
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
85+
* value of {@code sha-512}. It is a {@code HashAlgorithm} alias for the native
86+
* Java JCA {@code SHA-512} {@code MessageDigest} algorithm.
87+
*/
88+
public final HashAlgorithm SHA512 = get("sha-512");
89+
90+
/**
91+
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
92+
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
93+
* value of {@code sha3-256}. It is a {@code HashAlgorithm} alias for the native
94+
* Java JCA {@code SHA3-256} {@code MessageDigest} algorithm.
95+
* <p><b>This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime
96+
* classpath.</b></p>
97+
*/
98+
public final HashAlgorithm SHA3_256 = get("sha3-256");
99+
100+
/**
101+
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
102+
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
103+
* value of {@code sha3-384}. It is a {@code HashAlgorithm} alias for the native
104+
* Java JCA {@code SHA3-384} {@code MessageDigest} algorithm.
105+
* <p><b>This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime
106+
* classpath.</b></p>
107+
*/
108+
public final HashAlgorithm SHA3_384 = get("sha3-384");
109+
110+
/**
111+
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA
112+
* hash algorithm</a> with an {@link Identifiable#getId() id} (aka IANA &quot;{@code Hash Name String}&quot;)
113+
* value of {@code sha3-512}. It is a {@code HashAlgorithm} alias for the native
114+
* Java JCA {@code SHA3-512} {@code MessageDigest} algorithm.
115+
* <p><b>This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime
116+
* classpath.</b></p>
117+
*/
118+
public final HashAlgorithm SHA3_512 = get("sha3-512");
119+
74120
/**
75121
* Prevent external instantiation.
76122
*/

impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.jsonwebtoken.security.HashAlgorithm;
2929
import io.jsonwebtoken.security.Jwk;
3030
import io.jsonwebtoken.security.JwkThumbprint;
31+
import io.jsonwebtoken.security.Jwks;
3132

3233
import java.nio.charset.StandardCharsets;
3334
import java.security.Key;
@@ -100,7 +101,7 @@ private String toThumbprintJson() {
100101

101102
@Override
102103
public JwkThumbprint thumbprint() {
103-
return thumbprint(DefaultHashAlgorithm.SHA256);
104+
return thumbprint(Jwks.HASH.SHA256);
104105
}
105106

106107
@Override

impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.jsonwebtoken.security.HashAlgorithm;
2020
import io.jsonwebtoken.security.Jwk;
2121
import io.jsonwebtoken.security.JwkBuilder;
22+
import io.jsonwebtoken.security.Jwks;
2223
import io.jsonwebtoken.security.MalformedKeyException;
2324
import io.jsonwebtoken.security.SecretJwk;
2425
import io.jsonwebtoken.security.SecretJwkBuilder;
@@ -108,7 +109,7 @@ public T setId(String id) {
108109

109110
@Override
110111
public T setIdFromThumbprint() {
111-
return setIdFromThumbprint(DefaultHashAlgorithm.SHA256);
112+
return setIdFromThumbprint(Jwks.HASH.SHA256);
112113
}
113114

114115
@Override

impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,20 @@
2222
import io.jsonwebtoken.security.VerifyDigestRequest;
2323

2424
import java.security.MessageDigest;
25+
import java.security.Provider;
26+
import java.util.Locale;
2527

2628
public final class DefaultHashAlgorithm extends CryptoAlgorithm implements HashAlgorithm {
2729

28-
public static final HashAlgorithm SHA1 = new DefaultHashAlgorithm("sha-1", "SHA-1");
29-
public static final HashAlgorithm SHA256 = new DefaultHashAlgorithm("sha-256", "SHA-256");
30+
public static final HashAlgorithm SHA1 = new DefaultHashAlgorithm("sha-1");
3031

31-
DefaultHashAlgorithm(String id, String jcaName) {
32+
DefaultHashAlgorithm(String id) {
33+
super(id, id.toUpperCase(Locale.ENGLISH));
34+
}
35+
36+
DefaultHashAlgorithm(String id, String jcaName, Provider provider) {
3237
super(id, jcaName);
38+
setProvider(provider);
3339
}
3440

3541
@Override

impl/src/main/java/io/jsonwebtoken/impl/security/DefaultX509Builder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.jsonwebtoken.lang.Objects;
2424
import io.jsonwebtoken.security.HashAlgorithm;
2525
import io.jsonwebtoken.security.Request;
26+
import io.jsonwebtoken.security.StandardHashAlgorithms;
2627
import io.jsonwebtoken.security.X509Builder;
2728
import io.jsonwebtoken.security.X509Mutator;
2829

@@ -127,7 +128,7 @@ public void apply() {
127128
setX509CertificateSha1Thumbprint(thumbprint);
128129
}
129130
if (computeX509Sha256Thumbprint) {
130-
byte[] thumbprint = computeThumbprint(firstCert, DefaultHashAlgorithm.SHA256);
131+
byte[] thumbprint = computeThumbprint(firstCert, StandardHashAlgorithms.get().SHA256);
131132
setX509CertificateSha256Thumbprint(thumbprint);
132133
}
133134
}

impl/src/main/java/io/jsonwebtoken/impl/security/StandardHashAlgorithmsBridge.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,56 @@
1515
*/
1616
package io.jsonwebtoken.impl.security;
1717

18+
import io.jsonwebtoken.impl.lang.CheckedSupplier;
19+
import io.jsonwebtoken.impl.lang.Conditions;
1820
import io.jsonwebtoken.impl.lang.IdRegistry;
21+
import io.jsonwebtoken.lang.Assert;
1922
import io.jsonwebtoken.lang.Collections;
20-
import io.jsonwebtoken.security.DigestAlgorithm;
2123
import io.jsonwebtoken.security.HashAlgorithm;
2224

25+
import java.security.MessageDigest;
26+
import java.security.Provider;
27+
import java.util.Locale;
28+
2329
/**
24-
* Static class definitions for standard {@link DigestAlgorithm} instances.
30+
* Backing implementation for the {@link io.jsonwebtoken.security.StandardHashAlgorithms} implementation.
2531
*
2632
* @since JJWT_RELEASE_VERSION
2733
*/
2834
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.StandardHashAlgorithms
2935
public class StandardHashAlgorithmsBridge extends DelegatingRegistry<HashAlgorithm> {
36+
3037
public StandardHashAlgorithmsBridge() {
3138
super(new IdRegistry<>("IANA Hash Algorithm", Collections.of(
32-
DefaultHashAlgorithm.SHA256
39+
// We don't include DefaultHashAlgorithm.SHA1 here on purpose because 1) it's not in the JWK IANA
40+
// registry so we don't need to expose it anyway, and 2) we don't want to expose a less-safe algorithm.
41+
// The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t`
42+
// (X.509 SHA-1 Thumbprint) computation - we don't use it anywhere else.
43+
(HashAlgorithm) new DefaultHashAlgorithm("sha-256"),
44+
new DefaultHashAlgorithm("sha-384"),
45+
new DefaultHashAlgorithm("sha-512"),
46+
fallbackProvider("sha3-256"),
47+
fallbackProvider("sha3-384"),
48+
fallbackProvider("sha3-512")
3349
)));
3450
}
51+
52+
private static DefaultHashAlgorithm fallbackProvider(String id) {
53+
String jcaName = id.toUpperCase(Locale.ENGLISH);
54+
Provider provider = Providers.findBouncyCastle(Conditions.notExists(new MessageDigestSupplier(jcaName)));
55+
return new DefaultHashAlgorithm(id, jcaName, provider);
56+
}
57+
58+
private static class MessageDigestSupplier implements CheckedSupplier<MessageDigest> {
59+
private final String jcaName;
60+
61+
private MessageDigestSupplier(String jcaName) {
62+
this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty.");
63+
}
64+
65+
@Override
66+
public MessageDigest get() throws Exception {
67+
return MessageDigest.getInstance(jcaName);
68+
}
69+
}
3570
}

impl/src/test/groovy/io/jsonwebtoken/impl/DefaultDynamicHeaderBuilderTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.jsonwebtoken.impl.security.TestKeys
2525
import io.jsonwebtoken.io.Encoders
2626
import io.jsonwebtoken.security.Jwks
2727
import io.jsonwebtoken.security.Request
28+
import io.jsonwebtoken.security.StandardHashAlgorithms
2829
import org.junit.Before
2930
import org.junit.Test
3031

@@ -181,7 +182,7 @@ class DefaultDynamicHeaderBuilderTest {
181182
void testSetX509CertificateSha256ThumbprintEnabled() {
182183
def chain = TestKeys.RS256.chain
183184
Request<byte[]> request = new DefaultRequest(chain[0].getEncoded(), null, null)
184-
def x5tS256 = DefaultHashAlgorithm.SHA256.digest(request)
185+
def x5tS256 = StandardHashAlgorithms.get().SHA256.digest(request)
185186
String encoded = Encoders.BASE64URL.encode(x5tS256)
186187
def header = builder.setX509CertificateChain(chain).withX509Sha256Thumbprint(true).build() as JwsHeader
187188
assertTrue header instanceof JwsHeader

impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsHeaderBuilderTest.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import io.jsonwebtoken.impl.security.TestKeys
2121
import io.jsonwebtoken.io.Encoders
2222
import io.jsonwebtoken.security.Jwks
2323
import io.jsonwebtoken.security.Request
24+
import io.jsonwebtoken.security.StandardHashAlgorithms
2425
import org.junit.Before
2526
import org.junit.Test
2627

@@ -95,7 +96,7 @@ class DefaultJwsHeaderBuilderTest {
9596
@Test
9697
void testSetX509CertificateSha256Thumbprint() {
9798
Request<byte[]> request = new DefaultRequest(TestKeys.RS256.cert.getEncoded(), null, null)
98-
def x5tS256 = DefaultHashAlgorithm.SHA256.digest(request)
99+
def x5tS256 = StandardHashAlgorithms.get().SHA256.digest(request)
99100
String encoded = Encoders.BASE64URL.encode(x5tS256)
100101
def header = builder.setX509CertificateSha256Thumbprint(x5tS256).build()
101102
assertArrayEquals x5tS256, header.getX509CertificateSha256Thumbprint()
@@ -106,7 +107,7 @@ class DefaultJwsHeaderBuilderTest {
106107
void testSetX509CertificateSha256ThumbprintEnabled() {
107108
def chain = TestKeys.RS256.chain
108109
Request<byte[]> request = new DefaultRequest(chain[0].getEncoded(), null, null)
109-
def x5tS256 = DefaultHashAlgorithm.SHA256.digest(request)
110+
def x5tS256 = StandardHashAlgorithms.get().SHA256.digest(request)
110111
String encoded = Encoders.BASE64URL.encode(x5tS256)
111112
def header = builder.setX509CertificateChain(chain).withX509Sha256Thumbprint(true).build()
112113
assertArrayEquals x5tS256, header.getX509CertificateSha256Thumbprint()

impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class AbstractAsymmetricJwkBuilderTest {
8585
@Test
8686
void testX509CertificateSha256Thumbprint() {
8787
Request<byte[]> request = new DefaultRequest(TestKeys.RS256.cert.getEncoded(), null, null)
88-
def x5tS256 = DefaultHashAlgorithm.SHA256.digest(request)
88+
def x5tS256 = StandardHashAlgorithms.get().SHA256.digest(request)
8989
def encoded = Encoders.BASE64URL.encode(x5tS256)
9090
def jwk = builder().setX509CertificateSha256Thumbprint(x5tS256).build()
9191
assertArrayEquals x5tS256, jwk.getX509CertificateSha256Thumbprint()
@@ -95,7 +95,7 @@ class AbstractAsymmetricJwkBuilderTest {
9595
@Test
9696
void testX509CertificateSha256ThumbprintEnabled() {
9797
Request<byte[]> request = new DefaultRequest(TestKeys.RS256.cert.getEncoded(), null, null)
98-
def x5tS256 = DefaultHashAlgorithm.SHA256.digest(request)
98+
def x5tS256 = StandardHashAlgorithms.get().SHA256.digest(request)
9999
def encoded = Encoders.BASE64URL.encode(x5tS256)
100100
def jwk = builder().setX509CertificateChain(CHAIN).withX509Sha256Thumbprint(true).build()
101101
assertArrayEquals x5tS256, jwk.getX509CertificateSha256Thumbprint()

0 commit comments

Comments
 (0)