Skip to content

Commit

Permalink
Made Curve and Jwks.CRV part of the public API (#797)
Browse files Browse the repository at this point in the history
* Made Curve concept part of the public API for key generation, and added Jwks.CRV utility class to reference standard curves

- Ensured PS256, PS384, and PS512 pem-encoded test key files accurately represented the rsassa-pss algorithmId (OID) with appropriate hash/mgf1 properties.
- Removed Jwts.SIG#Ed25519 and Jwts.SIG#Ed448 since they were only there for key generation and those keys can now be generated via the Jwks.CRV#Ed25519 and Jwks.CRV#Ed448 references.
- Consolidated duplicate use/key_ops logic for checking sig/sign/verify between SecretJwkFactory and RsaPrivateJwkFactory into JwkContext.isSigUse()
- Ensured if JwkContext.isSigUse() is true, and a JWK (from values only) is RSA and RSASSA-PSS is available (JDK 11+ or BC enabled), that the JWK's generated RSAPublicKey and RSAPrivateKey use the RSASSA-PSS algorithm instead of just RSA.
- Enforced that RSASSA-PSS keys cannot be used for encryption in the RSA KeyAlgorithm implementation (would be a security risk otherwise).
- Enforced that RSA encryption keys cannot be used to create RSASSA-PSS digital signatures (but can verify them) ala the "robustness principle" (to reduce security exposure).
- Ensured README.md and JavaReadmeTest reflected Jwks.CRV usage for keypair generation.

* Added TestCertificates workaround for https://bugs.openjdk.org/browse/JDK-8242556

* Added JwtX509StringConverter workaround for https://bugs.openjdk.org/browse/JDK-8242556

* Added JwtX509StringConverter workaround for https://bugs.openjdk.org/browse/JDK-8242556

* Reverted to former RsaSignatureAlgorithm logic for PSS key validation (no prevention of rsaEncryption keys with PSS) as RFC 7520 test vectors show using a standard RSA key to compute a PSS signature in https://www.rfc-editor.org/rfc/rfc7520.html#section-4.2.1

* Ensured Jwk tests that used RSASSA-PSS keys (from openssl files) used the BC provider since RSASSA-PSS isn't available natively before JDK 11

* Restored TestCertificates logic needed to address JDK 11 bug during tests https://bugs.openjdk.org/browse/JDK-8213363 (fixed in JDK 12+)
  • Loading branch information
lhazlewood authored Aug 17, 2023
1 parent c142fb5 commit 620cc5d
Show file tree
Hide file tree
Showing 90 changed files with 2,133 additions and 1,282 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,20 @@ deprecate some concepts, or in some cases, completely break backwards compatibil


* `io.jsonwebtoken.JwtParser` is now immutable. All mutation/modification methods (setters, etc) deprecated 4 years
ago have been removed. All parser configuration requires using the `JwtParserBuilder` (i.e.
`Jwts.parser()`).
ago have been removed. All parser configuration requires using the `JwtParserBuilder`.


* Similarly, `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a
`JwtParserBuilder` instead of a direct `JwtParser` instance. The previous `Jwts.parserBuilder()` method has been
removed as it is now redundant.


* The `JwtParserBuilder` no longer supports `PrivateKey`s for signature verification. This was an old
legacy behavior scheduled for removal years ago, and that change is now complete. For various cryptographic/security
reasons, asymmetric public/private key signatures should always be created with `PrivateKey`s and verified with
`PublicKey`s.


* `io.jsonwebtoken.CompressionCodec` implementations are no longer discoverable via `java.util.ServiceLoader` due to
runtime performance problems with the JDK's `ServiceLoader` implementation per
https://github.com/jwtk/jjwt/issues/648. Custom implementations should be made available to the `JwtParser` via
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3477,7 +3477,7 @@ Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519,
`OctetPublicJwk` interface names):

```java
PublicKey key = Jwts.SIG.Ed25519.keyPair().build().getPublic();
PublicKey key = Jwks.CRV.Ed25519.keyPair().build().getPublic();
OctetPublicJwk<PublicKey> jwk = builder().octetKey(key).idFromThumbprint().build();

assert jwk.getId().equals(jwk.thumbprint().toString());
Expand All @@ -3499,7 +3499,7 @@ Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519,
`OctetPrivateJwk` and `OctetPublicJwk` interface names):

