Made Curve and Jwks.CRV part of the public API (#797)

* 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+)
This commit is contained in:
lhazlewood 2023-08-17 15:21:54 -07:00 committed by GitHub
parent c142fb5c7a
commit 620cc5d97f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 2132 additions and 1281 deletions

View File

@ -186,8 +186,7 @@ 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 * `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. ago have been removed. All parser configuration requires using the `JwtParserBuilder`.
`Jwts.parser()`).
* Similarly, `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a * Similarly, `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a
@ -195,6 +194,12 @@ deprecate some concepts, or in some cases, completely break backwards compatibil
removed as it is now redundant. 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 * `io.jsonwebtoken.CompressionCodec` implementations are no longer discoverable via `java.util.ServiceLoader` due to
runtime performance problems with the JDK's `ServiceLoader` implementation per 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 https://github.com/jwtk/jjwt/issues/648. Custom implementations should be made available to the `JwtParser` via

View File

@ -3477,7 +3477,7 @@ Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519,
`OctetPublicJwk` interface names): `OctetPublicJwk` interface names):
```java ```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(); OctetPublicJwk<PublicKey> jwk = builder().octetKey(key).idFromThumbprint().build();
assert jwk.getId().equals(jwk.thumbprint().toString()); assert jwk.getId().equals(jwk.thumbprint().toString());
@ -3499,7 +3499,7 @@ Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519,
`OctetPrivateJwk` and `OctetPublicJwk` interface names): `OctetPrivateJwk` and `OctetPublicJwk` interface names):
```java ```java
KeyPair pair = Jwts.SIG.Ed448.keyPair().build(); KeyPair pair = Jwks.CRV.Ed448.keyPair().build();
PublicKey pubKey = pair.getPublic(); PublicKey pubKey = pair.getPublic();
PrivateKey privKey = pair.getPrivate(); PrivateKey privKey = pair.getPrivate();

View File

@ -40,6 +40,11 @@ package io.jsonwebtoken;
* parameter value.</td> * parameter value.</td>
* </tr> * </tr>
* <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>{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}</td>
* <td>JWE protected header's * <td>JWE protected header's
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3">{@code zip} (Compression Algorithm)</a> * <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3">{@code zip} (Compression Algorithm)</a>

View File

@ -527,14 +527,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <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><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><code>instanceof {@link PrivateKey}</code></td>
* <td>256</td> * <td>256 || 456</td>
* <td>{@link Jwts.SIG#Ed25519 Ed25519}</td> * <td>{@link Jwts.SIG#EdDSA EdDSA}</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>
* </tr> * </tr>
* </tbody> * </tbody>
* </table> * </table>

View File

@ -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 * {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name
* congruence with the {@link #decryptWith(SecretKey)} method.</p> * 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 * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital
* signatures. * signatures.

View File

@ -21,6 +21,7 @@ import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyPairBuilderSupplier;
import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.MacAlgorithm;
import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.Password;
import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.SecretKeyAlgorithm;
@ -293,33 +294,25 @@ public final class Jwts {
public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512"); public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512");
/** /**
* {@code EdDSA} signature algorithm as defined by * {@code EdDSA} signature algorithm defined by
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a>. This algorithm * <a href="https://www.rfc-editor.org/rfc/rfc8037#section-3.1">RFC 8037, Section 3.1</a> that requires
* requires either {@code Ed25519} or {@code Ed448} Edwards Curve keys. * either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve<sup><b>1</b></sup> keys.
* <p><b>This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime *
* <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> * classpath.</b></p>
*/ */
public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA"); 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");
} }
/** /**

View File

@ -351,7 +351,7 @@ public final class Assert {
* Assert that the provided object is an instance of the provided class. * Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre> * <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 clazz the required class
* @param obj the object to check * @param obj the object to check
* @return the expected instance of type {@code T} * @return the expected instance of type {@code T}
@ -423,35 +423,56 @@ public final class Assert {
* an {@link IllegalArgumentException} with the given message if not. * an {@link IllegalArgumentException} with the given message if not.
* *
* @param <T> the type of argument * @param <T> the type of argument
* @param requirement the integer that {@code value} must be greater than
* @param value the value to check * @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. * @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}. * @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public static <T extends Number> T eq(T requirement, T value, String msg) { public static <T extends Comparable<T>> T eq(T value, T requirement, String msg) {
notNull(requirement, "requirement cannot be null."); if (compareTo(value, requirement) != 0) {
notNull(value, "value cannot be null.");
if (!requirement.equals(value)) {
throw new IllegalArgumentException(msg); throw new IllegalArgumentException(msg);
} }
return value; 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 * Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing
* an {@link IllegalArgumentException} with the given message if not. * 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 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. * @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}. * @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public static Integer gt(Integer value, Integer requirement, String msg) { public static <T extends Comparable<T>> T gt(T value, T requirement, String msg) {
notNull(value, "value cannot be null."); if (!(compareTo(value, requirement) > 0)) {
notNull(requirement, "requirement cannot be null."); throw new IllegalArgumentException(msg);
if (!(value > requirement)) { }
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); throw new IllegalArgumentException(msg);
} }
return value; return value;
@ -495,7 +516,7 @@ public final class Assert {
* *
* @param value value to assert is not null * @param value value to assert is not null
* @param msg exception message to use if {@code value} is 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 * @return the non-null value
* @throws IllegalStateException with the specified {@code msg} if {@code value} is null. * @throws IllegalStateException with the specified {@code msg} if {@code value} is null.
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION

View File

@ -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 {
}

View File

@ -44,6 +44,121 @@ public final class Jwks {
private static final String PARSERBUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder"; 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>) * Various (<em>but not all</em>)
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA Hash * <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA Hash

View File

@ -84,7 +84,7 @@ public class OrgJsonSerializer<T> implements Serializer<T> {
} }
if (object instanceof Supplier) { if (object instanceof Supplier) {
object = ((Supplier<?>)object).get(); object = ((Supplier<?>) object).get();
} }
if (object instanceof JSONObject || object instanceof JSONArray if (object instanceof JSONObject || object instanceof JSONArray
@ -163,6 +163,12 @@ public class OrgJsonSerializer<T> implements Serializer<T> {
return array; return array;
} }
/**
* Serializes the specified org.json instance a byte array.
*
* @param o the org.json instance to serialize
* @return the JSON byte array
*/
@SuppressWarnings("WeakerAccess") //for testing @SuppressWarnings("WeakerAccess") //for testing
protected byte[] toBytes(Object o) { protected byte[] toBytes(Object o) {
String s; String s;

View File

@ -31,6 +31,8 @@
<properties> <properties>
<jjwt.root>${basedir}/..</jjwt.root> <jjwt.root>${basedir}/..</jjwt.root>
<!-- This module is not intended for direct use or extension, no need to worry about perfect JavaDoc: -->
<maven.javadoc.additionalOptions>-Xdoclint:none</maven.javadoc.additionalOptions>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -27,6 +27,7 @@ import io.jsonwebtoken.impl.security.DefaultAeadRequest;
import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultSecureRequest; import io.jsonwebtoken.impl.security.DefaultSecureRequest;
import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm; import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm;
import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms;
import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoder;
@ -49,6 +50,7 @@ import io.jsonwebtoken.security.SecureDigestAlgorithm;
import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecureRequest;
import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SecurityException;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.security.UnsupportedKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -179,10 +181,21 @@ public class DefaultJwtBuilder implements JwtBuilder {
return this.headerBuilder.add(name, value).and(); return this.headerBuilder.add(name, value).and();
} }
@SuppressWarnings("unchecked") // TODO: remove for 1.0 @SuppressWarnings({"unchecked", "deprecation"}) // TODO: remove for 1.0
protected static <K extends Key> SecureDigestAlgorithm<K, ?> forSigningKey(K key) { protected static <K extends Key> SecureDigestAlgorithm<K, ?> forSigningKey(K key) {
@SuppressWarnings("deprecation") io.jsonwebtoken.SignatureAlgorithm alg = io.jsonwebtoken.SignatureAlgorithm.forSigningKey(key); Assert.notNull(key, "Key cannot be null.");
return (SecureDigestAlgorithm<K, ?>) Jwts.SIG.get().forKey(alg.getValue()); SecureDigestAlgorithm<K, ?> alg = StandardSecureDigestAlgorithms.findBySigningKey(key);
if (alg == null) {
String msg = "Unable to determine a suitable MAC or Signature algorithm for the specified key using " +
"available heuristics: either the key size is too weak be used with available algorithms, or the " +
"key size is unavailable (e.g. if using a PKCS11 or HSM (Hardware Security Module) key store). " +
"If you are using a PKCS11 or HSM keystore, consider using the " +
"JwtBuilder.signWith(Key, SecureDigestAlgorithm) method instead.";
throw new UnsupportedKeyException(msg);
}
return alg;
// io.jsonwebtoken.SignatureAlgorithm dalg = io.jsonwebtoken.SignatureAlgorithm.forSigningKey(key);
//return (SecureDigestAlgorithm<K, ?>) Jwts.SIG.get().forKey(dalg.getValue());
} }
@Override @Override
@ -195,7 +208,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
@Override @Override
public <K extends Key> JwtBuilder signWith(K key, final SecureDigestAlgorithm<? super K, ?> alg) throws InvalidKeyException { public <K extends Key> JwtBuilder signWith(K key, final SecureDigestAlgorithm<? super K, ?> alg) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null."); Assert.notNull(key, "Key argument cannot be null.");
if (key instanceof PublicKey) { // it's always wrong to try to create signatures with PublicKeys: if (key instanceof PublicKey) { // it's always wrong/insecure to try to create signatures with PublicKeys:
throw new IllegalArgumentException(PUB_KEY_SIGN_MSG); throw new IllegalArgumentException(PUB_KEY_SIGN_MSG);
} }
// Implementation note: Ordinarily Passwords should not be used to create secure digests because they usually // Implementation note: Ordinarily Passwords should not be used to create secure digests because they usually

View File

@ -35,6 +35,7 @@ import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm;
import io.jsonwebtoken.security.UnsupportedKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.security.Key; import java.security.Key;
@ -229,7 +230,14 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
@Override @Override
public JwtParserBuilder setSigningKey(final Key key) { public JwtParserBuilder setSigningKey(final Key key) {
return verifyWith(key); if (key instanceof SecretKey) {
return verifyWith((SecretKey) key);
} else if (key instanceof PublicKey) {
return verifyWith((PublicKey) key);
}
String msg = "JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey " +
"(for Signature algorithms).";
throw new UnsupportedKeyException(msg);
} }
@Override @Override

View File

@ -82,7 +82,7 @@ public final class Conditions {
Object value = null; Object value = null;
try { try {
value = supplier.get(); value = supplier.get();
} catch (Exception ignored) { } catch (Throwable ignored) {
} }
return value != null; return value != null;
} }

View File

@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Converters; import io.jsonwebtoken.impl.lang.Converters;
import io.jsonwebtoken.impl.lang.Field; import io.jsonwebtoken.impl.lang.Field;
import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Curve;
import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.UnsupportedKeyException; import io.jsonwebtoken.security.UnsupportedKeyException;

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Field; import io.jsonwebtoken.impl.lang.Field;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.KeyException; import io.jsonwebtoken.security.KeyException;
@ -73,8 +74,14 @@ abstract class AbstractFamilyJwkFactory<K extends Key, J extends Jwk<K>> impleme
return generateKey(ctx, this.keyType, fn); return generateKey(ctx, this.keyType, fn);
} }
protected String getKeyFactoryJcaName(final JwkContext<?> ctx) {
String jcaName = KeysBridge.findAlgorithm(ctx.getKey());
return Strings.hasText(jcaName) ? jcaName : getId();
}
protected <T extends Key> T generateKey(final JwkContext<?> ctx, final Class<T> type, final CheckedFunction<KeyFactory, T> fn) { protected <T extends Key> T generateKey(final JwkContext<?> ctx, final Class<T> type, final CheckedFunction<KeyFactory, T> fn) {
JcaTemplate template = new JcaTemplate(getId(), ctx.getProvider(), ctx.getRandom()); String jcaName = getKeyFactoryJcaName(ctx);
JcaTemplate template = new JcaTemplate(jcaName, ctx.getProvider(), ctx.getRandom());
return template.withKeyFactory(new CheckedFunction<KeyFactory, T>() { return template.withKeyFactory(new CheckedFunction<KeyFactory, T>() {
@Override @Override
public T apply(KeyFactory instance) { public T apply(KeyFactory instance) {

View File

@ -41,7 +41,7 @@ abstract class AbstractSecureDigestAlgorithm<S extends Key, V extends Key> exten
@Override @Override
public final byte[] digest(SecureRequest<byte[], S> request) throws SecurityException { public final byte[] digest(SecureRequest<byte[], S> request) throws SecurityException {
Assert.notNull(request, "Request cannot be null."); Assert.notNull(request, "Request cannot be null.");
final S key = Assert.notNull(request.getKey(), "Request key cannot be null."); final S key = Assert.notNull(request.getKey(), "Signing key cannot be null.");
Assert.notEmpty(request.getPayload(), "Request content cannot be null or empty."); Assert.notEmpty(request.getPayload(), "Request content cannot be null or empty.");
try { try {
validateKey(key, true); validateKey(key, true);
@ -60,7 +60,7 @@ abstract class AbstractSecureDigestAlgorithm<S extends Key, V extends Key> exten
@Override @Override
public final boolean verify(VerifySecureDigestRequest<V> request) throws SecurityException { public final boolean verify(VerifySecureDigestRequest<V> request) throws SecurityException {
Assert.notNull(request, "Request cannot be null."); Assert.notNull(request, "Request cannot be null.");
final V key = Assert.notNull(request.getKey(), "Request key cannot be null."); final V key = Assert.notNull(request.getKey(), "Verification key cannot be null.");
Assert.notEmpty(request.getPayload(), "Request content cannot be null or empty."); Assert.notEmpty(request.getPayload(), "Request content cannot be null or empty.");
Assert.notEmpty(request.getDigest(), "Request signature byte array cannot be null or empty."); Assert.notEmpty(request.getDigest(), "Request signature byte array cannot be null or empty.");
try { try {

View File

@ -33,7 +33,7 @@ abstract class AbstractSignatureAlgorithm extends AbstractSecureDigestAlgorithm<
private static final String KEY_TYPE_MSG_PATTERN = private static final String KEY_TYPE_MSG_PATTERN =
"{0} {1} keys must be {2}s (implement {3}). Provided key type: {4}."; "{0} {1} keys must be {2}s (implement {3}). Provided key type: {4}.";
protected AbstractSignatureAlgorithm(String id, String jcaName) { AbstractSignatureAlgorithm(String id, String jcaName) {
super(id, jcaName); super(id, jcaName);
} }

View File

@ -21,6 +21,7 @@ import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.Curve;
import java.security.spec.EllipticCurve; import java.security.spec.EllipticCurve;
import java.util.Collection; import java.util.Collection;
@ -33,7 +34,7 @@ public final class Curves {
private static final Collection<ECCurve> EC_CURVES = Collections.setOf((ECCurve) P_256, (ECCurve) P_384, (ECCurve) P_521); private static final Collection<ECCurve> EC_CURVES = Collections.setOf((ECCurve) P_256, (ECCurve) P_384, (ECCurve) P_521);
private static final Collection<Curve> VALUES = new LinkedHashSet<>(); static final Collection<Curve> VALUES = new LinkedHashSet<>();
static { static {
VALUES.addAll(EC_CURVES); VALUES.addAll(EC_CURVES);

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Curve;
import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.KeyPairBuilder;
import java.security.Provider; import java.security.Provider;

View File

@ -209,6 +209,20 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
return this; return this;
} }
@Override
public boolean isSigUse() {
// Even though 'use' is for PUBLIC KEY use (as defined in RFC 7515), RFC 7520 shows secret keys with
// 'use' values, so we'll account for that as well:
if ("sig".equals(getPublicKeyUse())) {
return true;
}
Set<String> ops = getOperations();
if (Collections.isEmpty(ops)) {
return false;
}
return ops.contains("sign") || ops.contains("verify");
}
@Override @Override
public K getKey() { public K getKey() {
return this.key; return this.key;

View File

@ -29,39 +29,51 @@ import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.security.Key; import java.security.Key;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey, SecretKey> implements MacAlgorithm { final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey, SecretKey> implements MacAlgorithm {
private final int minKeyBitLength; //in bits private static final String HS256_OID = "1.2.840.113549.2.9";
private static final String HS384_OID = "1.2.840.113549.2.10";
private static final String HS512_OID = "1.2.840.113549.2.11";
private static final Set<String> JWA_STANDARD_IDS = new LinkedHashSet<>(Collections.of("HS256", "HS384", "HS512")); private static final Set<String> JWA_STANDARD_IDS = new LinkedHashSet<>(Collections.of("HS256", "HS384", "HS512"));
// PKCS12 OIDs are added to these lists per https://bugs.openjdk.java.net/browse/JDK-8243551 static final DefaultMacAlgorithm HS256 = new DefaultMacAlgorithm(256);
private static final Set<String> HS256_JCA_NAMES = new LinkedHashSet<>(Collections.of("HMACSHA256", "1.2.840.113549.2.9")); static final DefaultMacAlgorithm HS384 = new DefaultMacAlgorithm(384);
private static final Set<String> HS384_JCA_NAMES = new LinkedHashSet<>(Collections.of("HMACSHA384", "1.2.840.113549.2.10")); static final DefaultMacAlgorithm HS512 = new DefaultMacAlgorithm(512);
private static final Set<String> HS512_JCA_NAMES = new LinkedHashSet<>(Collections.of("HMACSHA512", "1.2.840.113549.2.11"));
private static final Set<String> VALID_HS256_JCA_NAMES; private static final Map<String, MacAlgorithm> JCA_NAME_MAP;
private static final Set<String> VALID_HS384_JCA_NAMES;
static { static {
VALID_HS384_JCA_NAMES = new LinkedHashSet<>(HS384_JCA_NAMES); JCA_NAME_MAP = new LinkedHashMap<>(6);
VALID_HS384_JCA_NAMES.addAll(HS512_JCA_NAMES);
VALID_HS256_JCA_NAMES = new LinkedHashSet<>(HS256_JCA_NAMES); // In addition to JCA names, PKCS12 OIDs are added to these per
VALID_HS256_JCA_NAMES.addAll(VALID_HS384_JCA_NAMES); // https://bugs.openjdk.java.net/browse/JDK-8243551 as well:
JCA_NAME_MAP.put(HS256.getJcaName().toUpperCase(Locale.ENGLISH), HS256); // for case-insensitive lookup
JCA_NAME_MAP.put(HS256_OID, HS256);
JCA_NAME_MAP.put(HS384.getJcaName().toUpperCase(Locale.ENGLISH), HS384);
JCA_NAME_MAP.put(HS384_OID, HS384);
JCA_NAME_MAP.put(HS512.getJcaName().toUpperCase(Locale.ENGLISH), HS512);
JCA_NAME_MAP.put(HS512_OID, HS512);
} }
public DefaultMacAlgorithm(int digestBitLength) { private final int minKeyBitLength; //in bits
private DefaultMacAlgorithm(int digestBitLength) {
this("HS" + digestBitLength, "HmacSHA" + digestBitLength, digestBitLength); this("HS" + digestBitLength, "HmacSHA" + digestBitLength, digestBitLength);
} }
public DefaultMacAlgorithm(String id, String jcaName, int minKeyBitLength) { DefaultMacAlgorithm(String id, String jcaName, int minKeyBitLength) {
super(id, jcaName); super(id, jcaName);
Assert.isTrue(minKeyBitLength > 0, "minKeyLength must be greater than zero."); Assert.isTrue(minKeyBitLength > 0, "minKeyLength must be greater than zero.");
this.minKeyBitLength = minKeyBitLength; this.minKeyBitLength = minKeyBitLength;
@ -76,10 +88,36 @@ public class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey
return JWA_STANDARD_IDS.contains(getId()); return JWA_STANDARD_IDS.contains(getId());
} }
private boolean isJwaStandardJcaName(String jcaName) { private static boolean isJwaStandardJcaName(String jcaName) {
return VALID_HS256_JCA_NAMES.contains(jcaName.toUpperCase(Locale.ENGLISH)); String key = jcaName.toUpperCase(Locale.ENGLISH);
return JCA_NAME_MAP.containsKey(key);
} }
static MacAlgorithm findByKey(Key key) {
String alg = KeysBridge.findAlgorithm(key);
if (!Strings.hasText(alg)) {
return null;
}
String upper = alg.toUpperCase(Locale.ENGLISH);
MacAlgorithm mac = JCA_NAME_MAP.get(upper);
if (mac == null) {
return null;
}
// even though we found a standard alg based on the JCA name, we need to confirm that the key length is
// sufficient if the encoded key bytes are available:
byte[] encoded = KeysBridge.findEncoded(key);
long size = Bytes.bitLength(encoded);
if (size >= mac.getKeyBitLength()) {
return mac;
}
return null; // couldn't find a suitable match
}
@Override @Override
public SecretKeyBuilder key() { public SecretKeyBuilder key() {
return new DefaultSecretKeyBuilder(getJcaName(), getKeyBitLength()); return new DefaultSecretKeyBuilder(getJcaName(), getKeyBitLength());
@ -91,11 +129,11 @@ public class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey
final String keyType = keyType(signing); final String keyType = keyType(signing);
if (k == null) { if (k == null) {
throw new IllegalArgumentException("Signature " + keyType + " key cannot be null."); throw new IllegalArgumentException("MAC " + keyType + " key cannot be null.");
} }
if (!(k instanceof SecretKey)) { if (!(k instanceof SecretKey)) {
String msg = "MAC " + keyType(signing) + " keys must be SecretKey instances. Specified key is of type " + String msg = "MAC " + keyType + " keys must be SecretKey instances. Specified key is of type " +
k.getClass().getName(); k.getClass().getName();
throw new InvalidKeyException(msg); throw new InvalidKeyException(msg);
} }
@ -116,24 +154,13 @@ public class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey
"HmacSHA* algorithm name or PKCS12 OID and cannot be used with " + id + "."); "HmacSHA* algorithm name or PKCS12 OID and cannot be used with " + id + ".");
} }
byte[] encoded = null; int size = KeysBridge.findBitLength(key);
// https://github.com/jwtk/jjwt/issues/478 // We can only perform length validation if key bit length is available
//
// Some KeyStore implementations (like Hardware Security Modules and later versions of Android) will not allow
// applications or libraries to obtain the secret key's encoded bytes. In these cases, key length assertions
// cannot be made, so we'll need to skip the key length checks if so.
try {
encoded = key.getEncoded();
} catch (Exception ignored) {
}
// We can only perform length validation if key.getEncoded() is not null or does not throw an exception
// per https://github.com/jwtk/jjwt/issues/478 and https://github.com/jwtk/jjwt/issues/619 // per https://github.com/jwtk/jjwt/issues/478 and https://github.com/jwtk/jjwt/issues/619
// so return early if we can't: // so return early if we can't:
if (encoded == null) return; if (size < 0) return;
int size = (int) Bytes.bitLength(encoded);
if (size < this.minKeyBitLength) { if (size < this.minKeyBitLength) {
String msg = "The " + keyType + " key's size is " + size + " bits which " + String msg = "The " + keyType + " key's size is " + size + " bits which " +
"is not secure enough for the " + id + " algorithm."; "is not secure enough for the " + id + " algorithm.";

View File

@ -22,6 +22,7 @@ import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyRequest;
import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.KeyResult;
import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SecurityException;
import io.jsonwebtoken.security.UnsupportedKeyException;
import io.jsonwebtoken.security.WeakKeyException; import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -56,6 +57,13 @@ public class DefaultRsaKeyAlgorithm extends CryptoAlgorithm implements KeyAlgori
} }
protected void validate(Key key, boolean encryption) { // true = encryption, false = decryption protected void validate(Key key, boolean encryption) { // true = encryption, false = decryption
if (RsaSignatureAlgorithm.isPss(key)) {
String msg = "RSASSA-PSS keys may not be used for " + keyType(encryption) +
", only digital signature algorithms.";
throw new UnsupportedKeyException(msg);
}
// Some PKCS11 providers and HSMs won't expose the RSAKey interface, so we have to check to see if we can cast // Some PKCS11 providers and HSMs won't expose the RSAKey interface, so we have to check to see if we can cast
// If so, we can provide additional safety checks: // If so, we can provide additional safety checks:
if (key instanceof RSAKey) { if (key instanceof RSAKey) {

View File

@ -19,9 +19,11 @@ import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.KeyPairBuilder;
import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecureRequest;
import io.jsonwebtoken.security.SignatureAlgorithm;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.VerifySecureDigestRequest;
@ -33,19 +35,28 @@ import java.security.Signature;
import java.security.interfaces.ECKey; import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec; import java.security.spec.ECGenParameterSpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
// @since JJWT_RELEASE_VERSION // @since JJWT_RELEASE_VERSION
public class EcSignatureAlgorithm extends AbstractSignatureAlgorithm { final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
private static final String REQD_ORDER_BIT_LENGTH_MSG = "orderBitLength must equal 256, 384, or 521."; private static final String REQD_ORDER_BIT_LENGTH_MSG = "orderBitLength must equal 256, 384, or 521.";
private static final String DER_ENCODING_SYS_PROPERTY_NAME = private static final String DER_ENCODING_SYS_PROPERTY_NAME =
"io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported"; "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported";
private static final String ES256_OID = "1.2.840.10045.4.3.2";
private static final String ES384_OID = "1.2.840.10045.4.3.3";
private static final String ES512_OID = "1.2.840.10045.4.3.4";
private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS; private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS;
private final int orderBitLength; private final int orderBitLength;
private final String OID;
/** /**
* JWA EC (concat formatted) length in bytes for this instance's {@link #orderBitLength}. * JWA EC (concat formatted) length in bytes for this instance's {@link #orderBitLength}.
*/ */
@ -89,9 +100,51 @@ public class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
return orderBitLength == 256 || orderBitLength == 384 || orderBitLength == 521; return orderBitLength == 256 || orderBitLength == 384 || orderBitLength == 521;
} }
public EcSignatureAlgorithm(int orderBitLength) { static final EcSignatureAlgorithm ES256 = new EcSignatureAlgorithm(256, ES256_OID);
static final EcSignatureAlgorithm ES384 = new EcSignatureAlgorithm(384, ES384_OID);
static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID);
private static final Map<String, SignatureAlgorithm> ALGS_BY_OID;
static {
ALGS_BY_OID = new LinkedHashMap<>(3);
ALGS_BY_OID.put(ES256_OID, ES256);
ALGS_BY_OID.put(ES384_OID, ES384);
ALGS_BY_OID.put(ES512_OID, ES512);
}
static SignatureAlgorithm findByKey(Key key) {
String algName = KeysBridge.findAlgorithm(key);
if (!Strings.hasText(algName)) {
return null;
}
algName = algName.toUpperCase(Locale.ENGLISH);
SignatureAlgorithm alg = ALGS_BY_OID.get(algName);
if (alg != null) {
return alg;
}
if ("EC".equalsIgnoreCase(algName) || "ECDSA".equalsIgnoreCase(algName)) {
// some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it:
final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out
if (bitLength == ES512.orderBitLength) {
return ES512;
} else if (bitLength == ES384.orderBitLength) {
return ES384;
} else if (bitLength == ES256.orderBitLength) {
return ES256;
}
}
return null;
}
private EcSignatureAlgorithm(int orderBitLength, String oid) {
super("ES" + shaSize(orderBitLength), "SHA" + shaSize(orderBitLength) + "withECDSA"); super("ES" + shaSize(orderBitLength), "SHA" + shaSize(orderBitLength) + "withECDSA");
Assert.isTrue(isSupportedOrderBitLength(orderBitLength), REQD_ORDER_BIT_LENGTH_MSG); Assert.isTrue(isSupportedOrderBitLength(orderBitLength), REQD_ORDER_BIT_LENGTH_MSG);
this.OID = Assert.hasText(oid, "Invalid OID.");
String curveName = "secp" + orderBitLength + "r1"; String curveName = "secp" + orderBitLength + "r1";
this.KEY_PAIR_GEN_PARAMS = new ECGenParameterSpec(curveName); this.KEY_PAIR_GEN_PARAMS = new ECGenParameterSpec(curveName);
this.orderBitLength = orderBitLength; this.orderBitLength = orderBitLength;
@ -142,7 +195,7 @@ public class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
}); });
} }
protected boolean isValidRAndS(PublicKey key, byte[] concatSignature) { boolean isValidRAndS(PublicKey key, byte[] concatSignature) {
if (key instanceof ECKey) { //Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check first if (key instanceof ECKey) { //Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check first
ECKey ecKey = (ECKey) key; ECKey ecKey = (ECKey) key;
BigInteger order = ecKey.getParams().getOrder(); BigInteger order = ecKey.getParams().getOrder();

View File

@ -23,25 +23,27 @@ import io.jsonwebtoken.security.UnsupportedKeyException;
import io.jsonwebtoken.security.VerifyDigestRequest; import io.jsonwebtoken.security.VerifyDigestRequest;
import java.security.Key; import java.security.Key;
import java.security.PrivateKey;
public class EdSignatureAlgorithm extends AbstractSignatureAlgorithm { final class EdSignatureAlgorithm extends AbstractSignatureAlgorithm {
private static final String ID = "EdDSA"; private static final String ID = "EdDSA";
private final EdwardsCurve preferredCurve; private final EdwardsCurve preferredCurve;
public EdSignatureAlgorithm() { static final EdSignatureAlgorithm INSTANCE = new EdSignatureAlgorithm();
static boolean isSigningKey(PrivateKey key) {
EdwardsCurve curve = EdwardsCurve.findByKey(key);
return curve != null && curve.isSignatureCurve();
}
private EdSignatureAlgorithm() {
super(ID, ID); super(ID, ID);
this.preferredCurve = EdwardsCurve.Ed448; this.preferredCurve = EdwardsCurve.Ed448;
// EdDSA is not available natively until JDK 15, so try to load BC as a backup provider if possible: // EdDSA is not available natively until JDK 15, so try to load BC as a backup provider if possible:
setProvider(this.preferredCurve.getProvider()); setProvider(this.preferredCurve.getProvider());
} Assert.isTrue(this.preferredCurve.isSignatureCurve(), "Must be signature curve, not key agreement curve.");
public EdSignatureAlgorithm(EdwardsCurve preferredCurve) {
super(ID, preferredCurve.getJcaName());
this.preferredCurve = Assert.notNull(preferredCurve, "preferredCurve cannot be null.");
Assert.isTrue(preferredCurve.isSignatureCurve(), "EdwardsCurve must be a signature curve, not a key agreement curve.");
setProvider(preferredCurve.getProvider());
} }
@Override @Override
@ -74,7 +76,7 @@ public class EdSignatureAlgorithm extends AbstractSignatureAlgorithm {
EdwardsCurve curve = EdwardsCurve.findByKey(key); EdwardsCurve curve = EdwardsCurve.findByKey(key);
if (curve != null && !curve.isSignatureCurve()) { if (curve != null && !curve.isSignatureCurve()) {
String msg = curve.getId() + " keys may not be used with " + getId() + " digital signatures per " + String msg = curve.getId() + " keys may not be used with " + getId() + " digital signatures per " +
"https://www.rfc-editor.org/rfc/rfc8037#section-3.2"; "https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2";
throw new UnsupportedKeyException(msg); throw new UnsupportedKeyException(msg);
} }
} }

View File

@ -281,14 +281,14 @@ public class EdwardsCurve extends DefaultCurve implements KeyLengthSupplier {
int keyLen = 0; int keyLen = 0;
if (encoded[i] == 0x05) { // NULL terminator, next should be zero byte indicator if (encoded[i] == 0x05) { // NULL terminator, next should be zero byte indicator
int unusedBytes = encoded[++i]; int unusedBytes = encoded[++i];
Assert.eq(0, unusedBytes, "OID NULL terminator should indicate zero unused bytes."); Assert.eq(unusedBytes, 0, "OID NULL terminator should indicate zero unused bytes.");
i++; i++;
} }
if (encoded[i] == 0x03) { // DER bit stream, Public Key if (encoded[i] == 0x03) { // DER bit stream, Public Key
i++; i++;
keyLen = encoded[i++]; keyLen = encoded[i++];
int unusedBytes = encoded[i++]; int unusedBytes = encoded[i++];
Assert.eq(0, unusedBytes, "BIT STREAM should not indicate unused bytes."); Assert.eq(unusedBytes, 0, "BIT STREAM should not indicate unused bytes.");
keyLen--; keyLen--;
} else if (encoded[i] == 0x04) { // DER octet sequence, Private Key. Key length follows as next byte. } else if (encoded[i] == 0x04) { // DER octet sequence, Private Key. Key length follows as next byte.
i++; i++;
@ -298,10 +298,10 @@ public class EdwardsCurve extends DefaultCurve implements KeyLengthSupplier {
keyLen = encoded[i++]; // next byte is length keyLen = encoded[i++]; // next byte is length
} }
} }
Assert.eq(this.encodedKeyByteLength, keyLen, "Invalid key length."); Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length.");
byte[] result = Arrays.copyOfRange(encoded, i, i + keyLen); byte[] result = Arrays.copyOfRange(encoded, i, i + keyLen);
keyLen = Bytes.length(result); keyLen = Bytes.length(result);
Assert.eq(this.encodedKeyByteLength, keyLen, "Invalid key length."); Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length.");
return result; return result;
} }

View File

@ -53,6 +53,20 @@ public interface JwkContext<K extends Key> extends Identifiable, Map<String, Obj
JwkContext<K> setPublicKeyUse(String use); JwkContext<K> setPublicKeyUse(String use);
/**
* Returns {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms,
* {@code false} otherwise. Specifically {@code true} is only returned if either:
* <ul>
* <li>&quot;sig&quot;.equals({@link #getPublicKeyUse()}), OR</li>
* <li>{@link #getOperations()} is not empty and contains either &quot;sign&quot; or &quot;verify&quot;</li>
* </ul>
* <p>otherwise {@code false}.</p>
*
* @return {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms,
* {@code false} otherwise.
*/
boolean isSigUse();
K getKey(); K getKey();
JwkContext<K> setKey(K key); JwkContext<K> setKey(K key);

View File

@ -15,16 +15,20 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Conditions;
import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.SecurityException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.security.Provider;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -44,32 +48,60 @@ public class JwtX509StringConverter implements Converter<X509Certificate, String
der = cert.getEncoded(); der = cert.getEncoded();
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {
String msg = "Unable to access X509Certificate encoded bytes necessary to perform DER " + String msg = "Unable to access X509Certificate encoded bytes necessary to perform DER " +
"Base64-encoding. Certificate: {" + cert + "}. Cause: " + e.getMessage(); "Base64-encoding. Certificate: {" + cert + "}. Cause: " + e.getMessage();
throw new IllegalArgumentException(msg, e); throw new IllegalArgumentException(msg, e);
} }
if (Arrays.length(der) == 0) { if (Bytes.isEmpty(der)) {
String msg = "X509Certificate encoded bytes cannot be null or empty. Certificate: {" + cert + "}."; String msg = "X509Certificate encoded bytes cannot be null or empty. Certificate: {" + cert + "}.";
throw new IllegalArgumentException(msg); throw new IllegalArgumentException(msg);
} }
return Encoders.BASE64.encode(der); return Encoders.BASE64.encode(der);
} }
//visible for testing // visible for testing
protected CertificateFactory newCertificateFactory() throws CertificateException { protected X509Certificate toCert(final byte[] der, Provider provider) throws SecurityException {
return CertificateFactory.getInstance("X.509"); JcaTemplate template = new JcaTemplate("X.509", provider);
final InputStream is = new ByteArrayInputStream(der);
return template.withCertificateFactory(new CheckedFunction<CertificateFactory, X509Certificate>() {
@Override
public X509Certificate apply(CertificateFactory cf) throws Exception {
return (X509Certificate) cf.generateCertificate(is);
}
});
} }
@Override @Override
public X509Certificate applyFrom(String s) { public X509Certificate applyFrom(String s) {
Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty."); Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty.");
byte[] der = null;
try { try {
byte[] der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url
CertificateFactory cf = newCertificateFactory(); return toCert(der, null);
InputStream stream = new ByteArrayInputStream(der); } catch (final Throwable t) {
return (X509Certificate) cf.generateCertificate(stream);
} catch (Exception e) { // Some JDK implementations don't support RSASSA-PSS certificates:
String msg = "Unable to convert Base64 String '" + s + "' to X509Certificate instance. Cause: " + e.getMessage(); //
throw new IllegalArgumentException(msg, e); // https://bugs.openjdk.org/browse/JDK-8242556
//
// Oracle only backported this fix to JDK 8u271+, 11.0.9+, and 15+, so we'll try to fall back to
// BC (which can read the files correctly) on JDK 9, 10, 12, 13, and 14:
String causeMsg = t.getMessage();
Provider bc = null;
if (!Bytes.isEmpty(der) && // Base64 decoding succeeded, so we can continue to try
Strings.hasText(causeMsg) && causeMsg.contains(RsaSignatureAlgorithm.PSS_OID)) {
// OID in exception message, so odds are high that the default provider doesn't support X.509
// certificates with a PSS_OID `AlgorithmId`. But BC does, so try to obtain that if we can:
bc = Providers.findBouncyCastle(Conditions.TRUE);
}
if (bc != null) {
try {
return toCert(der, bc);
} catch (Throwable ignored) {
// ignore this - we want to report the original exception to the caller
}
}
String msg = "Unable to convert Base64 String '" + s + "' to X509Certificate instance. Cause: " + causeMsg;
throw new IllegalArgumentException(msg, t);
} }
} }
} }

View File

@ -17,11 +17,15 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.Password;
import io.jsonwebtoken.security.UnsupportedKeyException; import io.jsonwebtoken.security.UnsupportedKeyException;
import javax.crypto.SecretKey;
import java.security.Key; import java.security.Key;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation @SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
public final class KeysBridge { public final class KeysBridge {
@ -34,6 +38,20 @@ public final class KeysBridge {
return new PasswordSpec(password); return new PasswordSpec(password);
} }
public static String findAlgorithm(Key key) {
return key != null ? Strings.clean(key.getAlgorithm()) : null;
}
/**
* Returns the specified key's available encoded bytes, or {@code null} if not available.
*
* <p>Some KeyStore implementations - like Hardware Security Modules, PKCS11 key stores, and later versions
* of Android - will not allow applications or libraries to obtain a key's encoded bytes. In these cases,
* this method will return null.</p>
*
* @param key the key to inspect
* @return the specified key's available encoded bytes, or {@code null} if not available.
*/
public static byte[] findEncoded(Key key) { public static byte[] findEncoded(Key key) {
Assert.notNull(key, "Key cannot be null."); Assert.notNull(key, "Key cannot be null.");
byte[] encoded = null; byte[] encoded = null;
@ -44,6 +62,40 @@ public final class KeysBridge {
return encoded; return encoded;
} }
/**
* Returns the specified key's key length (in bits) if possible, or {@code -1} if unable to determine the length.
*
* <p>Some KeyStore implementations - like Hardware Security Modules, PKCS11 key stores, and later versions
* of Android - will not allow applications or libraries to determine a key's length. In these cases,
* this method will return {@code -1} to indicate the length could not be determined.</p>
*
* @param key the key to inspect
* @return the specified key's key length in bits, or {@code -1} if unable to determine length.
*/
public static int findBitLength(Key key) {
if (key instanceof SecretKey) {
SecretKey sk = (SecretKey) key;
if ("RAW".equals(sk.getFormat())) {
byte[] encoded = findEncoded(key);
if (encoded != null) {
long len = Bytes.bitLength(encoded);
return Assert.lte(len, (long) Integer.MAX_VALUE, "Excessive key bit length.").intValue();
}
}
} else if (key instanceof RSAKey) {
return ((RSAKey) key).getModulus().bitLength();
} else if (key instanceof ECKey) {
return ((ECKey) key).getParams().getOrder().bitLength();
} else {
//try to see if Edwards key:
EdwardsCurve curve = EdwardsCurve.findByKey(key);
if (curve != null) {
return curve.getKeyBitLength();
}
}
return -1; // unable to determine
}
public static byte[] getEncoded(Key key) { public static byte[] getEncoded(Key key) {
Assert.notNull(key, "Key cannot be null."); Assert.notNull(key, "Key cannot be null.");
byte[] encoded = findEncoded(key); byte[] encoded = findEncoded(key);

View File

@ -23,10 +23,15 @@ import io.jsonwebtoken.security.VerifySecureDigestRequest;
import java.security.Key; import java.security.Key;
public class NoneSignatureAlgorithm implements SecureDigestAlgorithm<Key, Key> { final class NoneSignatureAlgorithm implements SecureDigestAlgorithm<Key, Key> {
private static final String ID = "none"; private static final String ID = "none";
static final SecureDigestAlgorithm<Key, Key> INSTANCE = new NoneSignatureAlgorithm();
private NoneSignatureAlgorithm() {
}
@Override @Override
public String getId() { public String getId() {
return ID; return ID;

View File

@ -196,7 +196,7 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId())) { if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId())) {
List<RSAOtherPrimeInfo> otherPrimes = reader.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO); List<RSAOtherPrimeInfo> otherPrimes = reader.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO);
RSAOtherPrimeInfo[] arr = new RSAOtherPrimeInfo[Collections.size(otherPrimes)]; RSAOtherPrimeInfo[] arr = new RSAOtherPrimeInfo[Collections.size(otherPrimes)];
otherPrimes.toArray(arr); arr = otherPrimes.toArray(arr);
spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime,
secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient, arr); secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient, arr);
} else { } else {

View File

@ -18,9 +18,12 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.CheckedSupplier; import io.jsonwebtoken.impl.lang.CheckedSupplier;
import io.jsonwebtoken.impl.lang.Conditions; import io.jsonwebtoken.impl.lang.Conditions;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.KeyPairBuilder;
import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecureRequest;
import io.jsonwebtoken.security.SignatureAlgorithm;
import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.VerifySecureDigestRequest;
import io.jsonwebtoken.security.WeakKeyException; import io.jsonwebtoken.security.WeakKeyException;
@ -32,43 +35,78 @@ import java.security.interfaces.RSAKey;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec; import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec; import java.security.spec.PSSParameterSpec;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/** /**
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm { final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
// Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1:
//private static final String RSA_ENC_OID = "1.2.840.113549.1.1.1"; // RFC 8017's "rsaEncryption"
// Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.3:
static final String PSS_JCA_NAME = "RSASSA-PSS";
static final String PSS_OID = "1.2.840.113549.1.1.10"; // RFC 8017's "id-RSASSA-PSS"
// Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4:
private static final String RS256_OID = "1.2.840.113549.1.1.11"; // RFC 8017's "sha256WithRSAEncryption"
private static final String RS384_OID = "1.2.840.113549.1.1.12"; // RFC 8017's "sha384WithRSAEncryption"
private static final String RS512_OID = "1.2.840.113549.1.1.13"; // RFC 8017's "sha512WithRSAEncryption"
private static final Set<String> PSS_ALG_NAMES = Collections.setOf(PSS_JCA_NAME, PSS_OID);
private static final String PSS_JCA_NAME = "RSASSA-PSS";
private static final int MIN_KEY_BIT_LENGTH = 2048; private static final int MIN_KEY_BIT_LENGTH = 2048;
private static AlgorithmParameterSpec pssParamFromSaltBitLength(int saltBitLength) { private static AlgorithmParameterSpec pssParamSpec(int digestBitLength) {
MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + saltBitLength); MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + digestBitLength);
int saltByteLength = saltBitLength / Byte.SIZE; int saltByteLength = digestBitLength / Byte.SIZE;
return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1); return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1);
} }
private static SignatureAlgorithm rsaSsaPss(int digestBitLength) {
return new RsaSignatureAlgorithm(digestBitLength, pssParamSpec(digestBitLength));
}
static final SignatureAlgorithm RS256 = new RsaSignatureAlgorithm(256);
static final SignatureAlgorithm RS384 = new RsaSignatureAlgorithm(384);
static final SignatureAlgorithm RS512 = new RsaSignatureAlgorithm(512);
static final SignatureAlgorithm PS256 = rsaSsaPss(256);
static final SignatureAlgorithm PS384 = rsaSsaPss(384);
static final SignatureAlgorithm PS512 = rsaSsaPss(512);
private static final Map<String, SignatureAlgorithm> PKCSv15_ALGS;
static {
PKCSv15_ALGS = new LinkedHashMap<>();
PKCSv15_ALGS.put(RS256_OID, RS256);
PKCSv15_ALGS.put(RS384_OID, RS384);
PKCSv15_ALGS.put(RS512_OID, RS512);
}
private final int preferredKeyBitLength; private final int preferredKeyBitLength;
private final AlgorithmParameterSpec algorithmParameterSpec; private final AlgorithmParameterSpec algorithmParameterSpec;
public RsaSignatureAlgorithm(String name, String jcaName, int preferredKeyBitLength, AlgorithmParameterSpec algParam) { private RsaSignatureAlgorithm(String name, String jcaName, int digestBitLength, AlgorithmParameterSpec paramSpec) {
super(name, jcaName); super(name, jcaName);
if (preferredKeyBitLength < MIN_KEY_BIT_LENGTH) { this.preferredKeyBitLength = digestBitLength * Byte.SIZE; // RSA invariant
String msg = "preferredKeyBitLength must be greater than the JWA mandatory minimum key length of " + // invariant since this is a protected constructor:
MIN_KEY_BIT_LENGTH; Assert.state(this.preferredKeyBitLength >= MIN_KEY_BIT_LENGTH);
throw new IllegalArgumentException(msg); this.algorithmParameterSpec = paramSpec;
}
this.preferredKeyBitLength = preferredKeyBitLength;
this.algorithmParameterSpec = algParam;
} }
public RsaSignatureAlgorithm(int digestBitLength, int preferredKeyBitLength) { private RsaSignatureAlgorithm(int digestBitLength) {
this("RS" + digestBitLength, "SHA" + digestBitLength + "withRSA", preferredKeyBitLength, null); this("RS" + digestBitLength, "SHA" + digestBitLength + "withRSA", digestBitLength, null);
} }
public RsaSignatureAlgorithm(int digestBitLength, int preferredKeyBitLength, int pssSaltBitLength) { // RSASSA-PSS constructor
this("PS" + digestBitLength, PSS_JCA_NAME, preferredKeyBitLength, pssParamFromSaltBitLength(pssSaltBitLength)); private RsaSignatureAlgorithm(int digestBitLength, AlgorithmParameterSpec paramSpec) {
// PSS is not available natively until JDK 11, so try to load BC as a backup provider if possible on <= JDK 10: this("PS" + digestBitLength, PSS_JCA_NAME, digestBitLength, paramSpec);
// RSASSA-PSS is not available natively until JDK 11, so try to load BC as a backup provider if possible:
setProvider(Providers.findBouncyCastle(Conditions.notExists(new CheckedSupplier<Signature>() { setProvider(Providers.findBouncyCastle(Conditions.notExists(new CheckedSupplier<Signature>() {
@Override @Override
public Signature get() throws Exception { public Signature get() throws Exception {
@ -77,23 +115,73 @@ public class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
}))); })));
} }
static SignatureAlgorithm findByKey(Key key) {
String algName = KeysBridge.findAlgorithm(key);
if (!Strings.hasText(algName)) {
return null;
}
algName = algName.toUpperCase(Locale.ENGLISH); // for checking against name Sets
// some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it:
final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out
if (PSS_ALG_NAMES.contains(algName)) { // generic RSASSA-PSS names, check for key lengths:
// even though we found an RSASSA-PSS key, we need to confirm that the key length is
// sufficient if the encoded key bytes are available:
if (bitLength >= 4096) {
return PS512;
} else if (bitLength >= 3072) {
return PS384;
} else if (bitLength >= MIN_KEY_BIT_LENGTH) {
return PS256;
}
}
// unable to resolve/recommend an RSASSA-PSS alg, so try PKCS v 1.5 algs by OID:
SignatureAlgorithm alg = PKCSv15_ALGS.get(algName);
if (alg != null) {
return alg;
}
if ("RSA".equals(algName)) {
if (bitLength >= 4096) {
return RS512;
} else if (bitLength >= 3072) {
return RS384;
} else if (bitLength >= MIN_KEY_BIT_LENGTH) {
return RS256;
}
}
return null;
}
static boolean isPss(Key key) {
String alg = KeysBridge.findAlgorithm(key);
return PSS_ALG_NAMES.contains(alg);
}
@Override @Override
public KeyPairBuilder keyPair() { public KeyPairBuilder keyPair() {
return new DefaultKeyPairBuilder("RSA", this.preferredKeyBitLength) final String jcaName = this.algorithmParameterSpec != null ? PSS_JCA_NAME : "RSA";
//TODO: JDK 8 or later, for RSASSA-PSS, use the following instead of what is below:
//
// AlgorithmParameterSpec keyGenSpec = new RSAKeyGenParameterSpec(this.preferredKeyBitLength,
// RSAKeyGenParameterSpec.F4, this.algorithmParameterSpec);
// return new DefaultKeyPairBuilder(jcaName, keyGenSpec).provider(getProvider()).random(Randoms.secureRandom());
//
return new DefaultKeyPairBuilder(jcaName, this.preferredKeyBitLength)
.provider(getProvider()) .provider(getProvider())
.random(Randoms.secureRandom()); .random(Randoms.secureRandom());
} }
@Override @Override
protected void validateKey(Key key, boolean signing) { protected void validateKey(Key key, boolean signing) {
super.validateKey(key, signing);
// https://github.com/jwtk/jjwt/issues/68 // https://github.com/jwtk/jjwt/issues/68 :
if (signing && !(key instanceof PrivateKey)) {
String msg = "Asymmetric key signatures must be created with PrivateKeys. The specified key is of type: " +
key.getClass().getName();
throw new InvalidKeyException(msg);
}
// Some PKCS11 providers and HSMs won't expose the RSAKey interface, so we have to check to see if we can cast // Some PKCS11 providers and HSMs won't expose the RSAKey interface, so we have to check to see if we can cast
// If so, we can provide additional safety checks: // If so, we can provide additional safety checks:
if (key instanceof RSAKey) { if (key instanceof RSAKey) {
@ -105,8 +193,8 @@ public class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " +
"enough for the " + id + " algorithm. The JWT JWA Specification (RFC 7518, Section " + "enough for the " + id + " algorithm. The JWT JWA Specification (RFC 7518, Section " +
section + ") states that RSA keys MUST have a size >= " + MIN_KEY_BIT_LENGTH + " bits. " + section + ") states that RSA keys MUST have a size >= " + MIN_KEY_BIT_LENGTH + " bits. " +
"Consider using the Jwts.SIG." + id + ".generateKeyPair() " + "method to create a " + "Consider using the Jwts.SIG." + id + ".keyPair() builder to create a " +
"key pair guaranteed to be secure enough for " + id + ". See " + "KeyPair guaranteed to be secure enough for " + id + ". See " +
"https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
@ -130,10 +218,6 @@ public class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
@Override @Override
protected boolean doVerify(final VerifySecureDigestRequest<PublicKey> request) { protected boolean doVerify(final VerifySecureDigestRequest<PublicKey> request) {
final Key key = request.getKey();
if (key instanceof PrivateKey) { //legacy support only TODO: remove for 1.0
return super.messageDigest(request);
}
return jca(request).withSignature(new CheckedFunction<Signature, Boolean>() { return jca(request).withSignature(new CheckedFunction<Signature, Boolean>() {
@Override @Override
public Boolean apply(Signature sig) throws Exception { public Boolean apply(Signature sig) throws Exception {

View File

@ -22,7 +22,6 @@ import io.jsonwebtoken.impl.lang.RequiredFieldReader;
import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.MacAlgorithm;
import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.MalformedKeyException;
@ -87,7 +86,12 @@ class SecretJwkFactory extends AbstractFamilyJwkFactory<SecretKey, SecretJwk> {
if (bitLen != requiredBitLen) { if (bitLen != requiredBitLen) {
// Implementors note: Don't print out any information about the `bytes` value itself - size, // Implementors note: Don't print out any information about the `bytes` value itself - size,
// content, etc., as it is considered secret material: // content, etc., as it is considered secret material:
String msg = "Secret JWK " + AbstractJwk.ALG + " value is '" + alg.getId() + "', but the " + DefaultSecretJwk.K + " length does not equal the '" + alg.getId() + "' length requirement of " + Bytes.bitsMsg(requiredBitLen) + ". This discrepancy could be the result of an algorithm " + "substitution attack or simply an erroneously constructed JWK. In either case, it is likely " + "to result in unexpected or undesired security consequences."; String msg = "Secret JWK " + AbstractJwk.ALG + " value is '" + alg.getId() +
"', but the " + DefaultSecretJwk.K + " length does not equal the '" + alg.getId() +
"' length requirement of " + Bytes.bitsMsg(requiredBitLen) +
". This discrepancy could be the result of an algorithm " +
"substitution attack or simply an erroneously constructed JWK. In either case, it is likely " +
"to result in unexpected or undesired security consequences.";
throw new MalformedKeyException(msg); throw new MalformedKeyException(msg);
} }
} }
@ -107,21 +111,15 @@ class SecretJwkFactory extends AbstractFamilyJwkFactory<SecretKey, SecretJwk> {
assertKeyBitLength(bytes, (MacAlgorithm) alg); assertKeyBitLength(bytes, (MacAlgorithm) alg);
} }
} }
if (!Strings.hasText(jcaName) && if (!Strings.hasText(jcaName)) {
("sig".equalsIgnoreCase(ctx.getPublicKeyUse()) || // [1] if (ctx.isSigUse()) {
(!Collections.isEmpty(ctx.getOperations()) && ctx.getOperations().contains("sign")))) { // [2] // The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length:
// [1] Even though 'use' is for PUBLIC KEY use (as defined in RFC 7515), RFC 7520 shows secret keys with jcaName = "HmacSHA" + Bytes.bitLength(bytes);
// 'use' values, so we'll account for that as well } else { // not an HS* algorithm, and all standard AeadAlgorithms use AES keys:
// jcaName = AesAlgorithm.KEY_ALG_NAME;
// [2] operations values are case-sensitive, so we don't need to test for upper/lowercase "sign" values }
// The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length:
jcaName = "HmacSHA" + Bytes.bitLength(bytes);
} }
if (jcaName == null) { // not an HS* algorithm, no signature "use", no "sign" key op, so default to encryption: Assert.stateNotNull(jcaName, "jcaName cannot be null (invariant)");
jcaName = AesAlgorithm.KEY_ALG_NAME;
}
SecretKey key = new SecretKeySpec(bytes, jcaName); SecretKey key = new SecretKeySpec(bytes, jcaName);
ctx.setKey(key); ctx.setKey(key);
return new DefaultSecretJwk(ctx); return new DefaultSecretJwk(ctx);

View File

@ -0,0 +1,27 @@
/*
* Copyright © 2023 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.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.security.Curve;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwks.CRV
public final class StandardCurves extends DelegatingRegistry<String, Curve> {
public StandardCurves() {
super(new IdRegistry<>("Elliptic Curve", Curves.VALUES, false));
}
}

View File

@ -18,51 +18,57 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry; import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Password;
import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.PrivateKey;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG
public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<String, SecureDigestAlgorithm<?, ?>> { public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<String, SecureDigestAlgorithm<?, ?>> {
private static final EdSignatureAlgorithm Ed25519 = new EdSignatureAlgorithm(EdwardsCurve.Ed25519);
private static final EdSignatureAlgorithm Ed448 = new EdSignatureAlgorithm(EdwardsCurve.Ed448);
public StandardSecureDigestAlgorithms() { public StandardSecureDigestAlgorithms() {
super(new IdRegistry<>("JWS Digital Signature or MAC", Collections.of( super(new IdRegistry<>("JWS Digital Signature or MAC", Collections.of(
new NoneSignatureAlgorithm(), NoneSignatureAlgorithm.INSTANCE,
new DefaultMacAlgorithm(256), DefaultMacAlgorithm.HS256,
new DefaultMacAlgorithm(384), DefaultMacAlgorithm.HS384,
new DefaultMacAlgorithm(512), DefaultMacAlgorithm.HS512,
new RsaSignatureAlgorithm(256, 2048), RsaSignatureAlgorithm.RS256,
new RsaSignatureAlgorithm(384, 3072), RsaSignatureAlgorithm.RS384,
new RsaSignatureAlgorithm(512, 4096), RsaSignatureAlgorithm.RS512,
new RsaSignatureAlgorithm(256, 2048, 256), RsaSignatureAlgorithm.PS256,
new RsaSignatureAlgorithm(384, 3072, 384), RsaSignatureAlgorithm.PS384,
new RsaSignatureAlgorithm(512, 4096, 512), RsaSignatureAlgorithm.PS512,
new EcSignatureAlgorithm(256), EcSignatureAlgorithm.ES256,
new EcSignatureAlgorithm(384), EcSignatureAlgorithm.ES384,
new EcSignatureAlgorithm(521), EcSignatureAlgorithm.ES512,
new EdSignatureAlgorithm() EdSignatureAlgorithm.INSTANCE
), false)); ), false));
} }
@Override @SuppressWarnings("unchecked")
public SecureDigestAlgorithm<?, ?> get(Object id) { public static <K extends Key> SecureDigestAlgorithm<K, ?> findBySigningKey(K key) {
String key = (String) id; // could throw ClassCastException, which is allowed per Map 'get' contract
if (EdwardsCurve.Ed448.getId().equalsIgnoreCase(key)) {
return Ed448;
} else if (EdwardsCurve.Ed25519.getId().equalsIgnoreCase(key)) {
return Ed25519;
}
return super.get(key);
}
@Override SecureDigestAlgorithm<?, ?> alg = null; // null value means no suitable match
public SecureDigestAlgorithm<?, ?> forKey(String id) throws IllegalArgumentException {
if (EdwardsCurve.Ed448.getId().equalsIgnoreCase(id)) { if (key instanceof SecretKey && !(key instanceof Password)) {
return Ed448;
} else if (EdwardsCurve.Ed25519.getId().equalsIgnoreCase(id)) { alg = DefaultMacAlgorithm.findByKey(key);
return Ed25519;
} else if (key instanceof PrivateKey) {
PrivateKey pk = (PrivateKey) key;
alg = RsaSignatureAlgorithm.findByKey(pk);
if (alg == null) {
alg = EcSignatureAlgorithm.findByKey(pk);
}
if (alg == null && EdSignatureAlgorithm.isSigningKey(pk)) {
alg = EdSignatureAlgorithm.INSTANCE;
}
} }
return super.forKey(id);
return (SecureDigestAlgorithm<K, ?>) alg;
} }
} }

View File

@ -0,0 +1,37 @@
/*
* Copyright © 2023 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
import io.jsonwebtoken.impl.security.Curves
import io.jsonwebtoken.impl.security.EdwardsCurve
import io.jsonwebtoken.security.Jwks
import org.junit.Test
import static org.junit.Assert.assertSame
class JwksCRVTest {
@Test
void testInstances() {
assertSame Curves.P_256, Jwks.CRV.P256
assertSame Curves.P_384, Jwks.CRV.P384
assertSame Curves.P_521, Jwks.CRV.P521
assertSame EdwardsCurve.X25519, Jwks.CRV.X25519
assertSame EdwardsCurve.X448, Jwks.CRV.X448
assertSame EdwardsCurve.Ed25519, Jwks.CRV.Ed25519
assertSame EdwardsCurve.Ed448, Jwks.CRV.Ed448
}
}

View File

@ -36,6 +36,8 @@ import java.security.Key
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.security.interfaces.ECPublicKey
import java.security.interfaces.RSAPublicKey
import static org.junit.Assert.* import static org.junit.Assert.*
@ -614,21 +616,6 @@ class JwtsTest {
testRsa(Jwts.SIG.PS512) testRsa(Jwts.SIG.PS512)
} }
@Test
void testRSA256WithPrivateKeyValidation() {
testRsa(Jwts.SIG.RS256, true)
}
@Test
void testRSA384WithPrivateKeyValidation() {
testRsa(Jwts.SIG.RS384, true)
}
@Test
void testRSA512WithPrivateKeyValidation() {
testRsa(Jwts.SIG.RS512, true)
}
@Test @Test
void testES256() { void testES256() {
testEC(Jwts.SIG.ES256) testEC(Jwts.SIG.ES256)
@ -651,12 +638,12 @@ class JwtsTest {
@Test @Test
void testEd25519() { void testEd25519() {
testEC(Jwts.SIG.Ed25519) testEC(Jwts.SIG.EdDSA, TestKeys.forAlgorithm(Jwks.CRV.Ed25519).pair)
} }
@Test @Test
void testEd448() { void testEd448() {
testEC(Jwts.SIG.Ed448) testEC(Jwts.SIG.EdDSA, TestKeys.forAlgorithm(Jwks.CRV.Ed448).pair)
} }
@Test @Test
@ -1211,16 +1198,20 @@ class JwtsTest {
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but // Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
// using it as an HMAC signing key instead of RSA: // using it as an HMAC signing key instead of RSA:
Mac mac = Mac.getInstance('HmacSHA256') Mac mac = Mac.getInstance('HmacSHA256')
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256')) byte[] raw = ((RSAPublicKey)publicKey).getModulus().toByteArray()
if (raw.length > 256) {
raw = Arrays.copyOfRange(raw, 1, raw.length)
}
mac.init(new SecretKeySpec(raw, 'HmacSHA256'))
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes)
//Finally, the forged token is the header + body + forged signature: //Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature String forged = compact + encodedSignature
// Assert that the server (that should always use the private key) does not recognized the forged token: // Assert that the server does not recognized the forged token:
try { try {
Jwts.parser().setSigningKey(privateKey).build().parse(forged) Jwts.parser().verifyWith(privateKey).build().parse(forged)
fail("Forged token must not be successfully parsed.") fail("Forged token must not be successfully parsed.")
} catch (UnsupportedJwtException expected) { } catch (UnsupportedJwtException expected) {
assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the') assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the')
@ -1243,7 +1234,11 @@ class JwtsTest {
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but // Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
// using it as an HMAC signing key instead of RSA: // using it as an HMAC signing key instead of RSA:
Mac mac = Mac.getInstance('HmacSHA256') Mac mac = Mac.getInstance('HmacSHA256')
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256')) byte[] raw = ((RSAPublicKey)publicKey).getModulus().toByteArray()
if (raw.length > 256) {
raw = Arrays.copyOfRange(raw, 1, raw.length)
}
mac.init(new SecretKeySpec(raw, 'HmacSHA256'))
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes)
@ -1275,7 +1270,11 @@ class JwtsTest {
// Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but // Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but
// using it as an HMAC signing key instead of Elliptic Curve: // using it as an HMAC signing key instead of Elliptic Curve:
Mac mac = Mac.getInstance('HmacSHA256') Mac mac = Mac.getInstance('HmacSHA256')
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256')) byte[] raw = ((ECPublicKey)publicKey).getParams().getOrder().toByteArray()
if (raw.length > 32) {
raw = Arrays.copyOfRange(raw, 1, raw.length)
}
mac.init(new SecretKeySpec(raw, 'HmacSHA256'))
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes)
@ -1566,7 +1565,7 @@ class JwtsTest {
return Jwts.parser().decryptWith(key).build().parseClaimsJwe(jwe) return Jwts.parser().decryptWith(key).build().parseClaimsJwe(jwe)
} }
static void testRsa(io.jsonwebtoken.security.SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) { static void testRsa(io.jsonwebtoken.security.SignatureAlgorithm alg) {
KeyPair kp = TestKeys.forAlgorithm(alg).pair KeyPair kp = TestKeys.forAlgorithm(alg).pair
PublicKey publicKey = kp.getPublic() PublicKey publicKey = kp.getPublic()
@ -1576,12 +1575,7 @@ class JwtsTest {
String jwt = Jwts.builder().claims().add(claims).and().signWith(privateKey, alg).compact() String jwt = Jwts.builder().claims().add(claims).and().signWith(privateKey, alg).compact()
def key = publicKey def token = Jwts.parser().verifyWith(publicKey).build().parse(jwt)
if (verifyWithPrivateKey) {
key = privateKey
}
def token = Jwts.parser().verifyWith(key).build().parse(jwt)
assertEquals([alg: alg.getId()], token.header) assertEquals([alg: alg.getId()], token.header)
assertEquals(claims, token.payload) assertEquals(claims, token.payload)
@ -1603,8 +1597,11 @@ class JwtsTest {
} }
static void testEC(io.jsonwebtoken.security.SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) { static void testEC(io.jsonwebtoken.security.SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) {
testEC(alg, TestKeys.forAlgorithm(alg).pair, verifyWithPrivateKey)
}
static void testEC(io.jsonwebtoken.security.SignatureAlgorithm alg, KeyPair pair, boolean verifyWithPrivateKey = false) {
KeyPair pair = TestKeys.forAlgorithm(alg).pair
PublicKey publicKey = pair.getPublic() PublicKey publicKey = pair.getPublic()
PrivateKey privateKey = pair.getPrivate() PrivateKey privateKey = pair.getPrivate()
@ -1621,6 +1618,7 @@ class JwtsTest {
assertEquals([alg: alg.getId()], token.header) assertEquals([alg: alg.getId()], token.header)
assertEquals(claims, token.payload) assertEquals(claims, token.payload)
} }
} }

View File

@ -21,14 +21,15 @@ import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.lang.Services
import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.impl.security.Randoms
import io.jsonwebtoken.impl.security.TestKey
import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.impl.security.TestKeys
import io.jsonwebtoken.io.* import io.jsonwebtoken.io.*
import io.jsonwebtoken.security.* import io.jsonwebtoken.security.*
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import javax.crypto.KeyGenerator
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.Provider import java.security.Provider
import java.security.SecureRandom import java.security.SecureRandom
@ -314,20 +315,56 @@ class DefaultJwtBuilderTest {
@Test @Test
void testSignWithKeyOnly() { void testSignWithKeyOnly() {
def b = new DefaultJwtBuilder() builder.subject("Joe") // make Claims JWS
b.header().keyId('a')
b.setPayload('foo')
def key = KeyGenerator.getInstance('HmacSHA256').generateKey() for (SecureDigestAlgorithm alg : Jwts.SIG.get().values()) {
if (alg.equals(Jwts.SIG.NONE)) { // skip
continue;
}
def key, vkey
if (alg instanceof KeyPairBuilderSupplier) {
def keyPair = alg.keyPair().build()
key = keyPair.private
vkey = keyPair.public
} else { // MAC
key = ((MacAlgorithm) alg).key().build()
vkey = key
}
b.signWith(key) def parser = Jwts.parser().verifyWith(vkey).build()
String s1 = b.compact()
//ensure matches same result with specified algorithm: String s1 = builder.signWith(key).compact()
b.signWith(key, SignatureAlgorithm.HS256) def jws = parser.parseClaimsJws(s1)
String s2 = b.compact()
assertEquals s1, s2 String s2 = builder.signWith(key, alg).compact()
def jws2 = parser.parseClaimsJws(s2)
// signatures differ across duplicate operations for some algorithms, so we can't do
// assertEquals jws, jws2 (since those .equals implementations use the signature)
// So we check for header and payload equality instead, and check the signature when we can:
assertEquals jws.getHeader(), jws2.getHeader()
assertEquals jws2.getPayload(), jws2.getPayload()
// ES* and PS* signatures are nondeterministic and differ on each sign operation, even for identical
// input, so we can't assert signature equality for them. But we can with the others:
if (!alg.id.startsWith('ES') && !alg.id.startsWith('PS')) {
assertTrue MessageDigest.isEqual(jws.getDigest(), jws2.getDigest())
}
}
}
@Test
void testSignWithKeyOnlyUsingUnsupportedKey() {
try {
builder.signWith(new TestKey(algorithm: 'foo'))
fail()
} catch (UnsupportedKeyException expected) {
String msg = 'Unable to determine a suitable MAC or Signature algorithm for the specified key using ' +
'available heuristics: either the key size is too weak be used with available algorithms, or ' +
'the key size is unavailable (e.g. if using a PKCS11 or HSM (Hardware Security Module) key ' +
'store). If you are using a PKCS11 or HSM keystore, consider using the ' +
'JwtBuilder.signWith(Key, SecureDigestAlgorithm) method instead.'
assertEquals msg, expected.getMessage()
}
} }
@Test @Test
@ -337,7 +374,8 @@ class DefaultJwtBuilderTest {
new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, bytes); new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, bytes);
fail() fail()
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
assertEquals "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message assertEquals "Key bytes may only be specified for HMAC signatures. If using RSA or " +
"Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message
} }
} }

View File

@ -357,6 +357,17 @@ class DefaultJwtParserBuilderTest {
} }
} }
@Test
void testSetSigningKeyWithPrivateKey() {
try {
builder.setSigningKey(TestKeys.RS256.pair.private)
fail()
} catch (UnsupportedKeyException e) {
String msg = 'JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey (for Signature algorithms).'
assertEquals msg, e.getMessage()
}
}
static class TestCompressionCodec implements CompressionCodec { static class TestCompressionCodec implements CompressionCodec {
String id String id

View File

@ -26,6 +26,7 @@ import org.junit.Test
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.Key import java.security.Key
import java.security.Provider
import static org.junit.Assert.* import static org.junit.Assert.*
@ -35,12 +36,8 @@ class DefaultJwkParserTest {
void testKeys() { void testKeys() {
Set<Key> keys = new LinkedHashSet<>() Set<Key> keys = new LinkedHashSet<>()
TestKeys.HS.each { keys.add(it) } TestKeys.SECRET.each { keys.add(it) }
TestKeys.RSA.each { TestKeys.ASYM.each {
keys.add(it.pair.public)
keys.add(it.pair.private)
}
TestKeys.EC.each {
keys.add(it.pair.public) keys.add(it.pair.public)
keys.add(it.pair.private) keys.add(it.pair.private)
} }
@ -48,7 +45,13 @@ class DefaultJwkParserTest {
def serializer = Services.loadFirst(Serializer) def serializer = Services.loadFirst(Serializer)
for (Key key : keys) { for (Key key : keys) {
//noinspection GroovyAssignabilityCheck //noinspection GroovyAssignabilityCheck
def jwk = Jwks.builder().key(key).build() Provider provider = null // assume default, but switch if key requires it
if (key.getClass().getName().startsWith("org.bouncycastle.")) {
// No native JVM support for the key, so we need to enable BC:
provider = Providers.findBouncyCastle(Conditions.TRUE)
}
//noinspection GroovyAssignabilityCheck
def jwk = Jwks.builder().provider(provider).key(key).build()
def data = serializer.serialize(jwk) def data = serializer.serialize(jwk)
String json = new String(data, StandardCharsets.UTF_8) String json = new String(data, StandardCharsets.UTF_8)
def parsed = Jwks.parser().build().parse(json) def parsed = Jwks.parser().build().parse(json)
@ -61,29 +64,37 @@ class DefaultJwkParserTest {
Set<Key> keys = new LinkedHashSet<>() Set<Key> keys = new LinkedHashSet<>()
TestKeys.HS.each { keys.add(it) } TestKeys.HS.each { keys.add(it) }
TestKeys.RSA.each { TestKeys.ASYM.each {
keys.add(it.pair.public)
keys.add(it.pair.private)
}
TestKeys.EC.each {
keys.add(it.pair.public) keys.add(it.pair.public)
keys.add(it.pair.private) keys.add(it.pair.private)
} }
def serializer = Services.loadFirst(Serializer) def serializer = Services.loadFirst(Serializer)
def provider = Providers.findBouncyCastle(Conditions.TRUE) def provider = Providers.findBouncyCastle(Conditions.TRUE) //always used
for (Key key : keys) { for (Key key : keys) {
//noinspection GroovyAssignabilityCheck //noinspection GroovyAssignabilityCheck
def jwk = Jwks.builder().key(key).build() def jwk = Jwks.builder().provider(provider).key(key).build()
def data = serializer.serialize(jwk) def data = serializer.serialize(jwk)
String json = new String(data, StandardCharsets.UTF_8) String json = new String(data, StandardCharsets.UTF_8)
def parsed = Jwks.parser().provider(provider).build().parse(json) def parsed = Jwks.parser().build().parse(json)
assertEquals jwk, parsed assertEquals jwk, parsed
assertSame provider, parsed.@context.@provider //assertSame provider, parsed.@context.@provider
} }
} }
@Test
void testParseWithProvider() {
def provider = Providers.findBouncyCastle(Conditions.TRUE)
def jwk = Jwks.builder().provider(provider).key(TestKeys.HS256).build()
def serializer = Services.loadFirst(Serializer)
def data = serializer.serialize(jwk)
String json = new String(data, StandardCharsets.UTF_8)
def parsed = Jwks.parser().provider(provider).build().parse(json)
assertEquals jwk, parsed
assertSame provider, parsed.@context.@provider
}
@Test @Test
void testDeserializationFailure() { void testDeserializationFailure() {
def parser = new DefaultJwkParser(null, Services.loadFirst(Deserializer)) { def parser = new DefaultJwkParser(null, Services.loadFirst(Deserializer)) {

View File

@ -22,7 +22,7 @@ import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.Key import java.security.Key
import static org.junit.Assert.assertEquals import static org.junit.Assert.*
class DefaultMacAlgorithmTest { class DefaultMacAlgorithmTest {
@ -164,4 +164,70 @@ class DefaultMacAlgorithmTest {
assertEquals 'The signing key\'s size is 192 bits which is not secure enough for the foo algorithm. The foo algorithm requires keys to have a size >= 256 bits.', expected.getMessage() assertEquals 'The signing key\'s size is 192 bits which is not secure enough for the foo algorithm. The foo algorithm requires keys to have a size >= 256 bits.', expected.getMessage()
} }
} }
@Test
void testFindByKeyWithNoAlgorithm() {
assertNull DefaultMacAlgorithm.findByKey(new TestSecretKey())
}
@Test
void testFindByKeyInvalidAlgorithm() {
assertNull DefaultMacAlgorithm.findByKey(new TestSecretKey(algorithm: 'foo'))
}
@Test
void testFindByKey() {
for(def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) {
def key = mac.key().build()
assertSame mac, DefaultMacAlgorithm.findByKey(key)
}
}
@Test
void testFindByKeyNull() {
assertNull DefaultMacAlgorithm.findByKey(null)
}
@Test
void testFindByNonSecretKey() {
assertNull DefaultMacAlgorithm.findByKey(TestKeys.RS256.pair.public)
}
@Test
void testFindByWeakKey() {
for(def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) {
def key = mac.key().build()
def encoded = new byte[key.getEncoded().length - 1] // one byte less than required
def weak = new TestSecretKey(algorithm: key.getAlgorithm(), format: key.getFormat(), encoded: encoded)
assertSame mac, DefaultMacAlgorithm.findByKey(key)
assertNull DefaultMacAlgorithm.findByKey(weak)
}
}
@Test
void testFindByLargerThanExpectedKey() {
for(def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) {
def key = mac.key().build()
def encoded = new byte[key.getEncoded().length + 1] // one byte less than required
def strong = new TestSecretKey(algorithm: key.getAlgorithm(), format: key.getFormat(), encoded: encoded)
assertSame mac, DefaultMacAlgorithm.findByKey(strong)
}
}
@Test
void testFindByKeyOid() {
for(def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) {
def key = mac.key().build()
def alg = key.getAlgorithm()
if (alg.endsWith('256')) {
alg = DefaultMacAlgorithm.HS256_OID
} else if (alg.endsWith('384')) {
alg = DefaultMacAlgorithm.HS384_OID
} else {
alg = DefaultMacAlgorithm.HS512_OID
}
def oidKey = new TestSecretKey(algorithm: alg, format: 'RAW', encoded: key.getEncoded())
assertSame mac, DefaultMacAlgorithm.findByKey(oidKey)
}
}
} }

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.UnsupportedKeyException
import io.jsonwebtoken.security.WeakKeyException import io.jsonwebtoken.security.WeakKeyException
import org.junit.Test import org.junit.Test
@ -40,10 +41,43 @@ class DefaultRsaKeyAlgorithmTest {
} }
} }
@Test
void testPssKey() {
for (DefaultRsaKeyAlgorithm alg : algs) {
RSAPublicKey key = createMock(RSAPublicKey)
expect(key.getAlgorithm()).andReturn(RsaSignatureAlgorithm.PSS_JCA_NAME)
replay(key)
try {
alg.validate(key, true)
} catch (UnsupportedKeyException expected) {
String msg = 'RSASSA-PSS keys may not be used for encryption, only digital signature algorithms.'
assertEquals msg, expected.getMessage()
}
verify(key)
}
}
@Test
void testPssOidKey() {
for (DefaultRsaKeyAlgorithm alg : algs) {
RSAPublicKey key = createMock(RSAPublicKey)
expect(key.getAlgorithm()).andReturn(RsaSignatureAlgorithm.PSS_OID)
replay(key)
try {
alg.validate(key, true)
} catch (UnsupportedKeyException expected) {
String msg = 'RSASSA-PSS keys may not be used for encryption, only digital signature algorithms.'
assertEquals msg, expected.getMessage()
}
verify(key)
}
}
@Test @Test
void testWeakEncryptionKey() { void testWeakEncryptionKey() {
for (DefaultRsaKeyAlgorithm alg : algs) { for (DefaultRsaKeyAlgorithm alg : algs) {
RSAPublicKey key = createMock(RSAPublicKey) RSAPublicKey key = createMock(RSAPublicKey)
expect(key.getAlgorithm()).andReturn("RSA")
expect(key.getModulus()).andReturn(BigInteger.ONE) expect(key.getModulus()).andReturn(BigInteger.ONE)
replay(key) replay(key)
try { try {
@ -65,6 +99,7 @@ class DefaultRsaKeyAlgorithmTest {
void testWeakDecryptionKey() { void testWeakDecryptionKey() {
for (DefaultRsaKeyAlgorithm alg : algs) { for (DefaultRsaKeyAlgorithm alg : algs) {
RSAPrivateKey key = createMock(RSAPrivateKey) RSAPrivateKey key = createMock(RSAPrivateKey)
expect(key.getAlgorithm()).andReturn("RSA")
expect(key.getModulus()).andReturn(BigInteger.ONE) expect(key.getModulus()).andReturn(BigInteger.ONE)
replay(key) replay(key)
try { try {

View File

@ -48,16 +48,44 @@ class EcSignatureAlgorithmTest {
@Test @Test
void testConstructorWithWeakKeyLength() { void testConstructorWithWeakKeyLength() {
try { try {
new EcSignatureAlgorithm(128) new EcSignatureAlgorithm(128, 'foo')
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
String msg = 'orderBitLength must equal 256, 384, or 521.' String msg = 'orderBitLength must equal 256, 384, or 521.'
assertEquals msg, iae.getMessage() assertEquals msg, iae.getMessage()
} }
} }
@Test
void testFindByNoAlgKey() {
assertNull EcSignatureAlgorithm.findByKey(new TestKey())
}
@Test
void testFindOidKeys() {
for(def alg : EcSignatureAlgorithm.ALGS_BY_OID.values()) {
String name = "${alg.getId()}_OID"
String oid = EcSignatureAlgorithm.metaClass.getAttribute(EcSignatureAlgorithm, name) as String
assertEquals oid, alg.OID
def key = new TestKey(algorithm: oid)
assertSame alg, EcSignatureAlgorithm.findByKey(key)
}
}
@Test
void testFindByWeakKey() {
ECPublicKey key = createMock(ECPublicKey)
ECParameterSpec spec = createMock(ECParameterSpec)
expect(key.getAlgorithm()).andStubReturn("EC")
expect(key.getParams()).andStubReturn(spec)
expect(spec.getOrder()).andStubReturn(BigInteger.ONE)
replay key, spec
assertNull EcSignatureAlgorithm.findByKey(key)
verify key, spec
}
@Test @Test
void testValidateKeyWithoutEcKey() { void testValidateKeyWithoutEcKey() {
def key = createMock(PublicKey) PublicKey key = createMock(PublicKey)
replay key replay key
algs().each { algs().each {
it.validateKey(key, false) it.validateKey(key, false)
@ -68,7 +96,7 @@ class EcSignatureAlgorithmTest {
@Test @Test
void testIsValidRAndSWithoutEcKey() { void testIsValidRAndSWithoutEcKey() {
def key = createMock(PublicKey) PublicKey key = createMock(PublicKey)
replay key replay key
algs().each { algs().each {
it.isValidRAndS(key, Bytes.EMPTY) it.isValidRAndS(key, Bytes.EMPTY)

View File

@ -17,7 +17,6 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.UnsupportedJwtException
import io.jsonwebtoken.security.SignatureAlgorithm
import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.SignatureException
import org.junit.Test import org.junit.Test
@ -28,41 +27,30 @@ import static org.junit.Assert.*
class EdSignatureAlgorithmTest { class EdSignatureAlgorithmTest {
static List<EdSignatureAlgorithm> algs = [Jwts.SIG.EdDSA, Jwts.SIG.Ed25519, Jwts.SIG.Ed448] as List<EdSignatureAlgorithm> static EdSignatureAlgorithm alg = Jwts.SIG.EdDSA as EdSignatureAlgorithm
@Test @Test
void testJcaName() { void testJcaName() {
assertEquals Jwts.SIG.EdDSA.getId(), Jwts.SIG.EdDSA.getJcaName() // the JWT RFC id and the JDK standard name appen to be the same:
assertEquals EdwardsCurve.Ed25519.getId(), Jwts.SIG.Ed25519.getJcaName() assertEquals alg.getId(), alg.getJcaName()
assertEquals EdwardsCurve.Ed448.getId(), Jwts.SIG.Ed448.getJcaName()
} }
@Test @Test
void testId() { void testId() {
//There is only one signature algorithm ID defined for Edwards curve keys per // https://www.rfc-editor.org/rfc/rfc8037#section-3.1:
// https://www.rfc-editor.org/rfc/rfc8037#section-3.1 and assertEquals 'EdDSA', alg.getId()
// https://www.rfc-editor.org/rfc/rfc8037#section-5
//
// As such, the Ed25519 and Ed448 SignatureAlgorithm instances _must_ reflect the same ID since that's the
// only one recognized by the spec. They are effectively just aliases of EdDSA but have the added
// functionality of generating Ed25519 and Ed448 keys, that's the only difference.
for (EdSignatureAlgorithm alg : algs) {
assertEquals Jwts.SIG.EdDSA.getId(), alg.getId() // all aliases of EdDSA per the RFC spec
}
} }
@Test @Test
void testKeyPairBuilder() { void testKeyPairBuilder() {
algs.each { def pair = alg.keyPair().build()
def pair = it.keyPair().build() assertNotNull pair.public
assertNotNull pair.public assertTrue pair.public instanceof PublicKey
assertTrue pair.public instanceof PublicKey String algName = pair.public.getAlgorithm()
String alg = pair.public.getAlgorithm() assertTrue alg.getId().equals(algName) || algName.equals(alg.preferredCurve.getId())
assertTrue Jwts.SIG.EdDSA.getId().equals(alg) || alg.equals(it.preferredCurve.getId())
alg = pair.private.getAlgorithm() algName = pair.private.getAlgorithm()
assertTrue Jwts.SIG.EdDSA.getId().equals(alg) || alg.equals(it.preferredCurve.getId()) assertTrue alg.getId().equals(algName) || algName.equals(alg.preferredCurve.getId())
}
} }
/** /**
@ -71,46 +59,44 @@ class EdSignatureAlgorithmTest {
@Test @Test
void testGetAlgorithmJcaNameWhenCantFindCurve() { void testGetAlgorithmJcaNameWhenCantFindCurve() {
def key = new TestKey(algorithm: 'foo') def key = new TestKey(algorithm: 'foo')
algs.each { def payload = [0x00] as byte[]
def payload = [0x00] as byte[] def req = new DefaultSecureRequest(payload, null, null, key)
def req = new DefaultSecureRequest(payload, null , null, key) assertEquals alg.getJcaName(), alg.getJcaName(req)
assertEquals it.getJcaName(), it.getJcaName(req)
}
} }
@Test @Test
void testEd25519SigVerifyWithEd448() { void testEd25519SigVerifyWithEd448() {
testIncorrectVerificationKey(Jwts.SIG.Ed25519, TestKeys.Ed25519.pair.private, TestKeys.Ed448.pair.public) testIncorrectVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.Ed448.pair.public)
} }
@Test @Test
void testEd25519SigVerifyWithX25519() { void testEd25519SigVerifyWithX25519() {
testInvalidVerificationKey(Jwts.SIG.Ed25519, TestKeys.Ed25519.pair.private, TestKeys.X25519.pair.public) testInvalidVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.X25519.pair.public)
} }
@Test @Test
void testEd25519SigVerifyWithX448() { void testEd25519SigVerifyWithX448() {
testInvalidVerificationKey(Jwts.SIG.Ed25519, TestKeys.Ed25519.pair.private, TestKeys.X448.pair.public) testInvalidVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.X448.pair.public)
} }
@Test @Test
void testEd448SigVerifyWithEd25519() { void testEd448SigVerifyWithEd25519() {
testIncorrectVerificationKey(Jwts.SIG.Ed448, TestKeys.Ed448.pair.private, TestKeys.Ed25519.pair.public) testIncorrectVerificationKey(TestKeys.Ed448.pair.private, TestKeys.Ed25519.pair.public)
} }
@Test @Test
void testEd448SigVerifyWithX25519() { void testEd448SigVerifyWithX25519() {
testInvalidVerificationKey(Jwts.SIG.Ed448, TestKeys.Ed448.pair.private, TestKeys.X25519.pair.public) testInvalidVerificationKey(TestKeys.Ed448.pair.private, TestKeys.X25519.pair.public)
} }
@Test @Test
void testEd448SigVerifyWithX448() { void testEd448SigVerifyWithX448() {
testInvalidVerificationKey(Jwts.SIG.Ed448, TestKeys.Ed448.pair.private, TestKeys.X448.pair.public) testInvalidVerificationKey(TestKeys.Ed448.pair.private, TestKeys.X448.pair.public)
} }
static void testIncorrectVerificationKey(SignatureAlgorithm alg, PrivateKey priv, PublicKey pub) { static void testIncorrectVerificationKey(PrivateKey priv, PublicKey pub) {
try { try {
testSig(alg, priv, pub) testSig(priv, pub)
fail() fail()
} catch (SignatureException expected) { } catch (SignatureException expected) {
// SignatureException message can differ depending on JDK version and if BC is enabled or not: // SignatureException message can differ depending on JDK version and if BC is enabled or not:
@ -124,20 +110,21 @@ class EdSignatureAlgorithmTest {
} }
} }
static void testInvalidVerificationKey(SignatureAlgorithm alg, PrivateKey priv, PublicKey pub) { static void testInvalidVerificationKey(PrivateKey priv, PublicKey pub) {
try { try {
testSig(alg, priv, pub) testSig(priv, pub)
fail() fail()
} catch (UnsupportedJwtException expected) { } catch (UnsupportedJwtException expected) {
def cause = expected.getCause() def cause = expected.getCause()
def keyCurve = EdwardsCurve.forKey(pub) def keyCurve = EdwardsCurve.forKey(pub)
String expectedMsg = "${keyCurve.getId()} keys may not be used with EdDSA digital signatures per https://www.rfc-editor.org/rfc/rfc8037#section-3.2" String expectedMsg = "${keyCurve.getId()} keys may not be used with EdDSA digital signatures per " +
"https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2"
assertEquals expectedMsg, cause.getMessage() assertEquals expectedMsg, cause.getMessage()
} }
} }
static void testSig(SignatureAlgorithm alg, PrivateKey signing, PublicKey verification) { static void testSig(PrivateKey signing, PublicKey verification) {
String jwt = Jwts.builder().setIssuer('me').setAudience('you').signWith(signing, alg).compact() String jwt = Jwts.builder().issuer('me').audience('you').signWith(signing, alg).compact()
def token = Jwts.parser().verifyWith(verification).build().parseClaimsJws(jwt) def token = Jwts.parser().verifyWith(verification).build().parseClaimsJws(jwt)
assertEquals([alg: alg.getId()], token.header) assertEquals([alg: alg.getId()], token.header)
assertEquals 'me', token.getPayload().getIssuer() assertEquals 'me', token.getPayload().getIssuer()

View File

@ -57,11 +57,6 @@ class EdwardsCurveTest {
assertFalse EdwardsCurve.isEdwards(null) assertFalse EdwardsCurve.isEdwards(null)
} }
@Test
void testFindByNullKey() {
assertNull EdwardsCurve.findByKey(null)
}
@Test @Test
void testForKeyNonEdwards() { void testForKeyNonEdwards() {
def alg = 'foo' def alg = 'foo'
@ -74,6 +69,22 @@ class EdwardsCurveTest {
} }
} }
@Test
void testFindByKey() { // happy path test
for(def alg : EdwardsCurve.VALUES) {
def keyPair = alg.keyPair().build()
def pub = keyPair.public
def priv = keyPair.private
assertSame alg, EdwardsCurve.findByKey(pub)
assertSame alg, EdwardsCurve.findByKey(priv)
}
}
@Test
void testFindByNullKey() {
assertNull EdwardsCurve.findByKey(null)
}
@Test @Test
void testFindByKeyUsingEncoding() { void testFindByKeyUsingEncoding() {
curves.each { curves.each {

View File

@ -40,9 +40,9 @@ class Issue542Test {
private static String PS512_0_10_7 = 'eyJhbGciOiJQUzUxMiJ9.eyJpc3MiOiJqb2UifQ.r6sisG-FVaMoIJacMSdYZLWFBVoT6bXmf3X3humLZqzoGfsRw3q9-wJ2oIiR4ua2L_mPnJqyPcjFWoXLUzw-URFSyQEAX_S2mWTBn7avCFsmJUh2fMkplG0ynbIHCqReRDl3moQGallbl-SYgArSRI2HbpVt05xsVbk3BmxB8N8buKbBPfUqwZMicRqNpHxoOc-IXaClc7y93gFNfGBMEwXn2nK_ZFXY03pMBL_MHVsJprPmtGfQw0ZZUv29zZbZTkRb6W6bRCi3jIP8sBMnYDqG3_Oyz9sF74IeOoD9sCpgAuRnrSAXhEb3tr1uBwyT__DOI1ZdT8QGFiRRNpUZDm7g4ub7njhXQ6ppkEY6kEKCCoxSq5sAh6EzZQgAfbpKNXy5VIu8s1nR-iJ8GDpeTcpLRhbX8havNzWjc-kSnU95_D5NFoaKfIjofKideVU46lUdCk-m7q8mOoFz8UEK1cXq3t7ay2jLG_sNvv7oZPe2TC4ovQGiQP0Mt446XBuIvyXSvygD3_ACpRSfpAqVoP7Ce98NkV2QCJxYNX1cZ4Zj4HrNoNWMx81TFoyU7RoUhj4tHcgBt_3_jbCO0OCejwswAFhwYRXP3jXeE2QhLaN1QJ7p97ly8WxjkBRac3I2WAeJhOM4CWhtgDmHAER9571MWp-7n4h4bnx9tXXfV7k' private static String PS512_0_10_7 = 'eyJhbGciOiJQUzUxMiJ9.eyJpc3MiOiJqb2UifQ.r6sisG-FVaMoIJacMSdYZLWFBVoT6bXmf3X3humLZqzoGfsRw3q9-wJ2oIiR4ua2L_mPnJqyPcjFWoXLUzw-URFSyQEAX_S2mWTBn7avCFsmJUh2fMkplG0ynbIHCqReRDl3moQGallbl-SYgArSRI2HbpVt05xsVbk3BmxB8N8buKbBPfUqwZMicRqNpHxoOc-IXaClc7y93gFNfGBMEwXn2nK_ZFXY03pMBL_MHVsJprPmtGfQw0ZZUv29zZbZTkRb6W6bRCi3jIP8sBMnYDqG3_Oyz9sF74IeOoD9sCpgAuRnrSAXhEb3tr1uBwyT__DOI1ZdT8QGFiRRNpUZDm7g4ub7njhXQ6ppkEY6kEKCCoxSq5sAh6EzZQgAfbpKNXy5VIu8s1nR-iJ8GDpeTcpLRhbX8havNzWjc-kSnU95_D5NFoaKfIjofKideVU46lUdCk-m7q8mOoFz8UEK1cXq3t7ay2jLG_sNvv7oZPe2TC4ovQGiQP0Mt446XBuIvyXSvygD3_ACpRSfpAqVoP7Ce98NkV2QCJxYNX1cZ4Zj4HrNoNWMx81TFoyU7RoUhj4tHcgBt_3_jbCO0OCejwswAFhwYRXP3jXeE2QhLaN1QJ7p97ly8WxjkBRac3I2WAeJhOM4CWhtgDmHAER9571MWp-7n4h4bnx9tXXfV7k'
private static Map<SignatureAlgorithm, String> JWS_0_10_7_VALUES = [ private static Map<SignatureAlgorithm, String> JWS_0_10_7_VALUES = [
(Jwts.SIG.PS256): PS256_0_10_7, (Jwts.SIG.RS256): PS256_0_10_7,
(Jwts.SIG.PS384): PS384_0_10_7, (Jwts.SIG.RS384): PS384_0_10_7,
(Jwts.SIG.PS512): PS512_0_10_7 (Jwts.SIG.RS512): PS512_0_10_7
] ]
/** /**
@ -50,10 +50,7 @@ class Issue542Test {
*/ */
@Test @Test
void testRsaSsaPssBackwardsCompatibility() { void testRsaSsaPssBackwardsCompatibility() {
for (alg in JWS_0_10_7_VALUES.keySet()) {
def algs = [Jwts.SIG.PS256, Jwts.SIG.PS384, Jwts.SIG.PS512]
for (alg in algs) {
PublicKey key = TestKeys.forAlgorithm(alg).pair.public PublicKey key = TestKeys.forAlgorithm(alg).pair.public
String jws = JWS_0_10_7_VALUES[alg] String jws = JWS_0_10_7_VALUES[alg]
def token = Jwts.parser().verifyWith(key).build().parseClaimsJws(jws) def token = Jwts.parser().verifyWith(key).build().parseClaimsJws(jws)
@ -66,8 +63,7 @@ class Issue542Test {
* class. This method implementation was retained only to demonstrate how they were created for future reference. * class. This method implementation was retained only to demonstrate how they were created for future reference.
*/ */
static void main(String[] args) { static void main(String[] args) {
def algs = [Jwts.SIG.PS256, Jwts.SIG.PS384, Jwts.SIG.PS512] for (alg in JWS_0_10_7_VALUES.keySet()) {
for (alg in algs) {
PrivateKey privateKey = TestKeys.forAlgorithm(alg).pair.private PrivateKey privateKey = TestKeys.forAlgorithm(alg).pair.private
String jws = Jwts.builder().setIssuer('joe').signWith(privateKey, alg).compact() String jws = Jwts.builder().setIssuer('joe').signWith(privateKey, alg).compact()
println "private static String ${alg.getId()}_0_10_7 = '$jws'" println "private static String ${alg.getId()}_0_10_7 = '$jws'"

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.lang.Conditions
import io.jsonwebtoken.impl.lang.Converters import io.jsonwebtoken.impl.lang.Converters
import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.io.Encoders
@ -23,10 +24,7 @@ import io.jsonwebtoken.security.*
import org.junit.Test import org.junit.Test
import javax.crypto.SecretKey import javax.crypto.SecretKey
import java.security.MessageDigest import java.security.*
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.security.interfaces.ECKey import java.security.interfaces.ECKey
import java.security.interfaces.ECPublicKey import java.security.interfaces.ECPublicKey
@ -186,7 +184,7 @@ class JwksTest {
for (def alg : algs) { for (def alg : algs) {
//get test cert: //get test cert:
X509Certificate cert = TestCertificates.readTestCertificate(alg) X509Certificate cert = TestKeys.forAlgorithm(alg).cert
def builder = Jwks.builder().chain(Arrays.asList(cert)) def builder = Jwks.builder().chain(Arrays.asList(cert))
if (number == 1) { if (number == 1) {
@ -255,19 +253,26 @@ class JwksTest {
@Test @Test
void testAsymmetricJwks() { void testAsymmetricJwks() {
Collection<KeyPairBuilderSupplier> algs = Jwts.SIG.get().values().findAll({ it instanceof SignatureAlgorithm }) as Collection<SignatureAlgorithm> Collection<SignatureAlgorithm> algs = Jwts.SIG.get().values()
.findAll({ it instanceof SignatureAlgorithm }) as Collection<SignatureAlgorithm>
for (def alg : algs) { for (SignatureAlgorithm alg : algs) {
def pair = alg.keyPair().build() def pair = alg.keyPair().build()
PublicKey pub = pair.getPublic() PublicKey pub = pair.getPublic()
PrivateKey priv = pair.getPrivate() PrivateKey priv = pair.getPrivate()
Provider provider = null // assume default
if (pub.getClass().getName().startsWith("org.bouncycastle.")) {
// No native JVM support for the key, so we need to enable BC:
provider = Providers.findBouncyCastle(Conditions.TRUE)
}
// test individual keys // test individual keys
PublicJwk pubJwk = Jwks.builder().key(pub).publicKeyUse("sig").build() PublicJwk pubJwk = Jwks.builder().provider(provider).key(pub).publicKeyUse("sig").build()
assertEquals pub, pubJwk.toKey() assertEquals pub, pubJwk.toKey()
def builder = Jwks.builder().key(priv).publicKeyUse('sig') def builder = Jwks.builder().provider(provider).key(priv).publicKeyUse('sig')
if (alg instanceof EdSignatureAlgorithm) { if (alg instanceof EdSignatureAlgorithm) {
// We haven't implemented EdDSA public-key derivation yet, so public key is required // We haven't implemented EdDSA public-key derivation yet, so public key is required
builder.publicKey(pub) builder.publicKey(pub)
@ -282,12 +287,13 @@ class JwksTest {
assertEquals priv, jwkPair.getPrivate() assertEquals priv, jwkPair.getPrivate()
// test pair // test pair
builder = Jwks.builder().provider(provider)
if (pub instanceof ECKey) { if (pub instanceof ECKey) {
builder = Jwks.builder().ecKeyPair(pair) builder = builder.ecKeyPair(pair)
} else if (pub instanceof RSAKey) { } else if (pub instanceof RSAKey) {
builder = Jwks.builder().rsaKeyPair(pair) builder = builder.rsaKeyPair(pair)
} else { } else {
builder = Jwks.builder().octetKeyPair(pair) builder = builder.octetKeyPair(pair)
} }
privJwk = builder.publicKeyUse("sig").build() as PrivateJwk privJwk = builder.publicKeyUse("sig").build() as PrivateJwk
assertEquals priv, privJwk.toKey() assertEquals priv, privJwk.toKey()

View File

@ -16,11 +16,14 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.security.SecurityException
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.Provider
import java.security.cert.CertificateEncodingException import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import static org.easymock.EasyMock.* import static org.easymock.EasyMock.*
@ -28,6 +31,13 @@ import static org.junit.Assert.*
class JwtX509StringConverterTest { class JwtX509StringConverterTest {
private JwtX509StringConverter converter
@Before
void setUp() {
converter = JwtX509StringConverter.INSTANCE
}
@Test @Test
void testApplyToThrowsEncodingException() { void testApplyToThrowsEncodingException() {
@ -38,12 +48,11 @@ class JwtX509StringConverterTest {
replay cert replay cert
try { try {
JwtX509StringConverter.INSTANCE.applyTo(cert) converter.applyTo(cert)
fail() fail()
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
String expectedMsg = 'Unable to access X509Certificate encoded bytes necessary to perform DER ' + String expectedMsg = "Unable to access X509Certificate encoded bytes necessary to perform DER " +
'Base64-encoding. Certificate: {EasyMock for class java.security.cert.X509Certificate}. ' + "Base64-encoding. Certificate: {${cert}}. Cause: " + ex.getMessage()
'Cause: ' + ex.getMessage()
assertSame ex, expected.getCause() assertSame ex, expected.getCause()
assertEquals expectedMsg, expected.getMessage() assertEquals expectedMsg, expected.getMessage()
} }
@ -59,7 +68,7 @@ class JwtX509StringConverterTest {
replay cert replay cert
try { try {
JwtX509StringConverter.INSTANCE.applyTo(cert) converter.applyTo(cert)
fail() fail()
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
String expectedMsg = 'X509Certificate encoded bytes cannot be null or empty. Certificate: ' + String expectedMsg = 'X509Certificate encoded bytes cannot be null or empty. Certificate: ' +
@ -71,12 +80,13 @@ class JwtX509StringConverterTest {
} }
@Test @Test
void testApplyFromThrowsCertificateException() { void testApplyFromBadBase64() {
final CertificateException ex = new CertificateException('nope')
def converter = new JwtX509StringConverter() { converter = new JwtX509StringConverter() {
@Override @Override
protected CertificateFactory newCertificateFactory() throws CertificateException { protected X509Certificate toCert(byte[] der, Provider provider) throws SecurityException {
throw new CertificateException("nope") assertNull provider // ensures not called twice (no fallback) because der bytes aren't available
throw ex
} }
} }
@ -87,6 +97,51 @@ class JwtX509StringConverterTest {
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
String expectedMsg = "Unable to convert Base64 String '$s' to X509Certificate instance. Cause: nope" String expectedMsg = "Unable to convert Base64 String '$s' to X509Certificate instance. Cause: nope"
assertEquals expectedMsg, expected.getMessage() assertEquals expectedMsg, expected.getMessage()
assertSame ex, expected.getCause()
}
}
@Test
void testApplyFromRsaSsaPssCertStringWithSuccessfulBCRetry() {
final CertificateException ex = new CertificateException("nope: ${RsaSignatureAlgorithm.PSS_OID}")
converter = new JwtX509StringConverter() {
@Override
protected X509Certificate toCert(byte[] der, Provider provider) throws SecurityException {
if (provider == null) {
throw ex // first time called, throw ex (simulates JVM parse failure)
} else { // this time BC is available:
assertNotNull provider
return super.toCert(der, provider)
}
}
}
def cert = TestKeys.RS256.cert
def validBase64 = Encoders.BASE64.encode(cert.getEncoded())
assertEquals cert, converter.applyFrom(validBase64)
}
@Test
void testApplyFromRsaSsaPssCertStringWithFailedBCRetry() {
final String exMsg = "nope: ${RsaSignatureAlgorithm.PSS_OID}"
final CertificateException ex = new CertificateException(exMsg)
converter = new JwtX509StringConverter() {
@Override
protected X509Certificate toCert(byte[] der, Provider provider) throws SecurityException {
throw ex // ensure fails first and second time
}
}
def cert = TestKeys.RS256.cert
def validBase64 = Encoders.BASE64.encode(cert.getEncoded())
try {
converter.applyFrom(validBase64)
fail()
} catch (IllegalArgumentException expected) {
String expectedMsg = "Unable to convert Base64 String '$validBase64' to X509Certificate instance. Cause: ${exMsg}"
assertEquals expectedMsg, expected.getMessage()
assertSame ex, expected.getCause()
} }
} }
} }

View File

@ -34,6 +34,7 @@ class PrivateConstructorsTest {
new Jwts.ENC() new Jwts.ENC()
new Jwts.KEY() new Jwts.KEY()
new Jwts.ZIP() new Jwts.ZIP()
new Jwks.CRV()
new Jwks.HASH() new Jwks.HASH()
} }
} }

View File

@ -16,10 +16,12 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.lang.CheckedFunction
import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.WeakKeyException import io.jsonwebtoken.security.WeakKeyException
import org.junit.Test import org.junit.Test
import java.security.KeyPair
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.PublicKey import java.security.PublicKey
import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPrivateKey
@ -30,15 +32,13 @@ import static org.junit.Assert.*
class RsaSignatureAlgorithmTest { class RsaSignatureAlgorithmTest {
static Collection<RsaSignatureAlgorithm> algs() { static final Collection<RsaSignatureAlgorithm> algs = Jwts.SIG.get().values().findAll({
return Jwts.SIG.get().values().findAll({ it instanceof RsaSignatureAlgorithm
it.id.startsWith("RS") || it.id.startsWith("PS") }) as Collection<RsaSignatureAlgorithm>
}) as Collection<RsaSignatureAlgorithm>
}
@Test @Test
void testKeyPairBuilder() { void testKeyPairBuilder() {
algs().each { algs.each {
def pair = it.keyPair().build() def pair = it.keyPair().build()
assertNotNull pair.public assertNotNull pair.public
assertTrue pair.public instanceof RSAPublicKey assertTrue pair.public instanceof RSAPublicKey
@ -48,16 +48,11 @@ class RsaSignatureAlgorithmTest {
} }
} }
@Test(expected = IllegalArgumentException)
void testWeakPreferredKeyLength() {
new RsaSignatureAlgorithm(256, 1024) //must be >= 2048
}
@Test @Test
void testValidateKeyWithoutRsaKey() { void testValidateKeyWithoutRsaKey() {
PublicKey key = createMock(PublicKey) PublicKey key = createMock(PublicKey)
replay key replay key
algs().each { algs.each {
it.validateKey(key, false) it.validateKey(key, false)
//no exception - can't check for RSAKey fields (e.g. PKCS11 or HSM key) //no exception - can't check for RSAKey fields (e.g. PKCS11 or HSM key)
} }
@ -72,7 +67,9 @@ class RsaSignatureAlgorithmTest {
Jwts.SIG.RS256.digest(request) Jwts.SIG.RS256.digest(request)
fail() fail()
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
assertTrue e.getMessage().startsWith("Asymmetric key signatures must be created with PrivateKeys. The specified key is of type: ") String expected = "RS256 signing keys must be PrivateKeys (implement java.security.PrivateKey). " +
"Provided key type: ${key.getClass().getName()}."
assertEquals expected, e.getMessage()
} }
} }
@ -80,15 +77,102 @@ class RsaSignatureAlgorithmTest {
void testValidateSigningKeyWeakKey() { void testValidateSigningKeyWeakKey() {
def gen = KeyPairGenerator.getInstance("RSA") def gen = KeyPairGenerator.getInstance("RSA")
gen.initialize(1024) //too week for any JWA RSA algorithm gen.initialize(1024) //too week for any JWA RSA algorithm
def pair = gen.generateKeyPair() def rsaPair = gen.generateKeyPair()
def request = new DefaultSecureRequest(new byte[1], null, null, pair.getPrivate()) def provider = RsaSignatureAlgorithm.PS256.getProvider() // in case BC was loaded
Jwts.SIG.get().values().findAll({ it.id.startsWith('RS') || it.id.startsWith('PS') }).each { def pssPair = new JcaTemplate(RsaSignatureAlgorithm.PSS_JCA_NAME, provider)
.withKeyPairGenerator(new CheckedFunction<KeyPairGenerator, KeyPair>() {
@Override
KeyPair apply(KeyPairGenerator generator) throws Exception {
generator.initialize(1024)
return generator.generateKeyPair()
}
})
algs.each {
def pair = it.getId().startsWith("PS") ? pssPair : rsaPair
def request = new DefaultSecureRequest(new byte[1], null, null, pair.getPrivate())
try { try {
it.digest(request) it.digest(request)
fail() fail()
} catch (WeakKeyException expected) { } catch (WeakKeyException expected) {
String id = it.getId()
String section = id.startsWith('PS') ? '3.5' : '3.3'
String msg = "The signing key's size is 1024 bits which is not secure enough for the ${it.getId()} " +
"algorithm. The JWT JWA Specification (RFC 7518, Section ${section}) states that RSA keys " +
"MUST have a size >= 2048 bits. Consider using the Jwts.SIG.${id}.keyPair() " +
"builder to create a KeyPair guaranteed to be secure enough for ${id}. See " +
"https://tools.ietf.org/html/rfc7518#section-${section} for more information."
assertEquals msg, expected.getMessage()
} }
} }
} }
@Test
void testFindByKeyWithNoAlgorithm() {
assertNull RsaSignatureAlgorithm.findByKey(new TestPrivateKey())
}
@Test
void testFindByKeyInvalidAlgorithm() {
assertNull DefaultMacAlgorithm.findByKey(new TestPrivateKey(algorithm: 'foo'))
}
@Test
void testFindByKey() {
for (def alg : algs) {
def pair = TestKeys.forAlgorithm(alg).pair
assertSame alg, RsaSignatureAlgorithm.findByKey(pair.public)
assertSame alg, RsaSignatureAlgorithm.findByKey(pair.private)
}
}
@Test
void testFindByKeyNull() {
assertNull RsaSignatureAlgorithm.findByKey(null)
}
@Test
void testFindByNonAsymmetricKey() {
assertNull RsaSignatureAlgorithm.findByKey(TestKeys.HS256)
}
@Test
void testFindByWeakKey() {
for (def alg : algs) {
def pair = TestKeys.forAlgorithm(alg).pair
byte[] mag = new byte[255] // one byte less than 256 (2048 bits) which is the minimum
Randoms.secureRandom().nextBytes(mag)
def modulus = new BigInteger(1, mag)
//def modulus = pair.public.modulus
def weakPub = new TestRSAKey(pair.public); weakPub.modulus = modulus
def weakPriv = new TestRSAKey(pair.private); weakPriv.modulus = modulus
assertNull RsaSignatureAlgorithm.findByKey(weakPub)
assertNull RsaSignatureAlgorithm.findByKey(weakPriv)
}
}
@Test
void testFindByLargerThanExpectedKey() {
for (def alg : algs) {
def pair = TestKeys.forAlgorithm(alg).pair
def mag = new byte[(alg.preferredKeyBitLength / Byte.SIZE) + 1] // one byte more than required
Randoms.secureRandom().nextBytes(mag)
def modulus = new BigInteger(1, mag)
def strongPub = new TestRSAKey(pair.public); strongPub.modulus = modulus
def strongPriv = new TestRSAKey(pair.private); strongPriv.modulus = modulus
assertSame alg, RsaSignatureAlgorithm.findByKey(strongPub)
assertSame alg, RsaSignatureAlgorithm.findByKey(strongPriv)
}
}
@Test
void testFindByKeyOid() {
for (def entry : RsaSignatureAlgorithm.PKCSv15_ALGS.entrySet()) {
def oid = entry.getKey()
def alg = entry.getValue()
def oidKey = new TestPrivateKey(algorithm: oid)
assertSame alg, RsaSignatureAlgorithm.findByKey(oidKey)
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2022 jsonwebtoken.io * Copyright © 2023 jsonwebtoken.io
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,10 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Identifiable; import org.junit.Test
import io.jsonwebtoken.security.KeyPairBuilderSupplier;
public interface Curve extends Identifiable, KeyPairBuilderSupplier { import static org.junit.Assert.assertNull
class StandardSecureDigestAlgorithmsTest {
@Test
void testFindByPublicSigningKey() {
//public keys are not supported for signing:
assertNull StandardSecureDigestAlgorithms.findBySigningKey(new TestPublicKey())
}
} }

View File

@ -15,12 +15,14 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Identifiable
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.impl.lang.CheckedFunction
import io.jsonwebtoken.impl.lang.Conditions
import io.jsonwebtoken.lang.Assert import io.jsonwebtoken.lang.Assert
import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.lang.Classes
import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.SecureDigestAlgorithm import io.jsonwebtoken.security.SignatureAlgorithm
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.openssl.PEMKeyPair import org.bouncycastle.openssl.PEMKeyPair
@ -53,6 +55,9 @@ import java.security.spec.X509EncodedKeySpec
*/ */
class TestCertificates { class TestCertificates {
private static Provider BC = Assert.notNull(Providers.findBouncyCastle(Conditions.TRUE),
"BC must be available to test cases.")
private static InputStream getResourceStream(String filename) { private static InputStream getResourceStream(String filename) {
String packageName = TestCertificates.class.getPackage().getName() String packageName = TestCertificates.class.getPackage().getName()
String resourcePath = Strings.replace(packageName, ".", "/") + "/" + filename String resourcePath = Strings.replace(packageName, ".", "/") + "/" + filename
@ -60,55 +65,75 @@ class TestCertificates {
} }
private static PEMParser getParser(String filename) { private static PEMParser getParser(String filename) {
InputStream is = Classes.getResourceAsStream('io/jsonwebtoken/impl/security/' + filename) InputStream is = getResourceStream(filename)
return new PEMParser(new BufferedReader(new InputStreamReader(is, StandardCharsets.ISO_8859_1))) return new PEMParser(new BufferedReader(new InputStreamReader(is, StandardCharsets.ISO_8859_1)))
} }
private static String getKeyFilePrefix(SecureDigestAlgorithm alg) { private static <T> T bcFallback(final Identifiable alg, Closure<T> closure) {
if (alg instanceof EdSignatureAlgorithm) { Provider provider = alg.getProvider() as Provider // null on JVMs with native support for `alg`
return alg.preferredCurve.getId()
}
return alg.getId()
}
static X509Certificate readTestCertificate(SecureDigestAlgorithm alg) {
InputStream is = getResourceStream(getKeyFilePrefix(alg) + '.crt.pem')
try { try {
JcaTemplate template = new JcaTemplate("X.509", alg.getProvider()) return closure.call(alg, provider)
template.withCertificateFactory(new CheckedFunction<CertificateFactory, X509Certificate>() { } catch (Throwable t) {
@Override
X509Certificate apply(CertificateFactory factory) throws Exception { // All test cert and key files were created with OpenSSL, so the only time this should happen is if the
return (X509Certificate) factory.generateCertificate(is) // JDK natively supports the alg, but has a bug that prevents it from reading the file correctly. So
} // we account for those bugs here as indicators that we should retry with BC.
})
} finally { // https://bugs.openjdk.org/browse/JDK-8242556
is.close() // Oracle only backported this fix to JDK 8u271+, 11.0.9+, and 15+, so we'll need to fall back to
// BC (which can read the files correctly) on JDK 9, 10, 12, 13, and 14.
boolean jdk8242556Bug = alg instanceof SignatureAlgorithm && alg.getId().startsWith("PS") &&
t.message.contains('Unsupported algorithm 1.2.840.113549.1.1.10')
// https://bugs.openjdk.org/browse/JDK-8213363) for X25519 and X448 encoded keys. JDK 11's
// SunCE provider incorrectly expects an ASN.1 OCTET STRING (without the DER tag/length prefix)
// when it should actually be a BER-encoded OCTET STRING (with the tag/length prefix).
boolean jdk8213363Bug = alg instanceof EdwardsCurve && !((EdwardsCurve) alg).isSignatureCurve() &&
System.getProperty("java.version").startsWith("11")
// Now assert that we're experiencing one of the expected bugs, because if not, we need to know about
// it in test results and fix this implementation:
if (!jdk8242556Bug && !jdk8213363Bug) {
String msg = "Unable to read ${alg.getId()} file: ${t.message}"
throw new IllegalStateException(msg, t)
}
// otherwise, we are indeed experiencing one of the expected bugs, so use BC as a backup:
return closure.call(alg, BC)
} }
} }
static PublicKey readTestPublicKey(EdwardsCurve crv) { private static def readPublicKey = { Identifiable alg, Provider provider ->
PEMParser parser = getParser(crv.getId() + '.pub.pem') PEMParser parser = getParser(alg.id + '.pub.pem')
try { parser.withCloseable {
SubjectPublicKeyInfo info = parser.readObject() as SubjectPublicKeyInfo SubjectPublicKeyInfo info = parser.readObject() as SubjectPublicKeyInfo
def template = new JcaTemplate(crv.getJcaName(), crv.getProvider()) JcaTemplate template = new JcaTemplate(alg.getJcaName(), provider)
return template.withKeyFactory(new CheckedFunction<KeyFactory, PublicKey>() { template.withKeyFactory(new CheckedFunction<KeyFactory, PublicKey>() {
@Override @Override
PublicKey apply(KeyFactory keyFactory) throws Exception { PublicKey apply(KeyFactory keyFactory) throws Exception {
return keyFactory.generatePublic(new X509EncodedKeySpec(info.getEncoded())) return keyFactory.generatePublic(new X509EncodedKeySpec(info.getEncoded()))
} }
}) })
} finally {
parser.close()
} }
} }
static PrivateKey readTestPrivateKey(SecureDigestAlgorithm alg) { private static def readCert = { Identifiable alg, Provider provider ->
return readTestPrivateKey(getKeyFilePrefix(alg), alg.getProvider()) InputStream is = getResourceStream(alg.id + '.crt.pem')
is.withCloseable {
JcaTemplate template = new JcaTemplate("X.509", provider)
template.withCertificateFactory(new CheckedFunction<CertificateFactory, X509Certificate>() {
@Override
X509Certificate apply(CertificateFactory factory) throws Exception {
return (X509Certificate) factory.generateCertificate(it)
}
})
}
} }
static PrivateKey readTestPrivateKey(String filenamePrefix, Provider provider) { private static def readPrivateKey = { Identifiable alg, Provider provider ->
PEMParser parser = getParser(filenamePrefix + '.key.pem') final String id = alg.id
try { PEMParser parser = getParser(id + '.key.pem')
parser.withCloseable {
PrivateKeyInfo info PrivateKeyInfo info
Object object = parser.readObject() Object object = parser.readObject()
if (object instanceof PEMKeyPair) { if (object instanceof PEMKeyPair) {
@ -116,12 +141,11 @@ class TestCertificates {
} else { } else {
info = (PrivateKeyInfo) object info = (PrivateKeyInfo) object
} }
def converter = new JcaPEMKeyConverter() def converter = new JcaPEMKeyConverter()
if (provider != null) { if (provider != null) {
converter.setProvider(provider) converter.setProvider(provider)
} else if (filenamePrefix.startsWith("X") && System.getProperty("java.version").startsWith("11")) { } else if (id.startsWith("X") && System.getProperty("java.version").startsWith("11")) {
EdwardsCurve curve = EdwardsCurve.findById(filenamePrefix) EdwardsCurve curve = EdwardsCurve.findById(id)
Assert.notNull(curve, "Curve cannot be null.") Assert.notNull(curve, "Curve cannot be null.")
int expectedByteLen = ((curve.keyBitLength + 7) / 8) as int int expectedByteLen = ((curve.keyBitLength + 7) / 8) as int
// Address the [JDK 11 SunCE provider bug](https://bugs.openjdk.org/browse/JDK-8213363) for X25519 // Address the [JDK 11 SunCE provider bug](https://bugs.openjdk.org/browse/JDK-8213363) for X25519
@ -139,20 +163,22 @@ class TestCertificates {
return curve.toPrivateKey(keyOctets, null) return curve.toPrivateKey(keyOctets, null)
} }
return converter.getPrivateKey(info) return converter.getPrivateKey(info)
} finally {
parser.close()
} }
} }
static TestKeys.Bundle readBundle(EdwardsCurve curve) { static TestKeys.Bundle readBundle(EdwardsCurve curve) {
PublicKey pub = readTestPublicKey(curve) //PublicKey pub = readTestPublicKey(curve)
PrivateKey priv = readTestPrivateKey(curve.getId(), curve.getProvider()) //PrivateKey priv = readTestPrivateKey(curve)
PublicKey pub = bcFallback(curve, readPublicKey) as PublicKey
PrivateKey priv = bcFallback(curve, readPrivateKey) as PrivateKey
return new TestKeys.Bundle(pub, priv) return new TestKeys.Bundle(pub, priv)
} }
static TestKeys.Bundle readAsymmetricBundle(SecureDigestAlgorithm alg) { static TestKeys.Bundle readBundle(Identifiable alg) {
X509Certificate cert = readTestCertificate(alg) //X509Certificate cert = readTestCertificate(alg)
PrivateKey priv = readTestPrivateKey(alg) //PrivateKey priv = readTestPrivateKey(alg)
X509Certificate cert = bcFallback(alg, readCert) as X509Certificate
PrivateKey priv = bcFallback(alg, readPrivateKey) as PrivateKey
return new TestKeys.Bundle(cert, priv) return new TestKeys.Bundle(cert, priv)
} }
} }

View File

@ -18,9 +18,7 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Identifiable import io.jsonwebtoken.Identifiable
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.lang.Collections
import io.jsonwebtoken.security.KeyBuilderSupplier import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.SecretKeyBuilder
import io.jsonwebtoken.security.SignatureAlgorithm
import javax.crypto.SecretKey import javax.crypto.SecretKey
import java.security.KeyPair import java.security.KeyPair
@ -65,14 +63,14 @@ class TestKeys {
// ======================================================= // =======================================================
// Elliptic Curve Keys & Certificates // Elliptic Curve Keys & Certificates
// ======================================================= // =======================================================
static Bundle ES256 = TestCertificates.readAsymmetricBundle(Jwts.SIG.ES256) static Bundle ES256 = TestCertificates.readBundle(Jwts.SIG.ES256)
static Bundle ES384 = TestCertificates.readAsymmetricBundle(Jwts.SIG.ES384) static Bundle ES384 = TestCertificates.readBundle(Jwts.SIG.ES384)
static Bundle ES512 = TestCertificates.readAsymmetricBundle(Jwts.SIG.ES512) static Bundle ES512 = TestCertificates.readBundle(Jwts.SIG.ES512)
static Set<Bundle> EC = Collections.setOf(ES256, ES384, ES512) static Set<Bundle> EC = Collections.setOf(ES256, ES384, ES512)
static Bundle EdDSA = TestCertificates.readAsymmetricBundle(Jwts.SIG.EdDSA) static Bundle EdDSA = TestCertificates.readBundle(Jwts.SIG.EdDSA)
static Bundle Ed25519 = TestCertificates.readAsymmetricBundle(Jwts.SIG.Ed25519) static Bundle Ed25519 = TestCertificates.readBundle(Jwks.CRV.Ed25519)
static Bundle Ed448 = TestCertificates.readAsymmetricBundle(Jwts.SIG.Ed448) static Bundle Ed448 = TestCertificates.readBundle(Jwks.CRV.Ed448)
static Bundle X25519 = TestCertificates.readBundle(EdwardsCurve.X25519) static Bundle X25519 = TestCertificates.readBundle(EdwardsCurve.X25519)
static Bundle X448 = TestCertificates.readBundle(EdwardsCurve.X448) static Bundle X448 = TestCertificates.readBundle(EdwardsCurve.X448)
static Set<Bundle> EdEC = Collections.setOf(EdDSA, Ed25519, Ed448, X25519, X448) static Set<Bundle> EdEC = Collections.setOf(EdDSA, Ed25519, Ed448, X25519, X448)
@ -80,10 +78,15 @@ class TestKeys {
// ======================================================= // =======================================================
// RSA Keys & Certificates // RSA Keys & Certificates
// ======================================================= // =======================================================
static Bundle RS256 = TestCertificates.readAsymmetricBundle(Jwts.SIG.RS256) static Bundle RS256 = TestCertificates.readBundle(Jwts.SIG.RS256)
static Bundle RS384 = TestCertificates.readAsymmetricBundle(Jwts.SIG.RS384) static Bundle RS384 = TestCertificates.readBundle(Jwts.SIG.RS384)
static Bundle RS512 = TestCertificates.readAsymmetricBundle(Jwts.SIG.RS512) static Bundle RS512 = TestCertificates.readBundle(Jwts.SIG.RS512)
static Set<Bundle> RSA = Collections.setOf(RS256, RS384, RS512) static Bundle PS256 = TestCertificates.readBundle(Jwts.SIG.PS256)
static Bundle PS384 = TestCertificates.readBundle(Jwts.SIG.PS384)
static Bundle PS512 = TestCertificates.readBundle(Jwts.SIG.PS512)
// static Set<Bundle> PKCSv15 = Collections.setOf(RS256, RS384, RS512)
// static Set<Bundle> RSASSA_PSS = Collections.setOf(PS256, PS384, PS512)
static Set<Bundle> RSA = Collections.setOf(RS256, RS384, RS512, PS256, PS384, PS512)
static Set<Bundle> ASYM = new LinkedHashSet<>() static Set<Bundle> ASYM = new LinkedHashSet<>()
static { static {
@ -92,22 +95,8 @@ class TestKeys {
ASYM.addAll(RSA) ASYM.addAll(RSA)
} }
static <T extends KeyBuilderSupplier<SecretKey, SecretKeyBuilder> & Identifiable> SecretKey forAlgorithm(T alg) { static Bundle forAlgorithm(Identifiable alg) {
String id = alg.getId() String id = alg.getId()
if (id.contains('-')) {
id = id.replace('-', '_')
}
return TestKeys.metaClass.getAttribute(TestKeys, id) as SecretKey
}
static Bundle forAlgorithm(SignatureAlgorithm alg) {
String id = alg.getId()
if (id.startsWith('PS')) {
id = 'R' + id.substring(1) //keys for PS* algs are the same as RS algs
}
if (alg instanceof EdSignatureAlgorithm) {
id = alg.preferredCurve.getId()
}
return TestKeys.metaClass.getAttribute(TestKeys, id) as Bundle return TestKeys.metaClass.getAttribute(TestKeys, id) as Bundle
} }
@ -126,10 +115,14 @@ class TestKeys {
this.pair = new KeyPair(cert.getPublicKey(), privateKey) this.pair = new KeyPair(cert.getPublicKey(), privateKey)
} }
Bundle(PublicKey pub, PrivateKey priv) { Bundle(KeyPair pair) {
this.cert = null this.cert = null
this.chain = Collections.emptyList() this.chain = Collections.emptyList()
this.pair = new KeyPair(pub, priv) this.pair = pair
}
Bundle(PublicKey pub, PrivateKey priv) {
this(new KeyPair(pub, priv))
} }
} }
} }

View File

@ -20,28 +20,18 @@ import java.security.interfaces.RSAKey
class TestRSAKey extends TestKey implements RSAKey { class TestRSAKey extends TestKey implements RSAKey {
final def src final def src
BigInteger modulus
TestRSAKey(def key) { TestRSAKey(def key) {
this.src = key this.src = key
} this.algorithm = key?.getAlgorithm()
this.format = key?.getFormat()
@Override this.encoded = key?.getEncoded()
String getAlgorithm() { this.modulus = key?.getModulus()
return src.algorithm
}
@Override
String getFormat() {
return src.format
}
@Override
byte[] getEncoded() {
return src.encoded
} }
@Override @Override
BigInteger getModulus() { BigInteger getModulus() {
return src.getModulus() return this.modulus
} }
} }

View File

@ -1,59 +0,0 @@
/*
* Copyright (C) 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.Jwts
import org.junit.Test
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertNull
/**
* The {@link StandardAlgorithmsTest} class contains the majority of test cases relevant for the
* {@link Jwts.SIG} implementation. This test class exists for additional checks/assertions
* for the convenience Ed2448 and Ed25519 aliases.
*/
class JwtsSigTest {
private static final def registry = Jwts.SIG.get()
@Test(expected = ClassCastException)
void testGetWithoutString() {
assertNull registry.get(1)
}
@Test
void testFindEd448() {
assertNotNull registry.get('Ed448')
}
@Test
void testFindEd448CaseInsensitive() {
assertNotNull registry.get('ED448')
assertNotNull registry.get('ed448')
}
@Test
void testFindEd25519() {
assertNotNull registry.get('Ed25519')
}
@Test
void testFindEd25519CaseInsensitive() {
assertNotNull registry.get('ED25519')
assertNotNull registry.get('ed25519')
}
}

View File

@ -76,12 +76,13 @@ class KeysImplTest {
PublicKey pub = pair.getPublic() PublicKey pub = pair.getPublic()
assert pub instanceof RSAPublicKey assert pub instanceof RSAPublicKey
assertEquals alg.familyName, pub.algorithm def keyAlgName = alg.jcaName.equals("RSASSA-PSS") ? "RSASSA-PSS" : alg.familyName
assertEquals keyAlgName, pub.algorithm
assertEquals alg.digestLength * 8, pub.modulus.bitLength() assertEquals alg.digestLength * 8, pub.modulus.bitLength()
PrivateKey priv = pair.getPrivate() PrivateKey priv = pair.getPrivate()
assert priv instanceof RSAPrivateKey assert priv instanceof RSAPrivateKey
assertEquals alg.familyName, priv.algorithm assertEquals keyAlgName, priv.algorithm
assertEquals alg.digestLength * 8, priv.modulus.bitLength() assertEquals alg.digestLength * 8, priv.modulus.bitLength()
} else if (alg.isEllipticCurve()) { } else if (alg.isEllipticCurve()) {

View File

@ -19,7 +19,9 @@ package io.jsonwebtoken.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.DefaultJwtBuilder import io.jsonwebtoken.impl.DefaultJwtBuilder
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.security.* import io.jsonwebtoken.impl.security.EdwardsCurve
import io.jsonwebtoken.impl.security.KeysBridge
import io.jsonwebtoken.impl.security.PasswordSpec
import org.junit.Test import org.junit.Test
import javax.crypto.SecretKey import javax.crypto.SecretKey
@ -154,12 +156,13 @@ class KeysTest {
PublicKey pub = pair.getPublic() PublicKey pub = pair.getPublic()
assert pub instanceof RSAPublicKey assert pub instanceof RSAPublicKey
assertEquals alg.familyName, pub.algorithm def keyAlgName = alg.jcaName.equals("RSASSA-PSS") ? "RSASSA-PSS" : alg.familyName
assertEquals keyAlgName, pub.algorithm
assertEquals alg.digestLength * 8, pub.modulus.bitLength() assertEquals alg.digestLength * 8, pub.modulus.bitLength()
PrivateKey priv = pair.getPrivate() PrivateKey priv = pair.getPrivate()
assert priv instanceof RSAPrivateKey assert priv instanceof RSAPrivateKey
assertEquals alg.familyName, priv.algorithm assertEquals keyAlgName, priv.algorithm
assertEquals alg.digestLength * 8, priv.modulus.bitLength() assertEquals alg.digestLength * 8, priv.modulus.bitLength()
} else if (alg.isEllipticCurve()) { } else if (alg.isEllipticCurve()) {
@ -205,11 +208,16 @@ class KeysTest {
} }
@Test @Test
void testKeyPairFor() { void testKeyPairBuilder() {
for (SecureDigestAlgorithm alg : Jwts.SIG.get().values()) { Collection<SignatureAlgorithm> algs = Jwts.SIG.get().values()
.findAll({it instanceof KeyPairBuilderSupplier}) as Collection<SignatureAlgorithm>
if (alg instanceof RsaSignatureAlgorithm) { for (SignatureAlgorithm alg : algs) {
String id = alg.getId()
if (id.startsWith("RS") || id.startsWith("PS")) {
def pair = alg.keyPair().build() def pair = alg.keyPair().build()
assertNotNull pair assertNotNull pair
@ -222,7 +230,7 @@ class KeysTest {
assert priv instanceof RSAPrivateKey assert priv instanceof RSAPrivateKey
assertEquals alg.preferredKeyBitLength, priv.modulus.bitLength() assertEquals alg.preferredKeyBitLength, priv.modulus.bitLength()
} else if (alg instanceof EdSignatureAlgorithm) { } else if (id == "EdDSA") {
def pair = alg.keyPair().build() def pair = alg.keyPair().build()
assertNotNull pair assertNotNull pair
@ -235,7 +243,7 @@ class KeysTest {
assert priv instanceof PrivateKey assert priv instanceof PrivateKey
assertTrue EdwardsCurve.isEdwards(priv) assertTrue EdwardsCurve.isEdwards(priv)
} else if (alg instanceof EcSignatureAlgorithm) { } else if (id.startsWith("ES")) {
def pair = alg.keyPair().build() def pair = alg.keyPair().build()
assertNotNull pair assertNotNull pair
@ -267,8 +275,8 @@ class KeysTest {
assertEquals alg.orderBitLength, priv.params.order.bitLength() assertEquals alg.orderBitLength, priv.params.order.bitLength()
} else { } else {
assertFalse alg instanceof SignatureAlgorithm // unexpected algorithm that is not accounted for in this test:
//assert we've accounted for all asymmetric ones above fail()
} }
} }
} }

View File

@ -87,4 +87,16 @@ class StandardAlgorithmsTest {
} }
} }
@SuppressWarnings('GroovyUnusedCatchParameter')
@Test
void testGetWithoutStringKey() {
registries.each { reg ->
try {
assertNull reg.get(2) // not a string, should fail
fail()
} catch (ClassCastException expected) { // allowed per Map#get contract
}
}
}
} }

View File

@ -15,22 +15,27 @@
# #
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDRDCCAiwCCQCgd9OzR40NCDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV MIIERTCCAvmgAwIBAgIUHAjkgfU2dEiUaT+VDBS3bXQgDcswQQYJKoZIhvcNAQEK
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw AKIDAgEgMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD
MzIzMDQzM1oYDzMwMjAwMjExMjMwNDMzWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP BgNVBAsMBGpqd3QwIBcNMjMwODE0MTk0NjU4WhgPMzAyMzA4MjIxOTQ2NThaMGMx
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIIBIjANBgkqhkiG9w0BAQEF CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g
AAOCAQ8AMIIBCgKCAQEAzkH0MwxQ2cUFWsvOPVFqI/dk2EFTjQolCy97mI5/wYCb RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq
aOoZ9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2Nv3pBJ/XCaybBlp61CLez7dQ d3QwggFWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG
2h5jUFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjvXQcYx5VmS4mIqTxj5gVGtQVi 9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQ8AMIIBCgKCAQEA0b7eGccEnfOG
0GXdH6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pThwvjpMwCZnkxCS0RKa9y4+5+ 0bd5zptCwGEhd+YmtvgaYKsmdxTK9y1vXq13ZsSyd4IpPZIVpam2BYgmxXix4ikH
7MkC33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo4ZBMloweW0/l8MOdVxeX7M/7 beKzydJG3bvr2J+f7Z9bgPZZNB9NJjX3j4IlEkGVI4/ZrROKrC004ItGcHqUJdlq
XSC1ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo8QIDAQABMA0GCSqGSIb3DQEB 48W3JFyKWswBrFYxaCR2HyTj6uI+dsxwuPWse/rqyu+1NKN7uxmh1RXAKa+kYVkK
CwUAA4IBAQBGbfmJumXEHMLko1ioY/eY5EYgrBRJAuuAMGqBZmK+1Iy2CqB90aEh Smyta2UN+TJZ3bj0a0Sk9cyAqYVGI+kPKF+BYItgh6W+1cmYEscBjNkgcLbV/hen
ve+jXjIBsrvXRuLxMdlzoP58Ia9C5M+78Vq0bEjuGJu3zxGev11Gt4E3V6bWfT7G BMjX90OZsTel/iQeXb0w7NgHfVQ2ZR3UmzkAwt8Eh/5+AtIsXRMuV9xgG17MKiN4
fhg66dbmjnqkhgSzpDzfYR7HHOQiDAGe5IH5FbvWehRzENoAODHHP1z3NdoGhsl9 JJUCjr8b/QIDAQABo1MwUTAdBgNVHQ4EFgQUl6+ASzYpbLLWGyKw/Qjj0JZK+24w
4DIjOTGYdhW0yUTSjGTWygo6OPU2L4M2k0gTA06FkvdLIS450GWRpgoVO/vfcPnO HwYDVR0jBBgwFoAUl6+ASzYpbLLWGyKw/Qjj0JZK+24wDwYDVR0TAQH/BAUwAwEB
h8KwZcWVwJVmG0Hv0fNhQk/tRuhYhCWGxc7gxkbLb7/xPpPKMD6EvgG0BSm27NxO /zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEI
H5l3KYwtbdj5nYHU73cLqC1D6ki6F8+h MA0GCWCGSAFlAwQCAQUAogMCASADggEBAHQQiwmyaT9Ga7Bug5ux6LquaJAQCHu7
THny14VdgcEdDp92+1+7CoRhDznpN6RKZPWyou6Jk0RL80ReKuijK054SjXiHsPx
UPSeHr5F7WCGTc6af8GXF7IJoEG+HjAXUz+lTQucK6d5TRk9mVknKnH4UiorRfZu
rdWmCVvb4CjKYCKVKfl/ZqAd8ZDE947AOosbCPcPivpWPIcuaAPeGiuJEcVkH537
onM3j3v1yMnaAbomUfWp5aEdv/7GCfYM0j4SpAOwoStfJBc1iPWymRCEhVSLNqKp
J3W63YTrBKyXzoXF2M/+Q2xdpwASAF+DwsFyEgcy7h/APKaEOkvmyMg=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -15,30 +15,31 @@
# #
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOQfQzDFDZxQVa MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI
y849UWoj92TYQVONCiULL3uYjn/BgJto6hn1GbtzrvmYB56ZG03OA1UTPubb3wQ2 hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA0b7eGccE
o0ao+TY2/ekEn9cJrJsGWnrUIt7Pt1DaHmNQUQnoUlyN4odL4zBevnq3YhJ18s0x nfOG0bd5zptCwGEhd+YmtvgaYKsmdxTK9y1vXq13ZsSyd4IpPZIVpam2BYgmxXix
i1UjGO9dBxjHlWZLiYipPGPmBUa1BWLQZd0fpK+l0pXR+MT0o6GOx0F8pDNl9xCW 4ikHbeKzydJG3bvr2J+f7Z9bgPZZNB9NJjX3j4IlEkGVI4/ZrROKrC004ItGcHqU
6weDylOHC+OkzAJmeTEJLREpr3Lj7n7syQLff7z6d5lTNLtvo10XGHpP+kxemR/x Jdlq48W3JFyKWswBrFYxaCR2HyTj6uI+dsxwuPWse/rqyu+1NKN7uxmh1RXAKa+k
3zV/NWjhkEyWjB5bT+Xww51XF5fsz/tdILVqeTmLcgRlyi0uYlgzASjusyllUt68 YVkKSmyta2UN+TJZ3bj0a0Sk9cyAqYVGI+kPKF+BYItgh6W+1cmYEscBjNkgcLbV
RDXXwSjxAgMBAAECggEAZ90ahaJMDH2ERsaeoo4e7uGjrKqo0jsrkEhm6tnHR7/l /henBMjX90OZsTel/iQeXb0w7NgHfVQ2ZR3UmzkAwt8Eh/5+AtIsXRMuV9xgG17M
gp1wWNaOaKDSG1aq7NqtAXL4Imroggv56TGrYWetf1+5OZTsCnkaz8Y8WBr/LIZZ KiN4JJUCjr8b/QIDAQABAoIBACj0qS/FYcxp6hB3UCycupsQHFXqNfMSXSw1H0yv
dp0a0dUdMhpXdTN/gh1zvCIbVcFTHoYYAjzxsGzcDHKIbeizzJIDeYVpoOlDQ9/9 XbaIQ6/sFV2W2PZnDyB7rwhrLCTGYjO7DpkHw/CcDNlC2x2e/T2OZc8jh92VvPNl
Bv6ft4mhaG5SHVnec9QdmbJnKDq5rI4aPXCCXOCzDjdTVfgntdH5TvoCH91ESSKw jU4BybZXBmAbOED6bNnT8AcQyLtz1qxN8zG0059oUwuhmk6CeW0qY3lfbUVFkc+i
kddciAbVsXoOWnBx3jKMj+hIA4F1p6nzZUbiVzmxhqfShQhDnCEvq8tF7KqRbUsS n+nYRUBuezEZ4QUg8oX98IFaCDikFByUvnpDoc2t43xX5A4f5mzC/Sa+MLUhgNBX
Gx8MVtwSkEGaiJCDVjwSRGkghXlguNwZcfnWMtGMYQKBgQDmFWAApeXv4xXF2a/1 9VL2D0iVD598tLiHb2TmGHtWzijF0JSqWlvVxRzfLi7w9Ke3pmAWTB2rKAaCa/Sx
HKumO5Z+w+XkKiM76YyTHTKO/KtDYRJiIlJMgx+hoRTBwlpYDrlbS9+Jnm7bZ9Ib 1G2HeeiobuLobgi9Gxcw5amd2bgdRBr6G4XtZfAY32DIuOECgYEA7aUKPByEx3ke
pxRyMAFRoV7eIhnoAn9KrxhS8xCYF2Km7U1lg/+m3pFKghjV4+K1GHbggmvoiIY1 5uCoafvzd73/FMlHGm5MC36U2UlGFDLxSQKq/iAzPyiFFhdMOsRXN4bARthgEmjC
2t250zkZSslwTxu/2+jRKYOptQKBgQDlfYrzvuGqClJ9QClxlOV2UrWiGxq6eTgL oZWFkR+UKKAx9p2clFtJpic5+xwds3bApE4fXmlG+vTjhAIPjPllSsPml7q44WSB
4V3l0HwPU9OW/hX0Hued8S70Dpb3o+AAyptbcAqFjSdyIPMbCfKLQQkKpfBUtOvb 2M4DnMWFmnEI5r2YvM5WScC2LYLDvpkCgYEA4fItV4kR3z5U7IQuyp/KfdD8csAI
Nm12z/VNKNZbu7kvaOJHunQNHzyMEHcjsB9daAVI0gJZKN+m6Qh4VF4jao7G9GNR nX5r1g98LxHo4AIRrwoeBxAB3XGIgz1ykIaw8dg6V0SG16sOkOQ24F9MV+cSe0EM
d7ge0KcXzQKBgQCqf8p9kHJ9OsVmsTMgK1fTvrJ+S8LvOn6TpjVCy08tAHYVXzjV tnWmy5SD2+nUJfMpfI/HDSI6H3Mx8E4Ae74ungsMFTmxuyu++/sNt/igIqfkQO2f
OePMyRpGluyfzNtQB9E5o1cKTzqNIjljvoN7PrGrgS6g45pZAIi9mlUnGvIAEsxL hV2VXu58mYUMWwUCgYB1oKZjQJ58ebhRAUx7QUmusG2tJT+7lnKvkdUthDZa0yhZ
MOy6vn9Tc/kswo2O6umUE4X8RwmZ7pmuDPtj+e+FG5N8w1Kn8VlsrhvgRQKBgAgz QifPJ7MWBQFzAM8rm3msM1fC+WD8W7xS7MazIZVdUoXIkxUo3dKjmnD5mV4eMZ6C
clG/koTnFYeQUWrTrVeLIR6H5W6gglY6WYaq6qQJlNgigFpW+GP2iH0EQHTdEFY2 9WRTf/qxRzvCYJ6/4cZAbp0Z50OR1QTsgnSJSb+qxV5pj9klQ2C0mt3RwxMOqQKB
51JfMKERKEW107o1ostDKbWNtIbyaDNPQJ4sVFHLkc15aea90shJa3hEk39V30wR gCQiZ+/04t/SByDgLt+G2Ipwjr8HSRlu624Lge/BLH4OtqdIte6pN7MjghKDFDxa
MS2/V+EAUEErasKmNT1Hlo2hczS86wewRY4kWrRJAoGAeYUG04cu55GwCgp50P3J 3hd/Xi0wr2P0Xlr7tG8DrqDsOn9tssvHWwp50PCtn5kGH19lWw8Vpzf6Y0UsJFWl
0NCNyiOkhnaj0wGPztMbDqNkaUAoaycoEsas5lhRAWT4YIVglz5pwR4uiI57w1cL 36y01ZTbajolz+BakSIX5/xC33Umy3k3szjAaTrgFU7FAoGBAMl9TGtpKAXuqvU7
Mvjk5yDiQs7h3bV/qtm95YPBBC+y3mmZYlEA1lH0qktRNBlMVtfYkPztBh50UBOH MIorzE1kd70bajWQiYrlxPu8kD6pi9POSZv4tEYF6sE8zK4OskvTn3F+mu/kmHN1
8qhIwqrpm3+JJ1p2p0XPl1c= qak4Sqgwek16n6AdvSoGlDG/eFVIioMaLXAGGXuWZ8J/doCTPH5JPRsEVdeWwI8E
v69m7uG79SuNZ1vHaycsQU7FEqe3
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -1 +0,0 @@
rsa2048.pkcs1.key.pem

View File

@ -0,0 +1,43 @@
#
# Copyright © 2020 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.
#
-----BEGIN RSA-PSS PRIVATE KEY-----
MIIEowIBAAKCAQEA0b7eGccEnfOG0bd5zptCwGEhd+YmtvgaYKsmdxTK9y1vXq13
ZsSyd4IpPZIVpam2BYgmxXix4ikHbeKzydJG3bvr2J+f7Z9bgPZZNB9NJjX3j4Il
EkGVI4/ZrROKrC004ItGcHqUJdlq48W3JFyKWswBrFYxaCR2HyTj6uI+dsxwuPWs
e/rqyu+1NKN7uxmh1RXAKa+kYVkKSmyta2UN+TJZ3bj0a0Sk9cyAqYVGI+kPKF+B
YItgh6W+1cmYEscBjNkgcLbV/henBMjX90OZsTel/iQeXb0w7NgHfVQ2ZR3UmzkA
wt8Eh/5+AtIsXRMuV9xgG17MKiN4JJUCjr8b/QIDAQABAoIBACj0qS/FYcxp6hB3
UCycupsQHFXqNfMSXSw1H0yvXbaIQ6/sFV2W2PZnDyB7rwhrLCTGYjO7DpkHw/Cc
DNlC2x2e/T2OZc8jh92VvPNljU4BybZXBmAbOED6bNnT8AcQyLtz1qxN8zG0059o
Uwuhmk6CeW0qY3lfbUVFkc+in+nYRUBuezEZ4QUg8oX98IFaCDikFByUvnpDoc2t
43xX5A4f5mzC/Sa+MLUhgNBX9VL2D0iVD598tLiHb2TmGHtWzijF0JSqWlvVxRzf
Li7w9Ke3pmAWTB2rKAaCa/Sx1G2HeeiobuLobgi9Gxcw5amd2bgdRBr6G4XtZfAY
32DIuOECgYEA7aUKPByEx3ke5uCoafvzd73/FMlHGm5MC36U2UlGFDLxSQKq/iAz
PyiFFhdMOsRXN4bARthgEmjCoZWFkR+UKKAx9p2clFtJpic5+xwds3bApE4fXmlG
+vTjhAIPjPllSsPml7q44WSB2M4DnMWFmnEI5r2YvM5WScC2LYLDvpkCgYEA4fIt
V4kR3z5U7IQuyp/KfdD8csAInX5r1g98LxHo4AIRrwoeBxAB3XGIgz1ykIaw8dg6
V0SG16sOkOQ24F9MV+cSe0EMtnWmy5SD2+nUJfMpfI/HDSI6H3Mx8E4Ae74ungsM
FTmxuyu++/sNt/igIqfkQO2fhV2VXu58mYUMWwUCgYB1oKZjQJ58ebhRAUx7QUmu
sG2tJT+7lnKvkdUthDZa0yhZQifPJ7MWBQFzAM8rm3msM1fC+WD8W7xS7MazIZVd
UoXIkxUo3dKjmnD5mV4eMZ6C9WRTf/qxRzvCYJ6/4cZAbp0Z50OR1QTsgnSJSb+q
xV5pj9klQ2C0mt3RwxMOqQKBgCQiZ+/04t/SByDgLt+G2Ipwjr8HSRlu624Lge/B
LH4OtqdIte6pN7MjghKDFDxa3hd/Xi0wr2P0Xlr7tG8DrqDsOn9tssvHWwp50PCt
n5kGH19lWw8Vpzf6Y0UsJFWl36y01ZTbajolz+BakSIX5/xC33Umy3k3szjAaTrg
FU7FAoGBAMl9TGtpKAXuqvU7MIorzE1kd70bajWQiYrlxPu8kD6pi9POSZv4tEYF
6sE8zK4OskvTn3F+mu/kmHN1qak4Sqgwek16n6AdvSoGlDG/eFVIioMaLXAGGXuW
Z8J/doCTPH5JPRsEVdeWwI8Ev69m7uG79SuNZ1vHaycsQU7FEqe3
-----END RSA-PSS PRIVATE KEY-----

View File

@ -0,0 +1,26 @@
#
# Copyright © 2020 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.
#
-----BEGIN PUBLIC KEY-----
MIIBVjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcN
AQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEPADCCAQoCggEBANG+3hnHBJ3zhtG3
ec6bQsBhIXfmJrb4GmCrJncUyvctb16td2bEsneCKT2SFaWptgWIJsV4seIpB23i
s8nSRt2769ifn+2fW4D2WTQfTSY194+CJRJBlSOP2a0TiqwtNOCLRnB6lCXZauPF
tyRcilrMAaxWMWgkdh8k4+riPnbMcLj1rHv66srvtTSje7sZodUVwCmvpGFZCkps
rWtlDfkyWd249GtEpPXMgKmFRiPpDyhfgWCLYIelvtXJmBLHAYzZIHC21f4XpwTI
1/dDmbE3pf4kHl29MOzYB31UNmUd1Js5AMLfBIf+fgLSLF0TLlfcYBtezCojeCSV
Ao6/G/0CAwEAAQ==
-----END PUBLIC KEY-----

View File

@ -15,27 +15,33 @@
# #
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIERDCCAqwCCQDqxucO41yAmTANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV MIIFRTCCA3mgAwIBAgIUcJUoNEM4A7jE+zrgPRQ3A3PdYdkwQQYJKoZIhvcNAQEK
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY MDSgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw AKIDAgEwMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD
MzIzMDQ1OFoYDzMwMjAwMjExMjMwNDU4WjBjMQswCQYDVQQGEwJVUzETMBEGA1UE VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP BgNVBAsMBGpqd3QwIBcNMjMwODE0MTk0NzEzWhgPMzAyMzA4MjIxOTQ3MTNaMGMx
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIIBojANBgkqhkiG9w0BAQEF CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g
AAOCAY8AMIIBigKCAYEA0DmQS4Xtgu5xtnQdxkuzB1j+W4OvNEOVOsg3Zcn9W1d5 RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq
NowtngUh9K9vwBpl59M2j5PHj9dseEIuRqr++ZnZhBMlh/lLiIQ0oQ3cEa7wrwJO d3QwggHWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG
i9ycZZbeNDHVNzAdQZEQR1DIMUhSTEMR96pVD4a9DzBKLQJaKxZRUOrMhf3QhAZ5 9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMAOCAY8AMIIBigKCAYEArTxQUZqiSCFF
9m0d/Kqu1Dm6YeWMLQQEewAaSQ9g22gty1EcLAvp/vnhR/DJSYIHCayd5mZwvk9q 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
bXVUUwwl8tRC
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -15,42 +15,43 @@
# #
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
5I2wbxUhb3FO7gj9pxJbwX4= SgCk6OgpjKqABr1NWFFgowjD7UomgT3pVm/KSJD2ebl2mONtr60I02wlskvtwe8L
lVynub2OR3qrrfMw0voYdeaYeLELzQ==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -1 +0,0 @@
rsa3072.pkcs1.key.pem

View File

@ -0,0 +1,55 @@
#
# Copyright © 2020 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.
#
-----BEGIN RSA-PSS PRIVATE KEY-----
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
-----END RSA-PSS PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
#
# Copyright © 2020 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.
#
-----BEGIN PUBLIC KEY-----
MIIB1jBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUAoRwwGgYJKoZIhvcN
AQEIMA0GCWCGSAFlAwQCAgUAogMCATADggGPADCCAYoCggGBAK08UFGaokghRc3G
5lYLgqgwmRrNvBbdP5ZsB2uWFiGtni9XlO2M3Sal20f+sJ+ZxqfhmFHVJ6hTso4X
3hrSAma5jQeEETSmY8aO4OmRJ/kxJ9bxy/MXmK5Z4zDz7eqK3cEK2Ac40epK33v9
eZdrJyScz6MdWoyTXyRHtNF60Hi59Z8EqwjFtkVi33l0yYZQs2FiilE1jSzNS4BY
ks0ZvU/7V4afY3UdNZdsLrasywCn4tX98EtWCoDK+00GZIN2H4oHPz6M/7ago5M6
YjVcMGghqnumVzeTpbmbuHQmswFfgpRqFsUKgcxdIoWVLH5CbXozFlTc3suyZpCE
3hiFquZNIoK16qd3zQ71LVpne9MLsFsh+yEYhHrWBd93k43VxbkDb20zPRquPvDl
x2nuSEvbe/bQCgMT4bPxFQ+yj4BrJSzCwhJuYM42e4JWtDsUxbaKtFc2s+wuCQvg
iD80jp+IZV4v0ibYS/GA2DjYJNTskBZ3hSxf50NRJLek6LUmFQIDAQAB
-----END PUBLIC KEY-----

View File

@ -15,33 +15,38 @@
# #
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFRDCCAywCCQC4g2isVGolKjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV MIIGRTCCA/mgAwIBAgIUII4HfeMyQ0DFwJgHBeB4DTuDCBUwQQYJKoZIhvcNAQEK
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY MDSgDzANBglghkgBZQMEAgMFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgMF
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw AKIDAgFAMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD
MzIzMDY1MloYDzMwMjAwMjExMjMwNjUyWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP BgNVBAsMBGpqd3QwIBcNMjMwODE0MTk0NzI0WhgPMzAyMzA4MjIxOTQ3MjRaMGMx
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIICIjANBgkqhkiG9w0BAQEF CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g
AAOCAg8AMIICCgKCAgEAw7jTXeRwCRrDdYnrIwcLvSNfhpmJZ0ap1jzKVgUyCOYL RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq
TaB9+naJRjHqx7B5wgx/ArRF2nluQ5tawZPMFw2I/iqXYrQEPTbq1XVugYC/C491 d3QwggJWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIDBQChHDAaBgkqhkiG
bcXOTKx+DgEvnhNysm/KmzFsEcw78prB5sIAZSR+S5zZPuny4zww2UzE9TZ433RB 9w0BAQgwDQYJYIZIAWUDBAIDBQCiAwIBQAOCAg8AMIICCgKCAgEAvDIWx+4AyoNT
kyA+wVkd64bgXdkMrVc+gsRsOtvwPFbQ89zg8d/pNV0mDtjDsfiYw0pSAah11fJG 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
33eif1l5Nk0= xnNcGpAEuk9ldyrj0pRYJcxu6ChtHmIYtLDB/OJ+UD3OJBUBG09dagHFFNrhNBWV
aRiBqvnSQ1lRdYssBbzbV995dApQ08t44rl0zUGwLdjcpwI2GtM89PKc5FaoqKHE
QrdlJyUCQUzUq1nq/O0KdGLG2tXlN/lV9G+0ayPNGfJYkn8kJCqZ1LXOYYz2ahyW
zG5fANL7asksaHBQRAlrNxqTdL31Xfy/a6iYV0ePv6xA3ZTGq8jxTpizK+sajFSh
7pV4WYYlS2DqgIxaNmAJIN463mcq6q+1U5VrldCSuMAURlF8e24CMXvRsd2Yc88l
E3vd9XobDXMOJLBBPLceTt8pvv/6GyF6bQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -15,54 +15,55 @@
# #
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
lczRTJNTE/KNUE0pkC5P4c/e0A1OFu0= gyQGFVddNtQu0ulqEd1c7joGeZRUtzTV1tfmOpNcOFQlNkWE5N5AN69AlKClbdxd
fSc/Kr3l8kzu0dexW4usUiKuLju4X471jKg=
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -1 +0,0 @@
rsa4096.pkcs1.key.pem

View File

@ -0,0 +1,67 @@
#
# Copyright © 2020 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.
#
-----BEGIN RSA-PSS PRIVATE KEY-----
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
-----END RSA-PSS PRIVATE KEY-----

View File

@ -0,0 +1,31 @@
#
# Copyright © 2020 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.
#
-----BEGIN PUBLIC KEY-----
MIICVjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAwUAoRwwGgYJKoZIhvcN
AQEIMA0GCWCGSAFlAwQCAwUAogMCAUADggIPADCCAgoCggIBALwyFsfuAMqDU0sX
qO97Z+b4BXLFBOJ4NOAhhAMoptoe6jiN7iLhLPeD+GBZM+3nrMLVa/1mDdpVJz6C
QH6tRBgOxc+DSLNdGIEbHtyOcvwCrxipqpdLSHkJGq/6+kTNBIyv/Uv7Aou+/fHz
7H++ktmeHRtMqSB9VeCwMGkrSOvsE+vfvWS8TLScQBdNrEBmmQoRhPpRa6MnOF83
Hmc6XvlQlw4Ld4YBFwBio/Iq+nwHWOI4f2zM9vv5o4FHgoaHBWRvvZVqy0uYGPp6
SBmhI9gzP0DqBE5howInmJY6a+HggliAJugupcC7EXY9IZje+6byuJc6F5JZiNi3
kAyVR8eoq/+1Fr7KVaZN6DhLYT5KSN298pvxAAoPzB+IsF8nohsO9oYRWJ0xg0Yp
6rv6144CHsVDC7aTE2mrnCYEy6Dj3aBVtJruB7inNYTbILzFmmh9/5dPaCg66rOE
At0kFUOIL5mb1rlSOVv79V9oZk2sDRNd+nPhTtxE+G+0U06FHKvSGWVe+rrJe19Z
dpIqYqH0/UFewA8P4apjqYi2wLdqt8EYx10G9IuiJZLl6TiOrSHIu08NyHra9k55
01SQBnsx+9HFXgHRaAhOgV++3LJxp63bZn0TDWhnrY/MFvcjceb67EIZUc+AFcfJ
7MeEDt9mGPbMDBsDKqZD4qnhgqfbAgMBAAE=
-----END PUBLIC KEY-----

View File

@ -1,18 +1,28 @@
The RSA `*.key.pem`, `*.crt.pem`, `*.pub.pem`, and `*.pkcs1.key.pem` files in this directory were created for testing as follows: The RSA `*.key.pem`, `*.crt.pem`, `*.pub.pem`, and `*.pkcs1.key.pem` files in this directory were created for testing
using the `openssl` version `3.1.2` as follows:
openssl req -x509 -newkey rsa:2048 -keyout rsa2048.key.pem -out rsa2048.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' openssl req -new -x509 -newkey rsa:2048 -keyout RS256.key.pem -out RS256.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt'
openssl req -x509 -newkey rsa:3072 -keyout rsa3072.key.pem -out rsa3072.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' openssl req -new -x509 -newkey rsa:3072 -keyout RS384.key.pem -out RS384.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt'
openssl req -x509 -newkey rsa:4096 -keyout rsa4096.key.pem -out rsa4096.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' openssl req -new -x509 -newkey rsa:4096 -keyout RS512.key.pem -out RS512.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt'
openssl req -new -x509 -newkey rsa-pss -keyout PS256.key.pem -out PS256.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -sigopt rsa_mgf1_md:sha256 -sha256
openssl req -new -x509 -newkey rsa-pss -keyout PS384.key.pem -out PS384.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' -pkeyopt rsa_keygen_bits:3072 -pkeyopt rsa_pss_keygen_md:sha384 -pkeyopt rsa_pss_keygen_mgf1_md:sha384 -pkeyopt rsa_pss_keygen_saltlen:48 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:48 -sigopt rsa_mgf1_md:sha384 -sha384
openssl req -new -x509 -newkey rsa-pss -keyout PS512.key.pem -out PS512.crt.pem -days 365250 -nodes -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_pss_keygen_md:sha512 -pkeyopt rsa_pss_keygen_mgf1_md:sha512 -pkeyopt rsa_pss_keygen_saltlen:64 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:64 -sigopt rsa_mgf1_md:sha512 -sha512
# extract the public key from the X.509 certificates to their own files: # extract the public key from the X.509 certificates to their own files:
openssl x509 -pubkey -noout -in rsa2048.crt.pem > rsa2048.pub.pem openssl x509 -pubkey -noout -in RS256.crt.pem > RS256.pub.pem
openssl x509 -pubkey -noout -in rsa3072.crt.pem > rsa3072.pub.pem openssl x509 -pubkey -noout -in RS384.crt.pem > RS384.pub.pem
openssl x509 -pubkey -noout -in rsa4096.crt.pem > rsa4096.pub.pem openssl x509 -pubkey -noout -in RS512.crt.pem > RS512.pub.pem
openssl x509 -pubkey -noout -in PS256.crt.pem > PS256.pub.pem
openssl x509 -pubkey -noout -in PS384.crt.pem > PS384.pub.pem
openssl x509 -pubkey -noout -in PS512.crt.pem > PS512.pub.pem
# convert the PKCS8 private key format to PKCS1 format for additional testing: # convert the PKCS8 private key format to PKCS1 format for additional testing:
openssl rsa -in rsa2048.key.pem -out rsa2048.pkcs1.key.pem openssl rsa -in RS256.key.pem -traditional -out RS256.pkcs1.key.pem
openssl rsa -in rsa3072.key.pem -out rsa3072.pkcs1.key.pem openssl rsa -in RS384.key.pem -traditional -out RS384.pkcs1.key.pem
openssl rsa -in rsa4096.key.pem -out rsa4096.pkcs1.key.pem openssl rsa -in RS512.key.pem -traditional -out RS512.pkcs1.key.pem
openssl rsa -in PS256.key.pem -traditional -out PS256.pkcs1.key.pem
openssl rsa -in PS384.key.pem -traditional -out PS384.pkcs1.key.pem
openssl rsa -in PS512.key.pem -traditional -out PS512.pkcs1.key.pem
The only difference is the key size and file names using sizes of `2048`, `3072`, and `4096`. The only difference is the key size and file names using sizes of `2048`, `3072`, and `4096`.
@ -63,6 +73,5 @@ All `ES*`, `RS*`, `PS*`, `X*` and `Ed*` file prefixes are equal to JWA standard
Curve IDs. This allows easy file lookup based on the `SignatureAlgorithm` `getId()` or `EdwardsCurve#getId()` value Curve IDs. This allows easy file lookup based on the `SignatureAlgorithm` `getId()` or `EdwardsCurve#getId()` value
when authoring tests. when authoring tests.
Finally, the `RS*`, `PS*`, and `EdDSA*` files in this directory are just are symlinks back to source files based on Finally, the `EdDSA*` files in this directory are just are symlinks back to source files based on
the JWT alg names and their respective key sizes. This is so the `RS*` and `PS*` algorithms can use the same files the `EdSignatureAlgorithm`'s preferred `Ed448` key sizes.
since there is no difference in keys between the two sets of algorithms.