```java
KeyPair pair = Jwts.SIG.Ed448.keyPair().build();
KeyPair pair = Jwks.CRV.Ed448.keyPair().build();
PublicKey pubKey = pair.getPublic();
PrivateKey privKey = pair.getPrivate();

Expand Down
5 changes: 5 additions & 0 deletions api/src/main/java/io/jsonwebtoken/Identifiable.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
* parameter value.</td>
* </tr>
* <tr>
* <td>{@link io.jsonwebtoken.security.Curve Curve}</td>
* <td>JWK's <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1">{@code crv} (Curve)</a>
* parameter value.</td>
* </tr>
* <tr>
* <td>{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}</td>
* <td>JWE protected header's
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3">{@code zip} (Compression Algorithm)</a>
Expand Down
10 changes: 2 additions & 8 deletions api/src/main/java/io/jsonwebtoken/JwtBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -527,14 +527,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <tr>
* <td><a href="https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/security/interfaces/EdECKey.html">EdECKey</a><sup>7</sup></td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>256</td>
* <td>{@link Jwts.SIG#Ed25519 Ed25519}</td>
* </tr>
* <tr>
* <td><a href="https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/security/interfaces/EdECKey.html">EdECKey</a><sup>7</sup></td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>456</td>
* <td>{@link Jwts.SIG#Ed448 Ed448}</td>
* <td>256 || 456</td>
* <td>{@link Jwts.SIG#EdDSA EdDSA}</td>
* </tr>
* </tbody>
* </table>
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
* {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name
* congruence with the {@link #decryptWith(SecretKey)} method.</p>
*
* <p>This method merely delegates directly to {@link #verifyWith(SecretKey)}.</p>
* <p>This method merely delegates directly to {@link #verifyWith(SecretKey) or {@link #verifyWith(PublicKey)}}.</p>
*
* @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital
* signatures.
Expand Down
41 changes: 17 additions & 24 deletions api/src/main/java/io/jsonwebtoken/Jwts.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyPairBuilderSupplier;
import io.jsonwebtoken.security.MacAlgorithm;
import io.jsonwebtoken.security.Password;
import io.jsonwebtoken.security.SecretKeyAlgorithm;
Expand Down Expand Up @@ -293,33 +294,25 @@ private SIG() {
public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512");

/**
* {@code EdDSA} signature algorithm as defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>. This algorithm
* requires either {@code Ed25519} or {@code Ed448} Edwards Curve keys.
* <p><b>This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* {@code EdDSA} signature algorithm defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a> that requires
* either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve<sup><b>1</b></sup> keys.
*
* <p><b>KeyPair Generation</b></p>
*
* <p>This instance's {@link KeyPairBuilderSupplier#keyPair() keyPair()} builder creates {@code Ed448} keys,
* and is essentially an alias for
* <code>{@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed448 Ed448}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}</code>.</p>
*
* <p>If you would like to generate an {@code Ed25519} {@code KeyPair} for use with the {@code EdDSA} algorithm,
* you may use the
* <code>{@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed25519 Ed25519}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}</code>
* builder instead.</p>
*
* <p><b><sup>1</sup>This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath.</b></p>
*/
public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA");

/**
* {@code EdDSA} signature algorithm using Curve {@code Ed25519} as defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>. This algorithm
* requires {@code Ed25519} Edwards Curve keys to create signatures. <b>This is a convenience alias for
* {@link #EdDSA}</b> that defaults key generation to {@code Ed25519} keys.
* <p><b>This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath.</b></p>
*/
public static final SignatureAlgorithm Ed25519 = Jwts.get(REGISTRY, "Ed25519");

/**
* {@code EdDSA} signature algorithm using Curve {@code Ed448} as defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>. This algorithm
* requires {@code Ed448} Edwards Curve keys to create signatures. <b>This is a convenience alias for
* {@link #EdDSA}</b> that defaults key generation to {@code Ed448} keys.
* <p><b>This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath.</b></p>
*/
public static final SignatureAlgorithm Ed448 = Jwts.get(REGISTRY, "Ed448");
}

/**
Expand Down
45 changes: 33 additions & 12 deletions api/src/main/java/io/jsonwebtoken/lang/Assert.java
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public static void notEmpty(Map map) {
* Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
*
* @param <T> the type of instance expected
* @param <T> the type of instance expected
* @param clazz the required class
* @param obj the object to check
* @return the expected instance of type {@code T}
Expand Down Expand Up @@ -423,35 +423,56 @@ public static void isAssignable(Class superType, Class subType, String message)
* an {@link IllegalArgumentException} with the given message if not.
*
* @param <T> the type of argument
* @param requirement the integer that {@code value} must be greater than
* @param value the value to check
* @param requirement the requirement that {@code value} must be greater than
* @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION
*/
public static <T extends Number> T eq(T requirement, T value, String msg) {
notNull(requirement, "requirement cannot be null.");
notNull(value, "value cannot be null.");
if (!requirement.equals(value)) {
public static <T extends Comparable<T>> T eq(T value, T requirement, String msg) {
if (compareTo(value, requirement) != 0) {
throw new IllegalArgumentException(msg);
}
return value;
}

private static <T extends Comparable<T>> int compareTo(T value, T requirement) {
notNull(value, "value cannot be null.");
notNull(requirement, "requirement cannot be null.");
return value.compareTo(requirement);
}

/**
* Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing
* an {@link IllegalArgumentException} with the given message if not.
*
* @param <T> the type of value to check and return if the requirement is met
* @param value the value to check
* @param requirement the integer that {@code value} must be greater than
* @param requirement the requirement that {@code value} must be greater than
* @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION
*/
public static Integer gt(Integer value, Integer requirement, String msg) {
notNull(value, "value cannot be null.");
notNull(requirement, "requirement cannot be null.");
if (!(value > requirement)) {
public static <T extends Comparable<T>> T gt(T value, T requirement, String msg) {
if (!(compareTo(value, requirement) > 0)) {
throw new IllegalArgumentException(msg);
}
return value;
}

/**
* Asserts that a specified {@code value} is less than or equal to the given {@code requirement}, throwing
* an {@link IllegalArgumentException} with the given message if not.
*
* @param <T> the type of value to check and return if the requirement is met
* @param value the value to check
* @param requirement the requirement that {@code value} must be greater than
* @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION
*/
public static <T extends Comparable<T>> T lte(T value, T requirement, String msg) {
if (compareTo(value, requirement) > 0) {
throw new IllegalArgumentException(msg);
}
return value;
Expand Down Expand Up @@ -495,7 +516,7 @@ public static void state(boolean expression) {
*
* @param value value to assert is not null
* @param msg exception message to use if {@code value} is null
* @param <T> value type
* @param <T> value type
* @return the non-null value
* @throws IllegalStateException with the specified {@code msg} if {@code value} is null.
* @since JJWT_RELEASE_VERSION
Expand Down
41 changes: 41 additions & 0 deletions api/src/main/java/io/jsonwebtoken/security/Curve.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright © 2022 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.security;

import io.jsonwebtoken.Identifiable;

/**
* A cryptographic Elliptic Curve for use with digital signature or key agreement algorithms.
*
* <p><b>Curve Identifier</b></p>
*
* <p>This interface extends {@link Identifiable}; the value returned from {@link #getId()} will
* be used as the JWK
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1"><code>crv</code></a> value.</p>
*
* <p><b>KeyPair Generation</b></p>
*
* <p>A secure-random KeyPair of sufficient strength on the curve may be obtained with its {@link #keyPair()} builder.</p>
*
* <p><b>Standard Implementations</b></p>
*
* <p>Constants for all JWA standard Curves are available via the {@link Jwks.CRV} registry.</p>
*
* @see Jwks.CRV
* @since JJWT_RELEASE_VERSION
*/
public interface Curve extends Identifiable, KeyPairBuilderSupplier {
}
115 changes: 115 additions & 0 deletions api/src/main/java/io/jsonwebtoken/security/Jwks.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,121 @@ private Jwks() {

private static final String PARSERBUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder";

/**
* Constants for all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}
* defined by <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-7.6">RFC 7518, Section 7.6</a>
* (for Weierstrass Elliptic Curves) and
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-5">RFC 8037, Section 5</a> (for Edwards Elliptic Curves).
* Each standard algorithm is available as a
* ({@code public static final}) constant for direct type-safe reference in application code. For example:
* <blockquote><pre>
* Jwks.CRV.P256.keyPair().build();</pre></blockquote>
* <p>They are also available together as a {@link Registry} instance via the {@link #get()} method.</p>
*
* @see #get()
* @since JJWT_RELEASE_VERSION
*/
public static final class CRV {

private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardCurves";
private static final Registry<String, Curve> REGISTRY = Classes.newInstance(IMPL_CLASSNAME);

/**
* Returns a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}
* defined by <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-7.6">RFC 7518, Section 7.6</a>
* (for Weierstrass Elliptic Curves) and
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-5">RFC 8037, Section 5</a> (for Edwards Elliptic Curves).
*
* @return a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}.
*/
public static Registry<String, Curve> get() {
return REGISTRY;
}

/**
* {@code P-256} Elliptic Curve defined by
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1">RFC 7518, Section 6.2.1.1</a>
* using the native Java JCA {@code secp256r1} algorithm.
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve P256 = get().forKey("P-256");

/**
* {@code P-384} Elliptic Curve defined by
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1">RFC 7518, Section 6.2.1.1</a>
* using the native Java JCA {@code secp384r1} algorithm.
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve P384 = get().forKey("P-384");

/**
* {@code P-521} Elliptic Curve defined by
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1">RFC 7518, Section 6.2.1.1</a>
* using the native Java JCA {@code secp521r1} algorithm.
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve P521 = get().forKey("P-521");

/**
* {@code Ed25519} Elliptic Curve defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>
* using the native Java JCA {@code Ed25519}<b><sup>1</sup></b> algorithm.
*
* <p><b><sup>1</sup></b> Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime
* classpath.</p>
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve Ed25519 = get().forKey("Ed25519");

/**
* {@code Ed448} Elliptic Curve defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>
* using the native Java JCA {@code Ed448}<b><sup>1</sup></b> algorithm.
*
* <p><b><sup>1</sup></b> Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime
* classpath.</p>
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve Ed448 = get().forKey("Ed448");

/**
* {@code X25519} Elliptic Curve defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.2">RFC 8037, Section 3.2</a>
* using the native Java JCA {@code X25519}<b><sup>1</sup></b> algorithm.
*
* <p><b><sup>1</sup></b> Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime
* classpath.</p>
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve X25519 = get().forKey("X25519");

/**
* {@code X448} Elliptic Curve defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.2">RFC 8037, Section 3.2</a>
* using the native Java JCA {@code X448}<b><sup>1</sup></b> algorithm.
*
* <p><b><sup>1</sup></b> Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime
* classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime
* classpath.</p>
*
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html">Java Security Standard Algorithm Names</a>
*/
public static final Curve X448 = get().forKey("X448");

//prevent instantiation
private CRV() {
}
}

/**
* Various (<em>but not all</em>)
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA Hash
Expand Down
Loading

0 comments on commit 620cc5d

Please sign in to comment.