View File

@ -1 +0,0 @@
rsa2048.pkcs1.key.pem

View File

@ -0,0 +1,43 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzkH0MwxQ2cUFWsvOPVFqI/dk2EFTjQolCy97mI5/wYCbaOoZ
9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2Nv3pBJ/XCaybBlp61CLez7dQ2h5j
UFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjvXQcYx5VmS4mIqTxj5gVGtQVi0GXd
H6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pThwvjpMwCZnkxCS0RKa9y4+5+7MkC
33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo4ZBMloweW0/l8MOdVxeX7M/7XSC1
ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo8QIDAQABAoIBAGfdGoWiTAx9hEbG
nqKOHu7ho6yqqNI7K5BIZurZx0e/5YKdcFjWjmig0htWquzarQFy+CJq6IIL+ekx
q2FnrX9fuTmU7Ap5Gs/GPFga/yyGWXadGtHVHTIaV3Uzf4Idc7wiG1XBUx6GGAI8
8bBs3AxyiG3os8ySA3mFaaDpQ0Pf/Qb+n7eJoWhuUh1Z3nPUHZmyZyg6uayOGj1w
glzgsw43U1X4J7XR+U76Ah/dREkisJHXXIgG1bF6Dlpwcd4yjI/oSAOBdaep82VG
4lc5sYan0oUIQ5whL6vLReyqkW1LEhsfDFbcEpBBmoiQg1Y8EkRpIIV5YLjcGXH5
1jLRjGECgYEA5hVgAKXl7+MVxdmv9RyrpjuWfsPl5CojO+mMkx0yjvyrQ2ESYiJS
TIMfoaEUwcJaWA65W0vfiZ5u22fSG6cUcjABUaFe3iIZ6AJ/Sq8YUvMQmBdipu1N
ZYP/pt6RSoIY1ePitRh24IJr6IiGNdrdudM5GUrJcE8bv9vo0SmDqbUCgYEA5X2K
877hqgpSfUApcZTldlK1ohsaunk4C+Fd5dB8D1PTlv4V9B7nnfEu9A6W96PgAMqb
W3AKhY0nciDzGwnyi0EJCqXwVLTr2zZtds/1TSjWW7u5L2jiR7p0DR88jBB3I7Af
XWgFSNICWSjfpukIeFReI2qOxvRjUXe4HtCnF80CgYEAqn/KfZByfTrFZrEzICtX
076yfkvC7zp+k6Y1QstPLQB2FV841TnjzMkaRpbsn8zbUAfROaNXCk86jSI5Y76D
ez6xq4EuoOOaWQCIvZpVJxryABLMSzDsur5/U3P5LMKNjurplBOF/EcJme6Zrgz7
Y/nvhRuTfMNSp/FZbK4b4EUCgYAIM3JRv5KE5xWHkFFq061XiyEeh+VuoIJWOlmG
quqkCZTYIoBaVvhj9oh9BEB03RBWNudSXzChEShFtdO6NaLLQym1jbSG8mgzT0Ce
LFRRy5HNeWnmvdLISWt4RJN/Vd9METEtv1fhAFBBK2rCpjU9R5aNoXM0vOsHsEWO
JFq0SQKBgHmFBtOHLueRsAoKedD9ydDQjcojpIZ2o9MBj87TGw6jZGlAKGsnKBLG
rOZYUQFk+GCFYJc+acEeLoiOe8NXCzL45Ocg4kLO4d21f6rZveWDwQQvst5pmWJR
ANZR9KpLUTQZTFbX2JD87QYedFATh/KoSMKq6Zt/iSdadqdFz5dX
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,25 @@
#
# 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.
#
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzkH0MwxQ2cUFWsvOPVFq
I/dk2EFTjQolCy97mI5/wYCbaOoZ9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2
Nv3pBJ/XCaybBlp61CLez7dQ2h5jUFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjv
XQcYx5VmS4mIqTxj5gVGtQVi0GXdH6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pT
hwvjpMwCZnkxCS0RKa9y4+5+7MkC33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo
4ZBMloweW0/l8MOdVxeX7M/7XSC1ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo
8QIDAQAB
-----END PUBLIC KEY-----

View File

@ -1 +0,0 @@
rsa3072.pkcs1.key.pem

View File

@ -0,0 +1,55 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
#
# 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.
#
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0DmQS4Xtgu5xtnQdxkuz
B1j+W4OvNEOVOsg3Zcn9W1d5NowtngUh9K9vwBpl59M2j5PHj9dseEIuRqr++ZnZ
hBMlh/lLiIQ0oQ3cEa7wrwJOi9ycZZbeNDHVNzAdQZEQR1DIMUhSTEMR96pVD4a9
DzBKLQJaKxZRUOrMhf3QhAZ59m0d/Kqu1Dm6YeWMLQQEewAaSQ9g22gty1EcLAvp
/vnhR/DJSYIHCayd5mZwvk9qWkXySfUmJGP70GFGG4GOnVLLkmCQgfT+OrzTtiIz
NT26mtAsoUMnoD42TTBkR0aLhULcj1MYUcB9uVCmJTvYuiCTODoNlL8T40r9L99H
oHlTWVi9vf6I1vyNdHp3dXKBMVL13+i5BeNIjADr0KKs0jtEEicWyuVhJz3rPLzB
bPhz/DGQ/hTj/DRSdo9L9YA4WkqgD8uFUvIEKAJ/hXYx3QPEiIMU7hT4jk2Z2SIB
iKKiNW4E/20EZOFmaeNWQaqqmwX0cHtMygJtTYnEJ1IDAgMBAAE=
-----END PUBLIC KEY-----

View File

@ -1 +0,0 @@
rsa4096.pkcs1.key.pem

View File

@ -0,0 +1,67 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,30 @@
#
# 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.
#
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw7jTXeRwCRrDdYnrIwcL
vSNfhpmJZ0ap1jzKVgUyCOYLTaB9+naJRjHqx7B5wgx/ArRF2nluQ5tawZPMFw2I
/iqXYrQEPTbq1XVugYC/C491bcXOTKx+DgEvnhNysm/KmzFsEcw78prB5sIAZSR+
S5zZPuny4zww2UzE9TZ433RBkyA+wVkd64bgXdkMrVc+gsRsOtvwPFbQ89zg8d/p
NV0mDtjDsfiYw0pSAah11fJGa+aRc46CqFu/6rHuN4uq6542LdtshPbHz29VKHxk
8agtcx06+8F05Bg4LFm1rRhY0g3KsT7s8XHMVdo9h2bIQuWOaFg3mehpH6ZYBV4E
No98V/jDaUPpBHsaUXw4fG/wrnI+YwRjGlmp2QEr5VRfh0x8Addf6N64lmbQUpCP
hJweJd54D3JvIpJr8HiG3GYWeFsrmDzmhrozZHxE4P7UesW6lWwQzGfwYGXs7j6T
Ea2hZ8EB/t1jsYNjZ5UYY/JbKgFGSkMGje4Bi5Bv6kh4+pp3DT5QsG/AfLVlr5in
eLDWkJ15uZjOxl12EOPXOCWViVqS6rayJfb95YJQ52rT4H83BsApHbzFj7q/CIae
JkUdv9GJ2SOADcXdgG7Xk7tHqb1VIH4zRBo5mc0qN1cAwjopxHv2h3tGaTHKbptv
fiLulH+AvvuWmLMEQo6ZDlsCAwEAAQ==
-----END PUBLIC KEY-----

View File

@ -1,36 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN CERTIFICATE-----
MIIDRDCCAiwCCQCgd9OzR40NCDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw
MzIzMDQzM1oYDzMwMjAwMjExMjMwNDMzWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAzkH0MwxQ2cUFWsvOPVFqI/dk2EFTjQolCy97mI5/wYCb
aOoZ9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2Nv3pBJ/XCaybBlp61CLez7dQ
2h5jUFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjvXQcYx5VmS4mIqTxj5gVGtQVi
0GXdH6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pThwvjpMwCZnkxCS0RKa9y4+5+
7MkC33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo4ZBMloweW0/l8MOdVxeX7M/7
XSC1ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo8QIDAQABMA0GCSqGSIb3DQEB
CwUAA4IBAQBGbfmJumXEHMLko1ioY/eY5EYgrBRJAuuAMGqBZmK+1Iy2CqB90aEh
ve+jXjIBsrvXRuLxMdlzoP58Ia9C5M+78Vq0bEjuGJu3zxGev11Gt4E3V6bWfT7G
fhg66dbmjnqkhgSzpDzfYR7HHOQiDAGe5IH5FbvWehRzENoAODHHP1z3NdoGhsl9
4DIjOTGYdhW0yUTSjGTWygo6OPU2L4M2k0gTA06FkvdLIS450GWRpgoVO/vfcPnO
h8KwZcWVwJVmG0Hv0fNhQk/tRuhYhCWGxc7gxkbLb7/xPpPKMD6EvgG0BSm27NxO
H5l3KYwtbdj5nYHU73cLqC1D6ki6F8+h
-----END CERTIFICATE-----

View File

@ -1,44 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOQfQzDFDZxQVa
y849UWoj92TYQVONCiULL3uYjn/BgJto6hn1GbtzrvmYB56ZG03OA1UTPubb3wQ2
o0ao+TY2/ekEn9cJrJsGWnrUIt7Pt1DaHmNQUQnoUlyN4odL4zBevnq3YhJ18s0x
i1UjGO9dBxjHlWZLiYipPGPmBUa1BWLQZd0fpK+l0pXR+MT0o6GOx0F8pDNl9xCW
6weDylOHC+OkzAJmeTEJLREpr3Lj7n7syQLff7z6d5lTNLtvo10XGHpP+kxemR/x
3zV/NWjhkEyWjB5bT+Xww51XF5fsz/tdILVqeTmLcgRlyi0uYlgzASjusyllUt68
RDXXwSjxAgMBAAECggEAZ90ahaJMDH2ERsaeoo4e7uGjrKqo0jsrkEhm6tnHR7/l
gp1wWNaOaKDSG1aq7NqtAXL4Imroggv56TGrYWetf1+5OZTsCnkaz8Y8WBr/LIZZ
dp0a0dUdMhpXdTN/gh1zvCIbVcFTHoYYAjzxsGzcDHKIbeizzJIDeYVpoOlDQ9/9
Bv6ft4mhaG5SHVnec9QdmbJnKDq5rI4aPXCCXOCzDjdTVfgntdH5TvoCH91ESSKw
kddciAbVsXoOWnBx3jKMj+hIA4F1p6nzZUbiVzmxhqfShQhDnCEvq8tF7KqRbUsS
Gx8MVtwSkEGaiJCDVjwSRGkghXlguNwZcfnWMtGMYQKBgQDmFWAApeXv4xXF2a/1
HKumO5Z+w+XkKiM76YyTHTKO/KtDYRJiIlJMgx+hoRTBwlpYDrlbS9+Jnm7bZ9Ib
pxRyMAFRoV7eIhnoAn9KrxhS8xCYF2Km7U1lg/+m3pFKghjV4+K1GHbggmvoiIY1
2t250zkZSslwTxu/2+jRKYOptQKBgQDlfYrzvuGqClJ9QClxlOV2UrWiGxq6eTgL
4V3l0HwPU9OW/hX0Hued8S70Dpb3o+AAyptbcAqFjSdyIPMbCfKLQQkKpfBUtOvb
Nm12z/VNKNZbu7kvaOJHunQNHzyMEHcjsB9daAVI0gJZKN+m6Qh4VF4jao7G9GNR
d7ge0KcXzQKBgQCqf8p9kHJ9OsVmsTMgK1fTvrJ+S8LvOn6TpjVCy08tAHYVXzjV
OePMyRpGluyfzNtQB9E5o1cKTzqNIjljvoN7PrGrgS6g45pZAIi9mlUnGvIAEsxL
MOy6vn9Tc/kswo2O6umUE4X8RwmZ7pmuDPtj+e+FG5N8w1Kn8VlsrhvgRQKBgAgz
clG/koTnFYeQUWrTrVeLIR6H5W6gglY6WYaq6qQJlNgigFpW+GP2iH0EQHTdEFY2
51JfMKERKEW107o1ostDKbWNtIbyaDNPQJ4sVFHLkc15aea90shJa3hEk39V30wR
MS2/V+EAUEErasKmNT1Hlo2hczS86wewRY4kWrRJAoGAeYUG04cu55GwCgp50P3J
0NCNyiOkhnaj0wGPztMbDqNkaUAoaycoEsas5lhRAWT4YIVglz5pwR4uiI57w1cL
Mvjk5yDiQs7h3bV/qtm95YPBBC+y3mmZYlEA1lH0qktRNBlMVtfYkPztBh50UBOH
8qhIwqrpm3+JJ1p2p0XPl1c=
-----END PRIVATE KEY-----

View File

@ -1,43 +0,0 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzkH0MwxQ2cUFWsvOPVFqI/dk2EFTjQolCy97mI5/wYCbaOoZ
9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2Nv3pBJ/XCaybBlp61CLez7dQ2h5j
UFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjvXQcYx5VmS4mIqTxj5gVGtQVi0GXd
H6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pThwvjpMwCZnkxCS0RKa9y4+5+7MkC
33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo4ZBMloweW0/l8MOdVxeX7M/7XSC1
ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo8QIDAQABAoIBAGfdGoWiTAx9hEbG
nqKOHu7ho6yqqNI7K5BIZurZx0e/5YKdcFjWjmig0htWquzarQFy+CJq6IIL+ekx
q2FnrX9fuTmU7Ap5Gs/GPFga/yyGWXadGtHVHTIaV3Uzf4Idc7wiG1XBUx6GGAI8
8bBs3AxyiG3os8ySA3mFaaDpQ0Pf/Qb+n7eJoWhuUh1Z3nPUHZmyZyg6uayOGj1w
glzgsw43U1X4J7XR+U76Ah/dREkisJHXXIgG1bF6Dlpwcd4yjI/oSAOBdaep82VG
4lc5sYan0oUIQ5whL6vLReyqkW1LEhsfDFbcEpBBmoiQg1Y8EkRpIIV5YLjcGXH5
1jLRjGECgYEA5hVgAKXl7+MVxdmv9RyrpjuWfsPl5CojO+mMkx0yjvyrQ2ESYiJS
TIMfoaEUwcJaWA65W0vfiZ5u22fSG6cUcjABUaFe3iIZ6AJ/Sq8YUvMQmBdipu1N
ZYP/pt6RSoIY1ePitRh24IJr6IiGNdrdudM5GUrJcE8bv9vo0SmDqbUCgYEA5X2K
877hqgpSfUApcZTldlK1ohsaunk4C+Fd5dB8D1PTlv4V9B7nnfEu9A6W96PgAMqb
W3AKhY0nciDzGwnyi0EJCqXwVLTr2zZtds/1TSjWW7u5L2jiR7p0DR88jBB3I7Af
XWgFSNICWSjfpukIeFReI2qOxvRjUXe4HtCnF80CgYEAqn/KfZByfTrFZrEzICtX
076yfkvC7zp+k6Y1QstPLQB2FV841TnjzMkaRpbsn8zbUAfROaNXCk86jSI5Y76D
ez6xq4EuoOOaWQCIvZpVJxryABLMSzDsur5/U3P5LMKNjurplBOF/EcJme6Zrgz7
Y/nvhRuTfMNSp/FZbK4b4EUCgYAIM3JRv5KE5xWHkFFq061XiyEeh+VuoIJWOlmG
quqkCZTYIoBaVvhj9oh9BEB03RBWNudSXzChEShFtdO6NaLLQym1jbSG8mgzT0Ce
LFRRy5HNeWnmvdLISWt4RJN/Vd9METEtv1fhAFBBK2rCpjU9R5aNoXM0vOsHsEWO
JFq0SQKBgHmFBtOHLueRsAoKedD9ydDQjcojpIZ2o9MBj87TGw6jZGlAKGsnKBLG
rOZYUQFk+GCFYJc+acEeLoiOe8NXCzL45Ocg4kLO4d21f6rZveWDwQQvst5pmWJR
ANZR9KpLUTQZTFbX2JD87QYedFATh/KoSMKq6Zt/iSdadqdFz5dX
-----END RSA PRIVATE KEY-----

View File

@ -1,25 +0,0 @@
#
# 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.
#
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzkH0MwxQ2cUFWsvOPVFq
I/dk2EFTjQolCy97mI5/wYCbaOoZ9Rm7c675mAeemRtNzgNVEz7m298ENqNGqPk2
Nv3pBJ/XCaybBlp61CLez7dQ2h5jUFEJ6FJcjeKHS+MwXr56t2ISdfLNMYtVIxjv
XQcYx5VmS4mIqTxj5gVGtQVi0GXdH6SvpdKV0fjE9KOhjsdBfKQzZfcQlusHg8pT
hwvjpMwCZnkxCS0RKa9y4+5+7MkC33+8+neZUzS7b6NdFxh6T/pMXpkf8d81fzVo
4ZBMloweW0/l8MOdVxeX7M/7XSC1ank5i3IEZcotLmJYMwEo7rMpZVLevEQ118Eo
8QIDAQAB
-----END PUBLIC KEY-----

View File

@ -1,41 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN CERTIFICATE-----
MIIERDCCAqwCCQDqxucO41yAmTANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw
MzIzMDQ1OFoYDzMwMjAwMjExMjMwNDU4WjBjMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIIBojANBgkqhkiG9w0BAQEF
AAOCAY8AMIIBigKCAYEA0DmQS4Xtgu5xtnQdxkuzB1j+W4OvNEOVOsg3Zcn9W1d5
NowtngUh9K9vwBpl59M2j5PHj9dseEIuRqr++ZnZhBMlh/lLiIQ0oQ3cEa7wrwJO
i9ycZZbeNDHVNzAdQZEQR1DIMUhSTEMR96pVD4a9DzBKLQJaKxZRUOrMhf3QhAZ5
9m0d/Kqu1Dm6YeWMLQQEewAaSQ9g22gty1EcLAvp/vnhR/DJSYIHCayd5mZwvk9q
WkXySfUmJGP70GFGG4GOnVLLkmCQgfT+OrzTtiIzNT26mtAsoUMnoD42TTBkR0aL
hULcj1MYUcB9uVCmJTvYuiCTODoNlL8T40r9L99HoHlTWVi9vf6I1vyNdHp3dXKB
MVL13+i5BeNIjADr0KKs0jtEEicWyuVhJz3rPLzBbPhz/DGQ/hTj/DRSdo9L9YA4
WkqgD8uFUvIEKAJ/hXYx3QPEiIMU7hT4jk2Z2SIBiKKiNW4E/20EZOFmaeNWQaqq
mwX0cHtMygJtTYnEJ1IDAgMBAAEwDQYJKoZIhvcNAQELBQADggGBAGKkmv6d372z
Ujt1qjsjH7LHfIsPXdvnp7OhvujDEtY7dzwDCtR40zgB2qp+iXUO61FXErx8yDp9
l7sDzk0AjY7RupANuo/3FyDuo0WoTUV3CJNnXf3Mrvu/DMjbaS6D4Jryz/HLE+2r
GYtdm165FZK/hQXuFfurkc4yqjrX90Wr+YHeen2y5Wk3jeUknmdp97F6+zkq6N5D
dKjy/ZOvy+1huNd5bzvJoiZLKqdSh/RQUoU6AP1p+83lo+7cPvS/zm/HvwxwMamA
1Cip1FypNxUxt5HR4bC5LwEvMTZ/+UTEelbyfjMdYU97aa58nPoMxf7DRBbr0tfj
GItI+mMoAw60eIaDbTncXvO1LVrFF5BfzVOTQ8ioPRwI7A5LMSC5JvxW8KsW2VX0
vGwRbw8I6HXGRbBZ3zwmAK73q7go31+Dl/5VPFo+fVTL0P7/k/g0ZAtCu4/Wly9e
DLnYMoZbIF5lgp9cAzPOaWXiInsA6HSdgFUfXsBemRpholuw+Sacxg==
-----END CERTIFICATE-----

View File

@ -1,56 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN PRIVATE KEY-----
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
5I2wbxUhb3FO7gj9pxJbwX4=
-----END PRIVATE KEY-----

View File

@ -1,55 +0,0 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -1,27 +0,0 @@
#
# 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.
#
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0DmQS4Xtgu5xtnQdxkuz
B1j+W4OvNEOVOsg3Zcn9W1d5NowtngUh9K9vwBpl59M2j5PHj9dseEIuRqr++ZnZ
hBMlh/lLiIQ0oQ3cEa7wrwJOi9ycZZbeNDHVNzAdQZEQR1DIMUhSTEMR96pVD4a9
DzBKLQJaKxZRUOrMhf3QhAZ59m0d/Kqu1Dm6YeWMLQQEewAaSQ9g22gty1EcLAvp
/vnhR/DJSYIHCayd5mZwvk9qWkXySfUmJGP70GFGG4GOnVLLkmCQgfT+OrzTtiIz
NT26mtAsoUMnoD42TTBkR0aLhULcj1MYUcB9uVCmJTvYuiCTODoNlL8T40r9L99H
oHlTWVi9vf6I1vyNdHp3dXKBMVL13+i5BeNIjADr0KKs0jtEEicWyuVhJz3rPLzB
bPhz/DGQ/hTj/DRSdo9L9YA4WkqgD8uFUvIEKAJ/hXYx3QPEiIMU7hT4jk2Z2SIB
iKKiNW4E/20EZOFmaeNWQaqqmwX0cHtMygJtTYnEJ1IDAgMBAAE=
-----END PUBLIC KEY-----

View File

@ -1,47 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN CERTIFICATE-----
MIIFRDCCAywCCQC4g2isVGolKjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEY
MBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIwMDIw
MzIzMDY1MloYDzMwMjAwMjExMjMwNjUyWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UECgwP
anNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAw7jTXeRwCRrDdYnrIwcLvSNfhpmJZ0ap1jzKVgUyCOYL
TaB9+naJRjHqx7B5wgx/ArRF2nluQ5tawZPMFw2I/iqXYrQEPTbq1XVugYC/C491
bcXOTKx+DgEvnhNysm/KmzFsEcw78prB5sIAZSR+S5zZPuny4zww2UzE9TZ433RB
kyA+wVkd64bgXdkMrVc+gsRsOtvwPFbQ89zg8d/pNV0mDtjDsfiYw0pSAah11fJG
a+aRc46CqFu/6rHuN4uq6542LdtshPbHz29VKHxk8agtcx06+8F05Bg4LFm1rRhY
0g3KsT7s8XHMVdo9h2bIQuWOaFg3mehpH6ZYBV4ENo98V/jDaUPpBHsaUXw4fG/w
rnI+YwRjGlmp2QEr5VRfh0x8Addf6N64lmbQUpCPhJweJd54D3JvIpJr8HiG3GYW
eFsrmDzmhrozZHxE4P7UesW6lWwQzGfwYGXs7j6TEa2hZ8EB/t1jsYNjZ5UYY/Jb
KgFGSkMGje4Bi5Bv6kh4+pp3DT5QsG/AfLVlr5ineLDWkJ15uZjOxl12EOPXOCWV
iVqS6rayJfb95YJQ52rT4H83BsApHbzFj7q/CIaeJkUdv9GJ2SOADcXdgG7Xk7tH
qb1VIH4zRBo5mc0qN1cAwjopxHv2h3tGaTHKbptvfiLulH+AvvuWmLMEQo6ZDlsC
AwEAATANBgkqhkiG9w0BAQsFAAOCAgEApnHjMQwt5hm6UlEDvdWCYbh7ctkLbgwR
iBP1lvunm2oF0jGpipt8oDR/TT43usb6ieuU+ABksjxOROeoVZbK8bEpnzeo3nNE
41ERI3Byjp7tsja8QGG0uBk9QZ0+7MhJqhEDVAIbS0Lf4exkWiLZrW7ogAEFYKTN
DE6CxOcfR/kXj6ejuCnvN4xYqnw8G/OF/3tnHMfKnnnqtMmWdAKd3Y5S1EJZ5vtp
lZ3I9HA5Hx0sTH1ruCOIRzaC5En1c6zW1HjxmeAqLeG814gezlEhHzb4SCkabgQh
Bq15O8eQaW92f8xZoUQN25w7SNYszk9AdhroJz3+BOzG3+Y1EInLk5hDHT8oUNFz
e8EosJEwJDK3wq9YOhn8PUT/DacyNKONJVNly3fTBXoSR3oReW61p6T19z4AYsY9
qMwSjIL2UcgAF8Kpsx2NdQrDfdveNMhul7AjIgz+e2DtRqCkZ6ypdhht2pmlpiXO
TiUG/1OBq2yTeJF9LjAUzsSNnsZ/F8pJbwSpr7VqDmTNGTfrh6x1ojHNFjJeTqK8
MCTmQtJJTAbV4nuB+thFFWDx0IWvbG7ViYds9sdJNO4L3baXeAioJhHs5buBy3eb
ZWjLAwHpSCqNY3d6+ouGLwE1YVFsk8sV9UM+gl15VynKkunbYoKhiD82HGASNYtE
33eif1l5Nk0=
-----END CERTIFICATE-----

View File

@ -1,68 +0,0 @@
#
# Copyright © 2020 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.
#
-----BEGIN PRIVATE KEY-----
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
lczRTJNTE/KNUE0pkC5P4c/e0A1OFu0=
-----END PRIVATE KEY-----

View File

@ -1,67 +0,0 @@
#
# 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.
#
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

Some files were not shown because too many files have changed in this diff Show More