mirror of https://github.com/jwtk/jjwt.git
Enabled key-specific Provider support during JwtParser execution. (#809)
* Enabled key-specific Provider support during JwtParser execution. Usage patterns and documentation updated inn JwtParserBuilder#keyLocator and README.md. * Updated Table of Contents with link to Key Locator key-specific Provider section * Added Pkcs11Test test case that ensures explicit .provider calls for all JWS and JWE operations are not needed when the PKCS11 provider is installed in the JVM via Security.addProvider * Removed unnecessary CryptoAlgorithm#nonPkcs11Provider method * Ensured RSA key validation implementation is consistent with name/available-length validation checks in other SecureDigestAlgorithm implementations
This commit is contained in:
parent
c3ff0bbf12
commit
8e0f740329
89
README.md
89
README.md
|
@ -65,6 +65,7 @@ enforcement.
|
|||
* [Custom Key Locator](#key-locator-custom)
|
||||
* [Key Locator Strategy](#key-locator-strategy)
|
||||
* [Key Locator Return Values](#key-locator-retvals)
|
||||
* [Provider-constrained Keys (PKCS11, HSM, etc)](#key-locator-provider)
|
||||
* [Claim Assertions](#jwt-read-claims)
|
||||
* [Accounting for Clock Skew](#jwt-read-clock)
|
||||
* [Custom Clock Support](#jwt-read-clock-custom)
|
||||
|
@ -1059,8 +1060,8 @@ try {
|
|||
jwt = Jwts.parser() // (1)
|
||||
|
||||
.keyLocator(keyLocator) // (2) dynamically locate signing or encryption keys
|
||||
//.verifyWith(key) // or a static key used to verify all signed JWTs
|
||||
//.decryptWith(key) // or a static key used to decrypt all encrypted JWTs
|
||||
//.verifyWith(key) // or a constant key used to verify all signed JWTs
|
||||
//.decryptWith(key) // or a constant key used to decrypt all encrypted JWTs
|
||||
|
||||
.build() // (3)
|
||||
|
||||
|
@ -1084,7 +1085,7 @@ catch (JwtException ex) { // (5)
|
|||
> instead of a generic `Jwt<?,?>` instance.
|
||||
|
||||
<a name="jwt-read-key"></a>
|
||||
### Static Parsing Key
|
||||
### Constant Parsing Key
|
||||
|
||||
If the JWT parsed is a JWS or JWE, a key will be necessary to verify the signature or decrypt it. If a JWS and
|
||||
signature verification fails, or if a JWE and decryption fails, the JWT cannot be safely trusted and should be
|
||||
|
@ -1285,6 +1286,46 @@ on the type of JWS or JWE algorithm used. That is:
|
|||
`Keys.password(char[] passwordCharacters)`.
|
||||
* For asymmetric key management algorithms, the returned decryption key should be a `PrivateKey` (not a `PublicKey`).
|
||||
|
||||
<a name="key-locator-provider"></a>
|
||||
#### Provider-constrained Keys
|
||||
|
||||
If any verification or decryption key returned from a Key `Locator` must be used with a specific security `Provider`
|
||||
(such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that `Provider` available for JWT parsing
|
||||
in one of 3 ways, listed in order of recommendation and simplicity:
|
||||
|
||||
1. [Configure the Provider in the JVM](https://docs.oracle.com/en/java/javase/17/security/howtoimplaprovider.html#GUID-831AA25F-F702-442D-A2E4-8DA6DEA16F33),
|
||||
either by modifying the `java.security` file or by registering the `Provider` dynamically via
|
||||
[Security.addProvider(Provider)](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/Security.html#addProvider(java.security.Provider)).
|
||||
This is the recommended approach so you do not need to modify code anywhere that may need to parse JWTs.
|
||||
|
||||
|
||||
2. Set the `Provider` as the parser default by calling `JwtParserBuilder#provider(Provider)`. This will
|
||||
ensure the provider is used by default with _all_ located keys unless overridden by a key-specific Provider. This
|
||||
is only recommended when you are confident that all JWTs encountered by the parser instance will use keys
|
||||
attributed to the same `Provider`, unless overridden by a specific key.
|
||||
|
||||
|
||||
3. Associate the `Provider` with a specific key using `Keys.builder` so it is used for that key only. This option is
|
||||
useful if some located keys require a specific provider, while other located keys can assume a default provider. For
|
||||
example:
|
||||
|
||||
```java
|
||||
public Key locate(Header<?> header) {
|
||||
|
||||
PrivateKey /* or SecretKey */ key = findKey(header); // implement me
|
||||
|
||||
Provider keySpecificProvider = findKeyProvider(key); // implement me
|
||||
if (keySpecificProvider != null) {
|
||||
// Ensure the key-specific provider (e.g. for PKCS11 or HSM) will be used
|
||||
// during decryption with the KeyAlgorithm in the JWE 'alg' header
|
||||
return Keys.builder(key).provider(keySpecificProvider).build();
|
||||
}
|
||||
|
||||
// otherwise default provider is fine:
|
||||
return key;
|
||||
}
|
||||
```
|
||||
|
||||
<a name="jwt-read-claims"></a><a name="jws-read-claims"></a> <!-- legacy anchor for old links -->
|
||||
### Claim Assertions
|
||||
|
||||
|
@ -1636,13 +1677,15 @@ If your secret key is:
|
|||
```
|
||||
* A raw (non-encoded) string (e.g. a password String):
|
||||
```java
|
||||
SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
|
||||
Password key = Keys.password(secretString.toCharArray());
|
||||
```
|
||||
It is always incorrect to call `secretString.getBytes()` (without providing a charset).
|
||||
|
||||
However, raw password strings like this, e.g. `correcthorsebatterystaple` should be avoided whenever possible
|
||||
because they can inevitably result in weak or susceptible keys. Secure-random keys are almost always stronger.
|
||||
If you are able, prefer creating a [new secure-random secret key](#jws-key-create-secret) instead.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> It is almost always incorrect to call any variant of `secretString.getBytes` in any cryptographic context.
|
||||
> Safe cryptographic keys are never represented as direct (unencoded) strings. If you have a password that should
|
||||
> be represented as a `Key` for `HMAC-SHA` algorithms, it is _strongly_ recommended to use a key derivation
|
||||
> algorithm to derive a cryptographically-strong `Key` from the password, and never use the password directly.
|
||||
|
||||
<a name="jws-create-key-algoverride"></a>
|
||||
##### SignatureAlgorithm Override
|
||||
|
@ -2266,18 +2309,17 @@ However, if your decryption `PrivateKey`s are stored in a Hardware Security Modu
|
|||
it is likely that your `PrivateKey` instances _do not_ implement `ECKey`.
|
||||
|
||||
In these cases, you need to provide both the PKCS11 `PrivateKey` and it's companion `PublicKey` during decryption
|
||||
by using the `Keys.wrap` method. For example:
|
||||
for example:
|
||||
by using the `Keys.builder` method. For example:
|
||||
|
||||
```java
|
||||
KeyPair pair = getMyPkcs11KeyPair();
|
||||
PrivateKey priv = pair.getPrivate();
|
||||
PublicKey pub = pair.getPublic(); // must implement ECKey or EdECKey or BouncyCastle equivalent
|
||||
PrivateKey decryptionKey = Keys.wrap(priv, pub);
|
||||
PrivateKey jwtParserDecryptionKey = Keys.builder(pair.getPrivate())
|
||||
.publicKey(pair.getPublic()) // PublicKey must implement ECKey or EdECKey or BouncyCastle equivalent
|
||||
.build();
|
||||
```
|
||||
|
||||
You then use the resulting `decryptionKey` (not `priv`) with the `JwtParserBuilder` or as the return value from
|
||||
a custom [Key Locator](#key-locator) implementation. For example:
|
||||
You then use the resulting `jwtParserDecryptionKey` (not `pair.getPrivate()`) with the `JwtParserBuilder` or as
|
||||
the return value from a custom [Key Locator](#key-locator) implementation. For example:
|
||||
|
||||
```java
|
||||
PrivateKey decryptionKey = Keys.wrap(pkcs11PrivateKey, pkcs11PublicKey);
|
||||
|
@ -2292,11 +2334,14 @@ Or as the return value from your key locator:
|
|||
|
||||
```java
|
||||
Jwts.parser()
|
||||
.keyLocator(keyLocator) // your keyLocator.locate(header) would return Keys.wrap(privateKey, publicKey)
|
||||
.keyLocator(keyLocator) // your keyLocator.locate(header) would return Keys.builder...
|
||||
.build()
|
||||
.parseClaimsJwe(jweString);
|
||||
```
|
||||
|
||||
Please see the [Provider-constrained Keys](#key-locator-provider) section for more information, as well as
|
||||
code examples of how to implement a Key `Locator` using the `Keys.builder` technique.
|
||||
|
||||
<a name="jwe-read-decompression"></a>
|
||||
#### JWE Decompression
|
||||
|
||||
|
@ -2474,7 +2519,7 @@ The resulting `JwkThumbprint` instance provides some useful methods:
|
|||
* `jwkThumbprint.toByteArray()`: the thumbprint's actual digest bytes - i.e. the raw output from the hash algorithm
|
||||
* `jwkThumbprint.toString()`: the digest bytes as a Base64URL-encoded string
|
||||
* `jwkThumbprint.getHashAlgorithm()`: the specific `HashAlgorithm` used to compute the thumbprint. Many standard IANA
|
||||
hash algorithms are available as constants in the `Jwts.HASH` utility class.
|
||||
hash algorithms are available as constants in the `Jwks.HASH` utility class.
|
||||
* `jwkThumbprint.toURI()`: the thumbprint's canonical URI as defined by the [JWK Thumbprint URI](https://www.rfc-editor.org/rfc/rfc9278.html) specification
|
||||
|
||||
<a name="jwk-thumbprint-kid"></a>
|
||||
|
@ -2589,10 +2634,10 @@ This is true for all secret or private key members in `SecretJwk` and `PrivateJw
|
|||
|
||||
> **Warning**
|
||||
>
|
||||
> **The JWT specifications tandardizes compression for JWEs (Encrypted JWTs) ONLY, however JJWT supports it for JWS
|
||||
> **The JWT specification standardizes compression for JWEs (Encrypted JWTs) ONLY, however JJWT supports it for JWS
|
||||
> (Signed JWTs) as well**.
|
||||
>
|
||||
> If you are positive that a JWT you create with JJWT will _also_ be parsed with JJWT,
|
||||
> If you are positive that a JWS you create with JJWT will _also_ be parsed with JJWT,
|
||||
> you can use this feature with both JWEs and JWSs, otherwise it is best to only use it for JWEs.
|
||||
|
||||
If a JWT's `payload` is sufficiently large - that is, it is a large content byte array or JSON with a lot of
|
||||
|
@ -3185,9 +3230,9 @@ This is an example showing how to digitally sign and verify a JWT using the
|
|||
The `EdDSA` signature algorithm is defined for JWS in [RFC 8037, Section 3.1](https://www.rfc-editor.org/rfc/rfc8037#section-3.1)
|
||||
using keys for two Edwards curves:
|
||||
|
||||
* `Ed25519`: `EdDSA` using curve `Ed25519`. `Ed25519` algorithm keys must be 256 bits (32 bytes) long and produce
|
||||
* `Ed25519`: `EdDSA` using curve `Ed25519`. `Ed25519` algorithm keys must be 255 bits long and produce
|
||||
signatures 512 bits (64 bytes) long.
|
||||
* `Ed448`: `EdDSA` using curve `Ed448`. `Ed448` algorithm keys must be 456 bits (57 bytes) long and produce signatures
|
||||
* `Ed448`: `EdDSA` using curve `Ed448`. `Ed448` algorithm keys must be 448 bits long and produce signatures
|
||||
912 bits (114 bytes) long.
|
||||
|
||||
In this example, Bob will sign a JWT using his Edwards Curve private key, and Alice can verify it came from Bob
|
||||
|
|
|
@ -95,7 +95,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
|||
JwtParserBuilder enableUnsecuredDecompression();
|
||||
|
||||
/**
|
||||
* Sets the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} if the
|
||||
* Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the
|
||||
* JCA subsystem preferred provider should be used.
|
||||
*
|
||||
* @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null}
|
||||
|
@ -432,7 +432,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
|||
* <li>If the parsed String is a JWE, it will be called to find the appropriate decryption key.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Specifying a key {@code Locator} is necessary when the signature verification or decryption key is not
|
||||
* <p>A key {@code Locator} is necessary when the signature verification or decryption key is not
|
||||
* already known before parsing the JWT and the JWT header must be inspected first to determine how to
|
||||
* look up the verification or decryption key. Once returned by the locator, the JwtParser will then either
|
||||
* verify the JWS signature or decrypt the JWE payload with the returned key. For example:</p>
|
||||
|
@ -453,6 +453,38 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
|||
*
|
||||
* <p>A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.</p>
|
||||
*
|
||||
* <p><b>Provider-constrained Keys</b></p>
|
||||
*
|
||||
* <p>If any verification or decryption key returned from a Key {@code Locator} must be used with a specific
|
||||
* security {@link Provider} (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that
|
||||
* Provider available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li><a href="https://docs.oracle.com/en/java/javase/17/security/howtoimplaprovider.html#GUID-831AA25F-F702-442D-A2E4-8DA6DEA16F33">
|
||||
* Configure the Provider in the JVM</a>, either by modifying the {@code java.security} file or by
|
||||
* registering the Provider dynamically via
|
||||
* {@link java.security.Security#addProvider(Provider) Security.addProvider(Provider)}. This is the
|
||||
* recommended approach so you do not need to modify code anywhere that may need to parse JWTs.</li>
|
||||
* <li>Specify the {@code Provider} as the {@code JwtParser} default via {@link #provider(Provider)}. This will
|
||||
* ensure the provider is used by default with <em>all</em> located keys unless overridden by a
|
||||
* key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the
|
||||
* parser instance will use keys attributed to the same {@code Provider}, unless overridden by a specific
|
||||
* key.</li>
|
||||
* <li>Associate the {@code Provider} with a specific key so it is used for that key only. This option
|
||||
* is useful if some located keys require a specific provider, while other located keys can assume a
|
||||
* default provider.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>If you need to use option #3, you associate a key for the {@code JwtParser}'s needs by using a
|
||||
* key builder before returning the key as the {@code Locator} return value. For example:</p>
|
||||
* <blockquote><pre>
|
||||
* public Key locate(Header<?> header) {
|
||||
* PrivateKey key = findKey(header); // or SecretKey
|
||||
* Provider keySpecificProvider = getKeyProvider(key); // implement me
|
||||
* // associate the key with its required provider:
|
||||
* return Keys.builder(key).provider(keySpecificProvider).build();
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* @param keyLocator the locator used to retrieve decryption or signature verification keys.
|
||||
* @return the parser builder for method chaining.
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.crypto.SecretKey;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
|
@ -35,7 +36,12 @@ public final class Keys {
|
|||
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge";
|
||||
private static final Class<?> BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME);
|
||||
private static final Class<?>[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class};
|
||||
private static final Class<?>[] ASSOCIATE_ARG_TYPES = new Class[]{PrivateKey.class, PublicKey.class};
|
||||
private static final Class<?>[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class};
|
||||
private static final Class<?>[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class};
|
||||
|
||||
private static <T> T invokeStatic(String method, Class<?>[] argTypes, Object... args) {
|
||||
return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args);
|
||||
}
|
||||
|
||||
//prevent instantiation
|
||||
private Keys() {
|
||||
|
@ -268,33 +274,59 @@ public final class Keys {
|
|||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public static Password password(char[] password) {
|
||||
return Classes.invokeStatic(BRIDGE_CLASS, "password", FOR_PASSWORD_ARG_TYPES, new Object[]{password});
|
||||
return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code PrivateKey} that may be used by algorithms that require the private key's public information.
|
||||
* This method is primarily only useful for PKCS11 private keys.
|
||||
* Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a
|
||||
* {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic
|
||||
* operations. For example:
|
||||
*
|
||||
* <p>If the private key instance is already capable of representing public information (because it
|
||||
* implements one of the <code>java.security.interfaces.{ECKey,RSAKey,XECKey,EdECKey}</code> interfaces),
|
||||
* this method does nothing and returns the private key unaltered.</p>
|
||||
* <blockquote><pre>
|
||||
* SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();</pre></blockquote>
|
||||
*
|
||||
* <p>If however the private key instance does <em>not</em> implement one of those interfaces, a new private key
|
||||
* instance that wraps both the specified private key and public key will be created and returned. JJWT
|
||||
* algorithms that require the key's public information know how to handle these wrapper instances to obtain the
|
||||
* 'real' private key for cryptography operations while using the associated public key for validation.</p>
|
||||
* <p>Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its
|
||||
* mandatory {@code Provider} if necessary.</p>
|
||||
*
|
||||
* @param priv the private key to use for cryptographic operations
|
||||
* @param pub the private key's associated PublicKey which must implement one of the required
|
||||
* <code>java.security.interfaces.{ECKey,RSAKey,XECKey,EdECKey}</code> interfaces
|
||||
* @return a {@code PrivateKey} that may be used by algorithms that require the private key's public information.
|
||||
* @throws UnsupportedKeyException if the {@code PublicKey} is required but does not implement one of the required
|
||||
* <code>java.security.interfaces.{ECKey,RSAKey,XECKey,EdECKey}</code> interfaces
|
||||
* <p>This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM
|
||||
* (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic
|
||||
* operations.</p>
|
||||
*
|
||||
* @param key the secret key to use for cryptographic operations, potentially associated with a configured
|
||||
* {@link Provider}
|
||||
* @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any
|
||||
* specified provider.
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public static PrivateKey wrap(PrivateKey priv, PublicKey pub) throws UnsupportedKeyException {
|
||||
Assert.notNull(priv, "PrivateKey cannot be null.");
|
||||
Assert.notNull(pub, "PublicKey cannot be null.");
|
||||
return Classes.invokeStatic(BRIDGE_CLASS, "wrap", ASSOCIATE_ARG_TYPES, new Object[]{priv, pub});
|
||||
public static SecretKeyBuilder builder(SecretKey key) {
|
||||
Assert.notNull(key, "SecretKey cannot be null.");
|
||||
return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a
|
||||
* {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a
|
||||
* {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic
|
||||
* operations. For example:
|
||||
*
|
||||
* <blockquote><pre>
|
||||
* PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();</pre></blockquote>
|
||||
*
|
||||
* <p>Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its
|
||||
* mandatory {@code Provider} or {@code PublicKey} if necessary.</p>
|
||||
*
|
||||
* <p>This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM
|
||||
* (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used
|
||||
* during cryptographic operations.</p>
|
||||
*
|
||||
* @param key the private key to use for cryptographic operations, potentially associated with a configured
|
||||
* {@link Provider} or {@link PublicKey}.
|
||||
* @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any
|
||||
* specified provider or {@code PublicKey}
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public static PrivateKeyBuilder builder(PrivateKey key) {
|
||||
Assert.notNull(key, "PrivateKey cannot be null.");
|
||||
return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.security;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
public interface PrivateKeyBuilder extends KeyBuilder<PrivateKey, PrivateKeyBuilder> {
|
||||
PrivateKeyBuilder publicKey(PublicKey publicKey);
|
||||
}
|
|
@ -27,6 +27,7 @@ import io.jsonwebtoken.impl.security.DefaultAeadRequest;
|
|||
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
|
||||
import io.jsonwebtoken.impl.security.DefaultSecureRequest;
|
||||
import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm;
|
||||
import io.jsonwebtoken.impl.security.ProviderKey;
|
||||
import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms;
|
||||
import io.jsonwebtoken.io.CompressionAlgorithm;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
|
@ -473,14 +474,16 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
this.headerBuilder.add(DefaultHeader.COMPRESSION_ALGORITHM.getId(), compressionAlgorithm.getId());
|
||||
}
|
||||
|
||||
Provider keyProvider = ProviderKey.getProvider(this.key, this.provider);
|
||||
Key key = ProviderKey.getKey(this.key);
|
||||
if (jwe) {
|
||||
return encrypt(payload);
|
||||
return encrypt(payload, key, keyProvider);
|
||||
} else {
|
||||
return compact(payload);
|
||||
return compact(payload, key, keyProvider);
|
||||
}
|
||||
}
|
||||
|
||||
private String compact(byte[] payload) {
|
||||
private String compact(byte[] payload, Key key, Provider provider) {
|
||||
|
||||
Assert.stateNotNull(sigAlg, "SignatureAlgorithm is required."); // invariant
|
||||
|
||||
|
@ -494,7 +497,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
|
||||
String jwt = base64UrlEncodedHeader + DefaultJwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
|
||||
|
||||
if (this.key != null) { //jwt must be signed:
|
||||
if (key != null) { //jwt must be signed:
|
||||
Assert.stateNotNull(key, "Signing key cannot be null.");
|
||||
Assert.stateNotNull(signFunction, "signFunction cannot be null.");
|
||||
byte[] data = jwt.getBytes(StandardCharsets.US_ASCII);
|
||||
|
@ -511,7 +514,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
return jwt;
|
||||
}
|
||||
|
||||
private String encrypt(byte[] payload) {
|
||||
private String encrypt(byte[] payload, Key key, Provider keyProvider) {
|
||||
|
||||
Assert.stateNotNull(key, "Key is required."); // set by encryptWith*
|
||||
Assert.stateNotNull(enc, "Encryption algorithm is required."); // set by encryptWith*
|
||||
|
@ -523,7 +526,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
//only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder
|
||||
// (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms):
|
||||
JweHeader delegate = new DefaultMutableJweHeader(this.headerBuilder);
|
||||
KeyRequest<Key> keyRequest = new DefaultKeyRequest<>(this.key, this.provider, this.secureRandom, delegate, enc);
|
||||
KeyRequest<Key> keyRequest = new DefaultKeyRequest<>(key, keyProvider, this.secureRandom, delegate, enc);
|
||||
KeyResult keyResult = keyAlgFunction.apply(keyRequest);
|
||||
Assert.stateNotNull(keyResult, "KeyAlgorithm must return a KeyResult.");
|
||||
|
||||
|
@ -541,12 +544,9 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
|
||||
// During encryption, the configured Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly
|
||||
// because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath).
|
||||
// As such, the need for a configured Provider is much more likely necessary for the KeyAlgorithm,
|
||||
// especially when using a HSM/PKCS11 Provider. However, if the `dir`ect key algorithm was chosen _and_
|
||||
// a Provider was configured, then the provider is likely necessary for that key, so we represent that
|
||||
// here:
|
||||
Provider aeadProvider = this.keyAlg.getId().equals(Jwts.KEY.DIRECT.getId()) ? this.provider : null;
|
||||
AeadRequest encRequest = new DefaultAeadRequest(payload, aeadProvider, secureRandom, cek, aad);
|
||||
// As such, the provider here is intentionally omitted (null):
|
||||
// TODO: add encProvider(Provider) builder method that applies to this request only?
|
||||
AeadRequest encRequest = new DefaultAeadRequest(payload, null, secureRandom, cek, aad);
|
||||
AeadResult encResult = encFunction.apply(encRequest);
|
||||
|
||||
byte[] iv = Assert.notEmpty(encResult.getInitializationVector(), "Encryption result must have a non-empty initialization vector.");
|
||||
|
|
|
@ -44,6 +44,7 @@ import io.jsonwebtoken.impl.security.DefaultAeadResult;
|
|||
import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest;
|
||||
import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest;
|
||||
import io.jsonwebtoken.impl.security.LocatingKeyResolver;
|
||||
import io.jsonwebtoken.impl.security.ProviderKey;
|
||||
import io.jsonwebtoken.io.CompressionAlgorithm;
|
||||
import io.jsonwebtoken.io.Decoder;
|
||||
import io.jsonwebtoken.io.DecodingException;
|
||||
|
@ -310,8 +311,11 @@ public class DefaultJwtParser implements JwtParser {
|
|||
byte[] signature = decode(tokenized.getDigest(), "JWS signature");
|
||||
|
||||
try {
|
||||
Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary
|
||||
key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider
|
||||
Assert.stateNotNull(key, "ProviderKey cannot be null."); //ProviderKey impl doesn't allow null
|
||||
VerifySecureDigestRequest<Key> request =
|
||||
new DefaultVerifySecureDigestRequest<>(data, this.provider, null, key, signature);
|
||||
new DefaultVerifySecureDigestRequest<>(data, provider, null, key, signature);
|
||||
if (!algorithm.verify(request)) {
|
||||
String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
|
||||
"asserted and should not be trusted.";
|
||||
|
@ -445,14 +449,17 @@ public class DefaultJwtParser implements JwtParser {
|
|||
@SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgFn.apply(jweHeader);
|
||||
Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null.");
|
||||
|
||||
final Key key = this.keyLocator.locate(jweHeader);
|
||||
Key key = this.keyLocator.locate(jweHeader);
|
||||
if (key == null) {
|
||||
String msg = "Cannot decrypt JWE payload: unable to locate key for JWE with header: " + jweHeader;
|
||||
throw new UnsupportedJwtException(msg);
|
||||
}
|
||||
|
||||
// extract key-specific provider if necessary;
|
||||
Provider provider = ProviderKey.getProvider(key, this.provider);
|
||||
key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider
|
||||
DecryptionKeyRequest<Key> request =
|
||||
new DefaultDecryptionKeyRequest<>(cekBytes, this.provider, null, jweHeader, encAlg, key);
|
||||
new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key);
|
||||
final SecretKey cek = keyAlg.getDecryptionKey(request);
|
||||
if (cek == null) {
|
||||
String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " +
|
||||
|
@ -460,15 +467,12 @@ public class DefaultJwtParser implements JwtParser {
|
|||
throw new IllegalStateException(msg);
|
||||
}
|
||||
|
||||
// During decryption, the configured Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly
|
||||
// During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly
|
||||
// because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath).
|
||||
// As such, the need for a configured Provider is much more likely necessary for the KeyAlgorithm,
|
||||
// especially when using a HSM/PKCS11 Provider. However, if the `dir`ect key algorithm was chosen _and_
|
||||
// a Provider was configured, then the provider is likely necessary for that key, so we represent that
|
||||
// here:
|
||||
final Provider aeadProvider = keyAlg.getId().equalsIgnoreCase(Jwts.KEY.DIRECT.getId()) ? this.provider : null;
|
||||
// As such, the provider here is intentionally omitted (null):
|
||||
// TODO: add encProvider(Provider) builder method that applies to this request only?
|
||||
DecryptAeadRequest decryptRequest =
|
||||
new DefaultAeadResult(aeadProvider, null, payload, cek, aad, tag, iv);
|
||||
new DefaultAeadResult(null, null, payload, cek, aad, tag, iv);
|
||||
Message<byte[]> result = encAlg.decrypt(decryptRequest);
|
||||
payload = result.getPayload();
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.security.SecurityBuilder;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
abstract class AbstractSecurityBuilder<T, B extends SecurityBuilder<T, B>> implements SecurityBuilder<T, B> {
|
||||
|
||||
protected Provider provider;
|
||||
protected SecureRandom random;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final B self() {
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B provider(Provider provider) {
|
||||
this.provider = provider;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B random(SecureRandom random) {
|
||||
this.random = random != null ? random : Randoms.secureRandom();
|
||||
return self();
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ package io.jsonwebtoken.impl.security;
|
|||
|
||||
import io.jsonwebtoken.Identifiable;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
import io.jsonwebtoken.security.AeadAlgorithm;
|
||||
import io.jsonwebtoken.security.KeyRequest;
|
||||
import io.jsonwebtoken.security.Request;
|
||||
|
@ -52,29 +51,11 @@ abstract class CryptoAlgorithm implements Identifiable {
|
|||
return this.jcaName;
|
||||
}
|
||||
|
||||
SecureRandom ensureSecureRandom(Request<?> request) {
|
||||
static SecureRandom ensureSecureRandom(Request<?> request) {
|
||||
SecureRandom random = request != null ? request.getSecureRandom() : null;
|
||||
return random != null ? random : Randoms.secureRandom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request provider only if it is <em>not</em> a PCKS11 provider. This is used by algorithms that
|
||||
* generate an ephemeral key(pair) where the resulting key material must exist for inclusion in the JWE. PCS11
|
||||
* providers will not expose private key material and therefore can't be used for ephemeral key(pair) generation.
|
||||
*
|
||||
* @param request request to inspect
|
||||
* @return the request provider or {@code null} if there is no provider, or {@code null} if the provider is a
|
||||
* PCKS11 provider
|
||||
*/
|
||||
static Provider nonPkcs11Provider(Request<?> request) {
|
||||
Provider provider = request != null ? request.getProvider() : null;
|
||||
String name = provider != null ? Strings.clean(provider.getName()) : null;
|
||||
if (provider != null && name != null && name.startsWith("SunPKCS11")) {
|
||||
provider = null; // don't use PKCS11 provider
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
protected JcaTemplate jca() {
|
||||
return new JcaTemplate(getJcaName());
|
||||
}
|
||||
|
@ -94,8 +75,7 @@ abstract class CryptoAlgorithm implements Identifiable {
|
|||
protected SecretKey generateCek(KeyRequest<?> request) {
|
||||
AeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null.");
|
||||
SecretKeyBuilder builder = Assert.notNull(enc.key(), "Request encryptionAlgorithm KeyBuilder cannot be null.");
|
||||
Provider provider = nonPkcs11Provider(request); // PKCS11 / HSM check
|
||||
SecretKey key = builder.provider(provider).random(request.getSecureRandom()).build();
|
||||
SecretKey key = builder.random(request.getSecureRandom()).build();
|
||||
return Assert.notNull(key, "Request encryptionAlgorithm SecretKeyBuilder cannot produce null keys.");
|
||||
}
|
||||
|
||||
|
|
|
@ -19,17 +19,13 @@ import io.jsonwebtoken.lang.Assert;
|
|||
import io.jsonwebtoken.security.KeyPairBuilder;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
public class DefaultKeyPairBuilder implements KeyPairBuilder {
|
||||
public class DefaultKeyPairBuilder extends AbstractSecurityBuilder<KeyPair, KeyPairBuilder> implements KeyPairBuilder {
|
||||
|
||||
private final String jcaName;
|
||||
private final int bitLength;
|
||||
private final AlgorithmParameterSpec params;
|
||||
private Provider provider;
|
||||
private SecureRandom random;
|
||||
|
||||
public DefaultKeyPairBuilder(String jcaName) {
|
||||
this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty.");
|
||||
|
@ -49,18 +45,6 @@ public class DefaultKeyPairBuilder implements KeyPairBuilder {
|
|||
this.bitLength = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPairBuilder provider(Provider provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPairBuilder random(SecureRandom random) {
|
||||
this.random = random;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair build() {
|
||||
JcaTemplate template = new JcaTemplate(this.jcaName, this.provider, this.random);
|
||||
|
|
|
@ -124,7 +124,7 @@ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey,
|
|||
return new DefaultSecretKeyBuilder(getJcaName(), getKeyBitLength());
|
||||
}
|
||||
|
||||
private String assertAlgorithmName(SecretKey key, boolean signing) {
|
||||
private void assertAlgorithmName(SecretKey key, boolean signing) {
|
||||
|
||||
String name = key.getAlgorithm();
|
||||
if (!Strings.hasText(name)) {
|
||||
|
@ -143,8 +143,6 @@ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey,
|
|||
"' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " +
|
||||
getId() + ".");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,11 +27,9 @@ import io.jsonwebtoken.security.WeakKeyException;
|
|||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.math.BigInteger;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
|
@ -58,28 +56,27 @@ public class DefaultRsaKeyAlgorithm extends CryptoAlgorithm implements KeyAlgori
|
|||
|
||||
protected void validate(Key key, boolean encryption) { // true = encryption, false = decryption
|
||||
|
||||
if (!RsaSignatureAlgorithm.isRsaAlgorithmName(key)) {
|
||||
throw new UnsupportedKeyException("Unsupported RSA key algorithm name.");
|
||||
}
|
||||
|
||||
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
|
||||
// If so, we can provide additional safety checks:
|
||||
if (key instanceof RSAKey) {
|
||||
RSAKey rsaKey = (RSAKey) key;
|
||||
BigInteger modulus = Assert.notNull(rsaKey.getModulus(), "RSAKey modulus cannot be null.");
|
||||
int size = modulus.bitLength();
|
||||
if (size < MIN_KEY_BIT_LENGTH) {
|
||||
String id = getId();
|
||||
String section = id.startsWith("RSA1") ? "4.2" : "4.3";
|
||||
String msg = "The RSA " + keyType(encryption) + " key's size (modulus) is " + size +
|
||||
" bits which is not secure 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. See " +
|
||||
"https://www.rfc-editor.org/rfc/rfc7518.html#section-" + section + " for more information.";
|
||||
throw new WeakKeyException(msg);
|
||||
}
|
||||
int size = KeysBridge.findBitLength(key);
|
||||
if (size < 0) return; // can't validate size: material or length not available (e.g. PKCS11 or HSM)
|
||||
if (size < MIN_KEY_BIT_LENGTH) {
|
||||
String id = getId();
|
||||
String section = id.startsWith("RSA1") ? "4.2" : "4.3";
|
||||
String msg = "The RSA " + keyType(encryption) + " key size (aka modulus bit length) is " + size +
|
||||
" bits which is not secure 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. See " +
|
||||
"https://www.rfc-editor.org/rfc/rfc7518.html#section-" + section + " for more information.";
|
||||
throw new WeakKeyException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,18 +19,15 @@ import io.jsonwebtoken.lang.Assert;
|
|||
import io.jsonwebtoken.security.SecretKeyBuilder;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public class DefaultSecretKeyBuilder implements SecretKeyBuilder {
|
||||
public class DefaultSecretKeyBuilder extends AbstractSecurityBuilder<SecretKey, SecretKeyBuilder>
|
||||
implements SecretKeyBuilder {
|
||||
|
||||
protected final String JCA_NAME;
|
||||
protected final int BIT_LENGTH;
|
||||
protected Provider provider;
|
||||
protected SecureRandom random;
|
||||
|
||||
public DefaultSecretKeyBuilder(String jcaName, int bitLength) {
|
||||
this.JCA_NAME = Assert.hasText(jcaName, "jcaName cannot be null or empty.");
|
||||
|
@ -42,18 +39,6 @@ public class DefaultSecretKeyBuilder implements SecretKeyBuilder {
|
|||
random(Randoms.secureRandom());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKeyBuilder provider(Provider provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKeyBuilder random(SecureRandom random) {
|
||||
this.random = random != null ? random : Randoms.secureRandom();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey build() {
|
||||
JcaTemplate template = new JcaTemplate(JCA_NAME, this.provider, this.random);
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.jsonwebtoken.security.KeyPairBuilder;
|
|||
import io.jsonwebtoken.security.SecureRequest;
|
||||
import io.jsonwebtoken.security.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import io.jsonwebtoken.security.UnsupportedKeyException;
|
||||
import io.jsonwebtoken.security.VerifySecureDigestRequest;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
@ -39,6 +40,7 @@ import java.util.Arrays;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
// @since JJWT_RELEASE_VERSION
|
||||
final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
||||
|
@ -51,6 +53,8 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
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 static final Set<String> KEY_ALG_NAMES = Collections.setOf("EC", "ECDSA", ES256_OID, ES384_OID, ES512_OID);
|
||||
|
||||
private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS;
|
||||
|
||||
private final int orderBitLength;
|
||||
|
@ -85,7 +89,6 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID);
|
||||
|
||||
private static final Map<String, SignatureAlgorithm> BY_OID = new LinkedHashMap<>(3);
|
||||
|
||||
static {
|
||||
for (EcSignatureAlgorithm alg : Collections.of(ES256, ES384, ES512)) {
|
||||
BY_OID.put(alg.OID, alg);
|
||||
|
@ -140,23 +143,19 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
@Override
|
||||
protected void validateKey(Key key, boolean signing) {
|
||||
super.validateKey(key, signing);
|
||||
// Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check to see if we can cast
|
||||
// If so, we can provide the additional safety checks:
|
||||
if (key instanceof ECKey) {
|
||||
final String name = getId();
|
||||
ECKey ecKey = (ECKey) key;
|
||||
BigInteger order = ecKey.getParams().getOrder();
|
||||
int orderBitLength = order.bitLength();
|
||||
int sigFieldByteLength = Bytes.length(orderBitLength);
|
||||
int concatByteLength = sigFieldByteLength * 2;
|
||||
|
||||
if (concatByteLength != this.signatureByteLength) {
|
||||
String msg = "The provided Elliptic Curve " + keyType(signing) +
|
||||
" key's size (aka Order bit length) is " + Bytes.bitsMsg(orderBitLength) + ", but the '" +
|
||||
name + "' algorithm requires EC Keys with " + Bytes.bitsMsg(this.orderBitLength) +
|
||||
" per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
|
||||
throw new InvalidKeyException(msg);
|
||||
}
|
||||
if (!KEY_ALG_NAMES.contains(KeysBridge.findAlgorithm(key))) {
|
||||
throw new UnsupportedKeyException("Unsupported EC key algorithm name.");
|
||||
}
|
||||
int size = KeysBridge.findBitLength(key);
|
||||
if (size < 0) return; // likely PKCS11 or HSM key, can't get the data we need
|
||||
int sigFieldByteLength = Bytes.length(size);
|
||||
int concatByteLength = sigFieldByteLength * 2;
|
||||
if (concatByteLength != this.signatureByteLength) {
|
||||
String msg = "The provided Elliptic Curve " + keyType(signing) +
|
||||
" key size (aka order bit length) is " + Bytes.bitsMsg(size) + ", but the '" +
|
||||
getId() + "' algorithm requires EC Keys with " + Bytes.bitsMsg(this.orderBitLength) +
|
||||
" per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
|
||||
throw new InvalidKeyException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +164,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
return jca(request).withSignature(new CheckedFunction<Signature, byte[]>() {
|
||||
@Override
|
||||
public byte[] apply(Signature sig) throws Exception {
|
||||
sig.initSign(request.getKey());
|
||||
sig.initSign(KeysBridge.root(request));
|
||||
sig.update(request.getPayload());
|
||||
byte[] signature = sig.sign();
|
||||
return transcodeDERToConcat(signature, signatureByteLength);
|
||||
|
|
|
@ -34,7 +34,6 @@ import io.jsonwebtoken.security.KeyAlgorithm;
|
|||
import io.jsonwebtoken.security.KeyLengthSupplier;
|
||||
import io.jsonwebtoken.security.KeyRequest;
|
||||
import io.jsonwebtoken.security.KeyResult;
|
||||
import io.jsonwebtoken.security.KeySupplier;
|
||||
import io.jsonwebtoken.security.OctetPublicJwk;
|
||||
import io.jsonwebtoken.security.PublicJwk;
|
||||
import io.jsonwebtoken.security.Request;
|
||||
|
@ -95,7 +94,7 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
|
|||
return jca(request).withKeyAgreement(new CheckedFunction<KeyAgreement, byte[]>() {
|
||||
@Override
|
||||
public byte[] apply(KeyAgreement keyAgreement) throws Exception {
|
||||
keyAgreement.init(priv, ensureSecureRandom(request));
|
||||
keyAgreement.init(KeysBridge.root(priv), ensureSecureRandom(request));
|
||||
keyAgreement.doPhase(pub, true);
|
||||
return keyAgreement.generateSecret();
|
||||
}
|
||||
|
@ -182,9 +181,8 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
|
|||
|
||||
// Generate our ephemeral key pair:
|
||||
final SecureRandom random = ensureSecureRandom(request);
|
||||
final Provider provider = nonPkcs11Provider(request); // PKCS11 / HSM check
|
||||
DynamicJwkBuilder<?, ?> jwkBuilder = Jwks.builder().random(random).provider(provider);
|
||||
KeyPair pair = generateKeyPair(curve, provider, random);
|
||||
DynamicJwkBuilder<?, ?> jwkBuilder = Jwks.builder().random(random);
|
||||
KeyPair pair = generateKeyPair(curve, null, random);
|
||||
|
||||
Assert.stateNotNull(pair, "Internal implementation state: KeyPair cannot be null.");
|
||||
|
||||
|
@ -225,14 +223,6 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
|
|||
throw new InvalidKeyException(msg);
|
||||
}
|
||||
|
||||
// PKCS11 unwrap if necessary:
|
||||
if (privateKey instanceof KeySupplier) {
|
||||
Key key = ((KeySupplier<?>) privateKey).getKey();
|
||||
// wrapped keys are only produced within JJWT internal implementations (not by app devs)
|
||||
// so the wrapped key should always be a PrivateKey:
|
||||
privateKey = Assert.isInstanceOf(PrivateKey.class, key, "Wrapped key is not a PrivateKey.");
|
||||
}
|
||||
|
||||
final SecretKey derived = deriveKey(request, epk.toKey(), privateKey);
|
||||
|
||||
DecryptionKeyRequest<SecretKey> unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(),
|
||||
|
|
|
@ -47,18 +47,13 @@ final class EdSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
@Override
|
||||
protected String getJcaName(Request<?> request) {
|
||||
SecureRequest<?, ?> req = Assert.isInstanceOf(SecureRequest.class, request, "SecureRequests are required.");
|
||||
Key key = req.getKey();
|
||||
|
||||
Key key = Assert.notNull(req.getKey(), "Request key cannot be null.");
|
||||
// If we're signing, and this instance's algorithm name is the default/generic 'EdDSA', then prefer the
|
||||
// signing key's curve algorithm ID. This ensures the most specific JCA algorithm is used for signing,
|
||||
// (while generic 'EdDSA' is fine for validation)
|
||||
String jcaName = getJcaName(); //default for JCA interaction
|
||||
boolean signing = !(request instanceof VerifyDigestRequest);
|
||||
if (ID.equals(jcaName) && signing) { // see if we can get a more-specific curve algorithm identifier:
|
||||
EdwardsCurve curve = EdwardsCurve.findByKey(key);
|
||||
if (curve != null) {
|
||||
jcaName = curve.getJcaName(); // prefer the key's specific curve algorithm identifier during signing
|
||||
}
|
||||
if (!(request instanceof VerifyDigestRequest)) { // then we're signing, not verifying
|
||||
jcaName = EdwardsCurve.forKey(key).getJcaName();
|
||||
}
|
||||
return jcaName;
|
||||
}
|
||||
|
@ -71,8 +66,9 @@ final class EdSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
@Override
|
||||
protected void validateKey(Key key, boolean signing) {
|
||||
super.validateKey(key, signing);
|
||||
EdwardsCurve curve = EdwardsCurve.findByKey(key);
|
||||
if (curve != null && !curve.isSignatureCurve()) {
|
||||
// should always be non-null due to algorithm name lookup, even without encoded key bytes:
|
||||
EdwardsCurve curve = EdwardsCurve.forKey(key);
|
||||
if (!curve.isSignatureCurve()) {
|
||||
String msg = curve.getId() + " keys may not be used with " + getId() + " digital signatures per " +
|
||||
"https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2";
|
||||
throw new UnsupportedKeyException(msg);
|
||||
|
|
|
@ -21,13 +21,14 @@ import io.jsonwebtoken.lang.Assert;
|
|||
import io.jsonwebtoken.lang.Strings;
|
||||
import io.jsonwebtoken.security.KeySupplier;
|
||||
import io.jsonwebtoken.security.Password;
|
||||
import io.jsonwebtoken.security.PrivateKeyBuilder;
|
||||
import io.jsonwebtoken.security.SecretKeyBuilder;
|
||||
import io.jsonwebtoken.security.UnsupportedKeyException;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
|
||||
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
|
||||
public final class KeysBridge {
|
||||
|
@ -48,24 +49,30 @@ public final class KeysBridge {
|
|||
return new PasswordSpec(password);
|
||||
}
|
||||
|
||||
public static PrivateKey wrap(PrivateKey priv, PublicKey pub) {
|
||||
Assert.notNull(priv, "PrivateKey cannot be null.");
|
||||
if (priv instanceof KeySupplier) { //already wrapped, don't wrap again
|
||||
return priv;
|
||||
}
|
||||
// We only need to wrap if:
|
||||
// 1. The private key is not already an ECKey. If it is, we can validate normally
|
||||
// 2. The public key is an ECKey - this must be true to represent EC params for the private key
|
||||
// 3. The private key indicates via its algorithm that it is intended to be used as an EC key.
|
||||
String privAlg = Strings.clean(priv.getAlgorithm());
|
||||
if (!(priv instanceof ECKey) && pub instanceof ECKey &&
|
||||
(("EC".equalsIgnoreCase(privAlg) || "ECDSA".equalsIgnoreCase(privAlg)) ||
|
||||
EcSignatureAlgorithm.findByKey(priv) != null /* match by OID just in case for PKCS11 keys */)) {
|
||||
return new PrivateECKey(priv, ((ECKey) pub).getParams());
|
||||
}
|
||||
public static SecretKeyBuilder builder(SecretKey key) {
|
||||
return new ProvidedSecretKeyBuilder(key);
|
||||
}
|
||||
|
||||
// otherwise, no need to wrap, return unchanged
|
||||
return priv;
|
||||
public static PrivateKeyBuilder builder(PrivateKey key) {
|
||||
return new ProvidedPrivateKeyBuilder(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the specified {@code key} is a {@link KeySupplier}, the 'root' (lowest level) key that may exist in
|
||||
* a {@code KeySupplier} chain is returned, otherwise the {@code key} is returned.
|
||||
*
|
||||
* @param key the key to check if it is a {@code KeySupplier}
|
||||
* @param <K> the key type
|
||||
* @return the lowest-level/root key available.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K extends Key> K root(K key) {
|
||||
return (key instanceof KeySupplier<?>) ? (K) root((KeySupplier<?>) key) : key;
|
||||
}
|
||||
|
||||
public static <K extends Key> K root(KeySupplier<K> supplier) {
|
||||
Assert.notNull(supplier, "KeySupplier canot be null.");
|
||||
return Assert.notNull(root(supplier.getKey()), "KeySupplier key cannot be null.");
|
||||
}
|
||||
|
||||
public static String findAlgorithm(Key key) {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.lang.Assert;
|
||||
import io.jsonwebtoken.security.KeyBuilder;
|
||||
|
||||
import java.security.Key;
|
||||
|
||||
abstract class ProvidedKeyBuilder<K extends Key, B extends KeyBuilder<K, B>> extends AbstractSecurityBuilder<K, B>
|
||||
implements KeyBuilder<K, B> {
|
||||
|
||||
protected final K key;
|
||||
|
||||
ProvidedKeyBuilder(K key) {
|
||||
this.key = Assert.notNull(key, "Key cannot be null.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final K build() {
|
||||
if (this.key instanceof ProviderKey) { // already wrapped, don't wrap again:
|
||||
return this.key;
|
||||
}
|
||||
return doBuild();
|
||||
}
|
||||
|
||||
abstract K doBuild();
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.lang.Strings;
|
||||
import io.jsonwebtoken.security.PrivateKeyBuilder;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
|
||||
public class ProvidedPrivateKeyBuilder extends ProvidedKeyBuilder<PrivateKey, PrivateKeyBuilder>
|
||||
implements PrivateKeyBuilder {
|
||||
|
||||
private PublicKey publicKey;
|
||||
|
||||
ProvidedPrivateKeyBuilder(PrivateKey key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKeyBuilder publicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey doBuild() {
|
||||
|
||||
PrivateKey key = this.key;
|
||||
|
||||
// We only need to wrap as an ECKey if:
|
||||
// 1. The private key is not already an ECKey. If it is, we can validate normally
|
||||
// 2. The private key indicates via its algorithm that it is intended to be used as an EC key.
|
||||
// 3. The public key is an ECKey - this must be true to represent EC params for the private key
|
||||
String privAlg = Strings.clean(this.key.getAlgorithm());
|
||||
if (!(key instanceof ECKey) && ("EC".equalsIgnoreCase(privAlg) || "ECDSA".equalsIgnoreCase(privAlg)) &&
|
||||
this.publicKey instanceof ECKey) {
|
||||
key = new PrivateECKey(key, ((ECKey) this.publicKey).getParams());
|
||||
}
|
||||
|
||||
return this.provider != null ? new ProviderPrivateKey(this.provider, key) : key;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.security.Password;
|
||||
import io.jsonwebtoken.security.SecretKeyBuilder;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
class ProvidedSecretKeyBuilder extends ProvidedKeyBuilder<SecretKey, SecretKeyBuilder> implements SecretKeyBuilder {
|
||||
|
||||
ProvidedSecretKeyBuilder(SecretKey key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey doBuild() {
|
||||
if (this.key instanceof Password) {
|
||||
return this.key; // provider never needed for Password instances.
|
||||
}
|
||||
return provider != null ? new ProviderSecretKey(this.provider, this.key) : this.key;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.lang.Assert;
|
||||
import io.jsonwebtoken.security.KeySupplier;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.Provider;
|
||||
|
||||
public class ProviderKey<T extends Key> implements Key, KeySupplier<T> {
|
||||
|
||||
private final T key;
|
||||
|
||||
private final Provider provider;
|
||||
|
||||
public static Provider getProvider(Key key, Provider backup) {
|
||||
if (key instanceof ProviderKey<?>) {
|
||||
ProviderKey<?> pkey = (ProviderKey<?>) key;
|
||||
return Assert.stateNotNull(pkey.getProvider(), "ProviderKey provider can never be null.");
|
||||
}
|
||||
return backup;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K extends Key> K getKey(K key) {
|
||||
return key instanceof ProviderKey ? ((ProviderKey<K>) key).getKey() : key;
|
||||
}
|
||||
|
||||
ProviderKey(Provider provider, T key) {
|
||||
this.provider = Assert.notNull(provider, "Provider cannot be null.");
|
||||
this.key = Assert.notNull(key, "Key argument cannot be null.");
|
||||
if (key instanceof ProviderKey<?>) {
|
||||
String msg = "Nesting not permitted.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return this.key.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return this.key.getFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return this.key.getEncoded();
|
||||
}
|
||||
|
||||
public final Provider getProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
|
||||
public final class ProviderPrivateKey extends ProviderKey<PrivateKey> implements PrivateKey {
|
||||
|
||||
ProviderPrivateKey(Provider provider, PrivateKey key) {
|
||||
super(provider, key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 javax.crypto.SecretKey;
|
||||
import java.security.Provider;
|
||||
|
||||
public final class ProviderSecretKey extends ProviderKey<SecretKey> implements SecretKey {
|
||||
|
||||
ProviderSecretKey(Provider provider, SecretKey key) {
|
||||
super(provider, key);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import io.jsonwebtoken.lang.Strings;
|
|||
import io.jsonwebtoken.security.KeyPairBuilder;
|
||||
import io.jsonwebtoken.security.SecureRequest;
|
||||
import io.jsonwebtoken.security.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.UnsupportedKeyException;
|
||||
import io.jsonwebtoken.security.VerifySecureDigestRequest;
|
||||
import io.jsonwebtoken.security.WeakKeyException;
|
||||
|
||||
|
@ -29,7 +30,6 @@ import java.security.Key;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
|
@ -57,6 +57,9 @@ final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
|
||||
private static final Set<String> PSS_ALG_NAMES = Collections.setOf(PSS_JCA_NAME, PSS_OID);
|
||||
|
||||
private static final Set<String> KEY_ALG_NAMES =
|
||||
Collections.setOf("RSA", PSS_JCA_NAME, PSS_OID, RS256_OID, RS384_OID, RS512_OID);
|
||||
|
||||
private static final int MIN_KEY_BIT_LENGTH = 2048;
|
||||
|
||||
private static AlgorithmParameterSpec pssParamSpec(int digestBitLength) {
|
||||
|
@ -153,6 +156,11 @@ final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
return PSS_ALG_NAMES.contains(alg);
|
||||
}
|
||||
|
||||
static boolean isRsaAlgorithmName(Key key) {
|
||||
String alg = KeysBridge.findAlgorithm(key);
|
||||
return KEY_ALG_NAMES.contains(alg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPairBuilder keyPair() {
|
||||
final String jcaName = this.algorithmParameterSpec != null ? PSS_JCA_NAME : "RSA";
|
||||
|
@ -170,23 +178,21 @@ final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm {
|
|||
@Override
|
||||
protected void validateKey(Key key, boolean signing) {
|
||||
super.validateKey(key, signing);
|
||||
// https://github.com/jwtk/jjwt/issues/68 :
|
||||
// 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 (key instanceof RSAKey) {
|
||||
RSAKey rsaKey = (RSAKey) key;
|
||||
int size = rsaKey.getModulus().bitLength();
|
||||
if (size < MIN_KEY_BIT_LENGTH) {
|
||||
String id = getId();
|
||||
String section = id.startsWith("PS") ? "3.5" : "3.3";
|
||||
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 " +
|
||||
section + ") states that RSA keys MUST have a size >= " + MIN_KEY_BIT_LENGTH + " 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.";
|
||||
throw new WeakKeyException(msg);
|
||||
}
|
||||
if (!isRsaAlgorithmName(key)) {
|
||||
throw new UnsupportedKeyException("Unsupported RSA or RSASSA-PSS key algorithm name.");
|
||||
}
|
||||
int size = KeysBridge.findBitLength(key);
|
||||
if (size < 0) return; // https://github.com/jwtk/jjwt/issues/68
|
||||
if (size < MIN_KEY_BIT_LENGTH) {
|
||||
String id = getId();
|
||||
String section = id.startsWith("PS") ? "3.5" : "3.3";
|
||||
String msg = "The RSA " + keyType(signing) + " key size (aka modulus bit length) is " + size + " bits " +
|
||||
"which is not secure 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. 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.";
|
||||
throw new WeakKeyException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.jsonwebtoken.impl.DefaultMutableJweHeader
|
|||
import io.jsonwebtoken.impl.lang.Bytes
|
||||
import io.jsonwebtoken.impl.lang.CheckedFunction
|
||||
import io.jsonwebtoken.lang.Arrays
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import io.jsonwebtoken.security.SecretKeyBuilder
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -89,7 +90,7 @@ class AesGcmKeyAlgorithmTest {
|
|||
def enc = new GcmAesAeadAlgorithm(keyLength) {
|
||||
@Override
|
||||
SecretKeyBuilder key() {
|
||||
return new FixedSecretKeyBuilder(cek)
|
||||
return Keys.builder(cek)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +128,7 @@ class AesGcmKeyAlgorithmTest {
|
|||
def enc = new GcmAesAeadAlgorithm(keyLength) {
|
||||
@Override
|
||||
SecretKeyBuilder key() {
|
||||
return new FixedSecretKeyBuilder(cek)
|
||||
return Keys.builder(cek)
|
||||
}
|
||||
}
|
||||
def delegate = new DefaultMutableJweHeader(headerBuilder)
|
||||
|
|
|
@ -17,8 +17,6 @@ package io.jsonwebtoken.impl.security
|
|||
|
||||
import org.junit.Test
|
||||
|
||||
import java.security.Provider
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class CryptoAlgorithmTest {
|
||||
|
@ -68,32 +66,6 @@ class CryptoAlgorithmTest {
|
|||
assertSame Randoms.secureRandom(), random
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonPkcs11ProviderNullRequest() {
|
||||
assertNull CryptoAlgorithm.nonPkcs11Provider(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonPkcs11ProviderNullRequestProvider() {
|
||||
def request = new DefaultRequest('foo', null, null)
|
||||
assertNull CryptoAlgorithm.nonPkcs11Provider(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonPkcs11ProviderEmptyRequestProviderName() {
|
||||
String name = null
|
||||
Provider provider = new TestProvider(name)
|
||||
def request = new DefaultRequest('foo', provider, null)
|
||||
assertSame provider, CryptoAlgorithm.nonPkcs11Provider(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPkcs11ProviderReturnsNull() {
|
||||
Provider provider = new TestProvider('SunPKCS11-test')
|
||||
def request = new DefaultRequest('foo', provider, null)
|
||||
assertNull CryptoAlgorithm.nonPkcs11Provider(request)
|
||||
}
|
||||
|
||||
class TestCryptoAlgorithm extends CryptoAlgorithm {
|
||||
TestCryptoAlgorithm() {
|
||||
this('test', 'jcaName')
|
||||
|
@ -103,11 +75,4 @@ class CryptoAlgorithmTest {
|
|||
super(id, jcaName)
|
||||
}
|
||||
}
|
||||
|
||||
static class TestProvider extends Provider {
|
||||
public TestProvider(String name) {
|
||||
super(name, 1.0d, 'info')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,9 +35,25 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
void testValidateNonRSAKey() {
|
||||
SecretKey key = Jwts.KEY.A128KW.key().build()
|
||||
for (DefaultRsaKeyAlgorithm alg : algs) {
|
||||
try {
|
||||
alg.validate(key, true)
|
||||
} catch (UnsupportedKeyException e) {
|
||||
assertEquals 'Unsupported RSA key algorithm name.', e.getMessage()
|
||||
}
|
||||
try {
|
||||
alg.validate(key, false)
|
||||
} catch (UnsupportedKeyException e) {
|
||||
assertEquals 'Unsupported RSA key algorithm name.', e.getMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateRsaKeyWithoutKeySize() {
|
||||
for (def alg : algs) {
|
||||
// if RSAKey interface isn't exposed (e.g. PKCS11 or HSM), don't error:
|
||||
alg.validate(key, true)
|
||||
alg.validate(key, false)
|
||||
alg.validate(new TestPublicKey(algorithm: 'RSA'), true)
|
||||
alg.validate(new TestPrivateKey(algorithm: 'RSA'), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +61,7 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
void testPssKey() {
|
||||
for (DefaultRsaKeyAlgorithm alg : algs) {
|
||||
RSAPublicKey key = createMock(RSAPublicKey)
|
||||
expect(key.getAlgorithm()).andReturn(RsaSignatureAlgorithm.PSS_JCA_NAME)
|
||||
expect(key.getAlgorithm()).andStubReturn(RsaSignatureAlgorithm.PSS_JCA_NAME)
|
||||
replay(key)
|
||||
try {
|
||||
alg.validate(key, true)
|
||||
|
@ -61,7 +77,7 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
void testPssOidKey() {
|
||||
for (DefaultRsaKeyAlgorithm alg : algs) {
|
||||
RSAPublicKey key = createMock(RSAPublicKey)
|
||||
expect(key.getAlgorithm()).andReturn(RsaSignatureAlgorithm.PSS_OID)
|
||||
expect(key.getAlgorithm()).andStubReturn(RsaSignatureAlgorithm.PSS_OID)
|
||||
replay(key)
|
||||
try {
|
||||
alg.validate(key, true)
|
||||
|
@ -77,7 +93,7 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
void testWeakEncryptionKey() {
|
||||
for (DefaultRsaKeyAlgorithm alg : algs) {
|
||||
RSAPublicKey key = createMock(RSAPublicKey)
|
||||
expect(key.getAlgorithm()).andReturn("RSA")
|
||||
expect(key.getAlgorithm()).andStubReturn("RSA")
|
||||
expect(key.getModulus()).andReturn(BigInteger.ONE)
|
||||
replay(key)
|
||||
try {
|
||||
|
@ -85,9 +101,9 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
} catch (WeakKeyException e) {
|
||||
String id = alg.getId()
|
||||
String section = id.equals("RSA1_5") ? "4.2" : "4.3"
|
||||
String msg = "The RSA encryption key's size (modulus) is 1 bits which is not secure enough for " +
|
||||
"the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) states that " +
|
||||
"RSA keys MUST have a size >= 2048 bits. " +
|
||||
String msg = "The RSA encryption key size (aka modulus bit length) is 1 bits which is not secure " +
|
||||
"enough for the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) " +
|
||||
"states that RSA keys MUST have a size >= 2048 bits. " +
|
||||
"See https://www.rfc-editor.org/rfc/rfc7518.html#section-$section for more information."
|
||||
assertEquals(msg, e.getMessage())
|
||||
}
|
||||
|
@ -99,7 +115,7 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
void testWeakDecryptionKey() {
|
||||
for (DefaultRsaKeyAlgorithm alg : algs) {
|
||||
RSAPrivateKey key = createMock(RSAPrivateKey)
|
||||
expect(key.getAlgorithm()).andReturn("RSA")
|
||||
expect(key.getAlgorithm()).andStubReturn("RSA")
|
||||
expect(key.getModulus()).andReturn(BigInteger.ONE)
|
||||
replay(key)
|
||||
try {
|
||||
|
@ -107,9 +123,9 @@ class DefaultRsaKeyAlgorithmTest {
|
|||
} catch (WeakKeyException e) {
|
||||
String id = alg.getId()
|
||||
String section = id.equals("RSA1_5") ? "4.2" : "4.3"
|
||||
String msg = "The RSA decryption key's size (modulus) is 1 bits which is not secure enough for " +
|
||||
"the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) states that " +
|
||||
"RSA keys MUST have a size >= 2048 bits. " +
|
||||
String msg = "The RSA decryption key size (aka modulus bit length) is 1 bits which is not secure " +
|
||||
"enough for the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) " +
|
||||
"states that RSA keys MUST have a size >= 2048 bits. " +
|
||||
"See https://www.rfc-editor.org/rfc/rfc7518.html#section-$section for more information."
|
||||
assertEquals(msg, e.getMessage())
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class EcSignatureAlgorithmTest {
|
|||
|
||||
@Test
|
||||
void testFindOidKeys() {
|
||||
for(def alg : EcSignatureAlgorithm.BY_OID.values()) {
|
||||
for (def alg : EcSignatureAlgorithm.BY_OID.values()) {
|
||||
String name = "${alg.getId()}_OID"
|
||||
String oid = EcSignatureAlgorithm.metaClass.getAttribute(EcSignatureAlgorithm, name) as String
|
||||
assertEquals oid, alg.OID
|
||||
|
@ -84,14 +84,24 @@ class EcSignatureAlgorithmTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testValidateKeyWithoutEcKey() {
|
||||
PublicKey key = createMock(PublicKey)
|
||||
replay key
|
||||
void testValidateKeyWithoutECOrECDSAAlgorithmName() {
|
||||
PublicKey key = new TestPublicKey(algorithm: 'foo')
|
||||
algs().each {
|
||||
it.validateKey(key, false)
|
||||
//no exception - can't check for ECKey fields (e.g. PKCS11 or HSM key)
|
||||
try {
|
||||
it.validateKey(key, false)
|
||||
} catch (Exception e) {
|
||||
String msg = 'Unsupported EC key algorithm name.'
|
||||
assertEquals msg, e.getMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateECAlgorithmKeyThatDoesntUseECKeyInterface() {
|
||||
PublicKey key = new TestPublicKey(algorithm: 'EC')
|
||||
algs().each {
|
||||
it.validateKey(key, false) //no exception - can't check for ECKey fields (e.g. PKCS11 or HSM key)
|
||||
}
|
||||
verify key
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -124,12 +134,12 @@ class EcSignatureAlgorithmTest {
|
|||
algs().each {
|
||||
BigInteger order = BigInteger.ONE
|
||||
ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1)
|
||||
ECPrivateKey priv = new TestECPrivateKey(params: spec)
|
||||
ECPrivateKey priv = new TestECPrivateKey(algorithm: 'EC', params: spec)
|
||||
def request = new DefaultSecureRequest(new byte[1], null, null, priv)
|
||||
try {
|
||||
it.digest(request)
|
||||
} catch (InvalidKeyException expected) {
|
||||
String msg = "The provided Elliptic Curve signing key's size (aka Order bit length) is " +
|
||||
String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " +
|
||||
"${Bytes.bitsMsg(order.bitLength())}, but the '${it.getId()}' algorithm requires EC Keys with " +
|
||||
"${Bytes.bitsMsg(it.orderBitLength)} per " +
|
||||
"[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." as String
|
||||
|
@ -146,7 +156,7 @@ class EcSignatureAlgorithmTest {
|
|||
try {
|
||||
Jwts.SIG.ES384.digest(req)
|
||||
} catch (InvalidKeyException expected) {
|
||||
String msg = "The provided Elliptic Curve signing key's size (aka Order bit length) is " +
|
||||
String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " +
|
||||
"256 bits (32 bytes), but the 'ES384' algorithm requires EC Keys with " +
|
||||
"384 bits (48 bytes) per " +
|
||||
"[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)."
|
||||
|
@ -178,12 +188,12 @@ class EcSignatureAlgorithmTest {
|
|||
algs().each {
|
||||
BigInteger order = BigInteger.ONE
|
||||
ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1)
|
||||
ECPublicKey pub = new TestECPublicKey(params: spec)
|
||||
ECPublicKey pub = new TestECPublicKey(algorithm: 'EC', params: spec)
|
||||
def request = new DefaultVerifySecureDigestRequest(new byte[1], null, null, pub, new byte[1])
|
||||
try {
|
||||
it.verify(request)
|
||||
} catch (InvalidKeyException expected) {
|
||||
String msg = "The provided Elliptic Curve verification key's size (aka Order bit length) is " +
|
||||
String msg = "The provided Elliptic Curve verification key size (aka order bit length) is " +
|
||||
"${Bytes.bitsMsg(order.bitLength())}, but the '${it.getId()}' algorithm requires EC Keys with " +
|
||||
"${Bytes.bitsMsg(it.orderBitLength)} per " +
|
||||
"[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." as String
|
||||
|
|
|
@ -57,11 +57,11 @@ class EdSignatureAlgorithmTest {
|
|||
* Likely when keys are from an HSM or PKCS key store
|
||||
*/
|
||||
@Test
|
||||
void testGetAlgorithmJcaNameWhenCantFindCurve() {
|
||||
def key = new TestKey(algorithm: 'foo')
|
||||
void testGetRequestJcaNameByKeyAlgorithmNameOnly() {
|
||||
def key = new TestKey(algorithm: EdwardsCurve.X25519.OID)
|
||||
def payload = [0x00] as byte[]
|
||||
def req = new DefaultSecureRequest(payload, null, null, key)
|
||||
assertEquals alg.getJcaName(), alg.getJcaName(req)
|
||||
assertEquals 'X25519', alg.getJcaName(req) // Not the EdDSA default
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -26,10 +26,7 @@ import org.junit.Test
|
|||
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.Provider
|
||||
import java.security.Security
|
||||
import java.security.*
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
@ -203,8 +200,11 @@ class Pkcs11Test {
|
|||
return findPkcs11(alg as Identifiable)?.pair
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJws() {
|
||||
/**
|
||||
* @param keyProvider the explicit provider to use with JwtBuilder/Parser calls or {@code null} to use the JVM default
|
||||
* provider(s).
|
||||
*/
|
||||
static void testJws(Provider keyProvider) {
|
||||
|
||||
def algs = [] as List<Identifiable>
|
||||
algs.addAll(Jwts.SIG.get().values().findAll({ it != Jwts.SIG.EdDSA })) // EdDSA accounted for by next two:
|
||||
|
@ -224,23 +224,34 @@ class Pkcs11Test {
|
|||
|
||||
alg = alg instanceof Curve ? Jwts.SIG.EdDSA : alg as SecureDigestAlgorithm
|
||||
|
||||
// We need to specify the PKCS11 provider since we can't access the private key material:
|
||||
def jws = Jwts.builder().provider(PKCS11).issuer('me').signWith(signKey, alg).compact()
|
||||
// We might need to specify the PKCS11 provider since we can't access the private key material:
|
||||
def jws = Jwts.builder().provider(keyProvider).issuer('me').signWith(signKey, alg).compact()
|
||||
|
||||
// We only need to specify a provider during parsing for MAC HSM keys: SignatureAlgorithm verification only
|
||||
// needs the PublicKey, and a recipient doesn't need/won't have an HSM for public material anyway.
|
||||
Provider provider = verifyKey instanceof SecretKey ? PKCS11 : null
|
||||
String iss = Jwts.parser().provider(provider).verifyWith(verifyKey).build()
|
||||
.parseClaimsJws(jws).getPayload().getIssuer()
|
||||
def builder = Jwts.parser()
|
||||
if (verifyKey instanceof SecretKey) {
|
||||
// We only need to specify a provider during parsing for MAC HSM keys: SignatureAlgorithm verification
|
||||
// only needs the PublicKey, and a recipient doesn't need/won't have an HSM for public material anyway.
|
||||
verifyKey = Keys.builder(verifyKey).provider(keyProvider).build()
|
||||
builder.verifyWith(verifyKey as SecretKey)
|
||||
} else {
|
||||
builder.verifyWith(verifyKey as PublicKey)
|
||||
}
|
||||
String iss = builder.build().parseClaimsJws(jws).getPayload().getIssuer()
|
||||
|
||||
assertEquals 'me', iss
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJws() {
|
||||
testJws(PKCS11)
|
||||
}
|
||||
|
||||
// create a jwe and then decrypt it
|
||||
static void encRoundtrip(TestKeys.Bundle bundle, def keyalg) {
|
||||
static void encRoundtrip(TestKeys.Bundle bundle, def keyalg, Provider provider /* may be null */) {
|
||||
def pair = bundle.pair
|
||||
def pub = pair.public
|
||||
def priv = pair.private
|
||||
if (pub.getAlgorithm().startsWith(EdwardsCurve.OID_PREFIX)) {
|
||||
// If < JDK 11, the PKCS11 KeyStore doesn't understand X25519 and X448 algorithms, and just returns
|
||||
// a generic X509Key from the X.509 certificate, but that can't be used for encryption. So we'll
|
||||
|
@ -251,10 +262,9 @@ class Pkcs11Test {
|
|||
def cert = new JcaTemplate("X.509", TestKeys.BC).generateX509Certificate(bundle.cert.getEncoded())
|
||||
bundle.cert = cert
|
||||
bundle.chain = [cert]
|
||||
bundle.pair = new java.security.KeyPair(cert.getPublicKey(), bundle.pair.private)
|
||||
bundle.pair = new java.security.KeyPair(cert.getPublicKey(), priv)
|
||||
pub = bundle.pair.public
|
||||
}
|
||||
def priv = pair.private != null ? Keys.wrap(pair.private, pub) : null
|
||||
|
||||
// Encryption uses the public key, and that key material is available, so no need for the PKCS11 provider:
|
||||
String jwe = Jwts.builder().issuer('me').encryptWith(pub, keyalg, Jwts.ENC.A256GCM).compact()
|
||||
|
@ -263,15 +273,15 @@ class Pkcs11Test {
|
|||
// encryption only worked because generic X.509 decoding (from the key certificate in the keystore) produced the
|
||||
// public key. So we can only decrypt if SunPKCS11 supports the private key, so check for non-null:
|
||||
if (priv) {
|
||||
// Decryption needs the private key, and that is inside the HSM, so the PKCS11 provider is required:
|
||||
String iss = Jwts.parser().provider(PKCS11).decryptWith(priv).build().parseClaimsJwe(jwe).getPayload().getIssuer()
|
||||
// Decryption may need private material inside the HSM:
|
||||
priv = Keys.builder(pair.private).publicKey(pub).provider(provider).build()
|
||||
|
||||
String iss = Jwts.parser().decryptWith(priv).build().parseClaimsJwe(jwe).getPayload().getIssuer()
|
||||
assertEquals 'me', iss
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJwe() {
|
||||
|
||||
static void testJwe(Provider provider) {
|
||||
def algs = []
|
||||
algs.addAll(Jwts.SIG.get().values().findAll({
|
||||
it.id.startsWith('RS') || it.id.startsWith('ES')
|
||||
|
@ -293,11 +303,11 @@ class Pkcs11Test {
|
|||
if (name == 'RSA') {
|
||||
// SunPKCS11 doesn't support RSA-OAEP* ciphers :(
|
||||
// So we can only try with RSA1_5 and we have to skip RSA_OAEP and RSA_OAEP_256:
|
||||
encRoundtrip(bundle, Jwts.KEY.RSA1_5)
|
||||
encRoundtrip(bundle, Jwts.KEY.RSA1_5, provider)
|
||||
} else if (StandardCurves.findByKey(bundle.pair.public) != null) { // EC or Ed key
|
||||
// try all ECDH key algorithms:
|
||||
Jwts.KEY.get().values().findAll({ it.id.startsWith('ECDH-ES') }).each {
|
||||
encRoundtrip(bundle, it)
|
||||
encRoundtrip(bundle, it, provider)
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected key algorithm: $name")
|
||||
|
@ -305,4 +315,25 @@ class Pkcs11Test {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJwe() {
|
||||
testJwe(PKCS11)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that for all JWE and JWS algorithms, when the PKCS11 provider is installed as a JVM provider,
|
||||
* no calls to JwtBuilder/Parser .provider are needed, and no ProviderKeys (Keys.builder) calls are needed
|
||||
* anywhere in application code.
|
||||
*/
|
||||
@Test
|
||||
void testPkcs11JvmProviderDoesNotRequireProviderKeys() {
|
||||
Security.addProvider(PKCS11)
|
||||
try {
|
||||
testJws(null)
|
||||
testJwe(null)
|
||||
} finally {
|
||||
Security.removeProvider(PKCS11.getName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.Bytes
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import org.junit.Test
|
||||
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.security.Provider
|
||||
|
||||
import static org.junit.Assert.assertSame
|
||||
|
||||
class ProvidedKeyBuilderTest {
|
||||
|
||||
@Test
|
||||
void testBuildWithSpecifiedProviderKey() {
|
||||
Provider provider = new TestProvider()
|
||||
SecretKey key = new SecretKeySpec(Bytes.random(256), 'AES')
|
||||
def providerKey = Keys.builder(key).provider(provider).build() as ProviderSecretKey
|
||||
|
||||
assertSame provider, providerKey.getProvider()
|
||||
assertSame key, providerKey.getKey()
|
||||
|
||||
// now for the test: ensure that our provider key isn't wrapped again
|
||||
SecretKey returned = Keys.builder(providerKey).provider(new TestProvider('different')).build()
|
||||
|
||||
assertSame providerKey, returned // not wrapped again
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.security.Keys
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertSame
|
||||
|
||||
class ProvidedSecretKeyBuilderTest {
|
||||
|
||||
@Test
|
||||
void testBuildPasswordWithoutProvider() {
|
||||
def password = Keys.password('foo'.toCharArray())
|
||||
assertSame password, Keys.builder(password).build() // does not wrap in ProviderKey
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildPasswordWithProvider() {
|
||||
def password = Keys.password('foo'.toCharArray())
|
||||
assertSame password, Keys.builder(password).provider(new TestProvider()).build() // does not wrap in ProviderKey
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.Bytes
|
||||
import org.junit.Test
|
||||
|
||||
import java.security.Provider
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertSame
|
||||
|
||||
class ProviderKeyTest {
|
||||
|
||||
static final Provider PROVIDER = new TestProvider()
|
||||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void testConstructorWithNullProvider() {
|
||||
new ProviderKey<>(null, TestKeys.HS256)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void testConstructorWithNullKey() {
|
||||
new ProviderKey<>(PROVIDER, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructorWithProviderKey() {
|
||||
def key = new ProviderKey(PROVIDER, TestKeys.HS256)
|
||||
// wrapping throws an exception:
|
||||
try {
|
||||
new ProviderKey<>(PROVIDER, key)
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = 'Nesting not permitted.'
|
||||
assertEquals msg, iae.getMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetKey() {
|
||||
def src = new TestKey()
|
||||
def key = new ProviderKey(PROVIDER, src)
|
||||
assertSame src, key.getKey()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetProvider() {
|
||||
def src = new TestKey()
|
||||
def key = new ProviderKey(PROVIDER, src)
|
||||
assertSame PROVIDER, key.getProvider()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlgorithm() {
|
||||
String name = 'myAlg'
|
||||
def key = new ProviderKey(PROVIDER, new TestKey(algorithm: name))
|
||||
assertEquals name, key.getAlgorithm()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFormat() {
|
||||
String name = 'myFormat'
|
||||
def key = new ProviderKey(PROVIDER, new TestKey(format: name))
|
||||
assertEquals name, key.getFormat()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetEncoded() {
|
||||
byte[] encoded = Bytes.random(256)
|
||||
def key = new ProviderKey(PROVIDER, new TestKey(encoded: encoded))
|
||||
assertSame encoded, key.getEncoded()
|
||||
}
|
||||
}
|
|
@ -125,7 +125,7 @@ class RFC7516AppendixA3Test {
|
|||
|
||||
@Override
|
||||
SecretKeyBuilder key() {
|
||||
return new FixedSecretKeyBuilder(CEK)
|
||||
return Keys.builder(CEK)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -285,7 +285,7 @@ class RFC7517AppendixCTest {
|
|||
def enc = new HmacAesAeadAlgorithm(128) {
|
||||
@Override
|
||||
SecretKeyBuilder key() {
|
||||
return new FixedSecretKeyBuilder(RFC_CEK)
|
||||
return Keys.builder(RFC_CEK)
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ import io.jsonwebtoken.impl.lang.Bytes
|
|||
import io.jsonwebtoken.impl.lang.CheckedFunction
|
||||
import io.jsonwebtoken.lang.Assert
|
||||
import io.jsonwebtoken.security.InvalidKeyException
|
||||
import io.jsonwebtoken.security.UnsupportedKeyException
|
||||
import io.jsonwebtoken.security.WeakKeyException
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -29,7 +30,7 @@ import java.security.PublicKey
|
|||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
||||
import static org.easymock.EasyMock.*
|
||||
import static org.easymock.EasyMock.createMock
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class RsaSignatureAlgorithmTest {
|
||||
|
@ -51,14 +52,37 @@ class RsaSignatureAlgorithmTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testValidateKeyWithoutRsaKey() {
|
||||
PublicKey key = createMock(PublicKey)
|
||||
replay key
|
||||
void testValidateKeyWithoutRSAorRSASSAPSSAlgorithmName() {
|
||||
PublicKey key = new TestPublicKey(algorithm: 'foo')
|
||||
algs.each {
|
||||
it.validateKey(key, false)
|
||||
//no exception - can't check for RSAKey fields (e.g. PKCS11 or HSM key)
|
||||
try {
|
||||
it.validateKey(key, false)
|
||||
} catch (Exception e) {
|
||||
String msg = 'Unsupported RSA or RSASSA-PSS key algorithm name.'
|
||||
assertEquals msg, e.getMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateRSAAlgorithmKeyThatDoesntUseRSAKeyInterface() {
|
||||
PublicKey key = new TestPublicKey(algorithm: 'RSA')
|
||||
algs.each {
|
||||
it.validateKey(key, false) //no exception - can't check for RSAKey length
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateKeyWithoutRsaKey() {
|
||||
PublicKey key = TestKeys.ES256.pair.public // not an RSA key
|
||||
algs.each {
|
||||
try {
|
||||
it.validateKey(key, false)
|
||||
} catch (UnsupportedKeyException e) {
|
||||
String msg = 'Unsupported RSA or RSASSA-PSS key algorithm name.'
|
||||
assertEquals msg, e.getMessage()
|
||||
}
|
||||
}
|
||||
verify key
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -99,8 +123,9 @@ class RsaSignatureAlgorithmTest {
|
|||
} 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 " +
|
||||
String msg = "The RSA signing key size (aka modulus bit length) 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."
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2021 jsonwebtoken.io
|
||||
* 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.
|
||||
|
@ -15,32 +15,15 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.security
|
||||
|
||||
import io.jsonwebtoken.security.SecretKeyBuilder
|
||||
|
||||
import javax.crypto.SecretKey
|
||||
import java.security.Provider
|
||||
import java.security.SecureRandom
|
||||
|
||||
class FixedSecretKeyBuilder implements SecretKeyBuilder {
|
||||
class TestProvider extends Provider {
|
||||
|
||||
final SecretKey key
|
||||
|
||||
FixedSecretKeyBuilder(SecretKey key) {
|
||||
this.key = key
|
||||
TestProvider() {
|
||||
this('test')
|
||||
}
|
||||
|
||||
@Override
|
||||
SecretKey build() {
|
||||
return this.key
|
||||
}
|
||||
|
||||
@Override
|
||||
SecretKeyBuilder provider(Provider provider) {
|
||||
return this
|
||||
}
|
||||
|
||||
@Override
|
||||
SecretKeyBuilder random(SecureRandom random) {
|
||||
return this
|
||||
TestProvider(String name) {
|
||||
super(name, 1.0d, 'info')
|
||||
}
|
||||
}
|
|
@ -209,7 +209,7 @@ class KeysTest {
|
|||
void testKeyPairBuilder() {
|
||||
|
||||
Collection<SignatureAlgorithm> algs = Jwts.SIG.get().values()
|
||||
.findAll({it instanceof KeyPairBuilderSupplier}) as Collection<SignatureAlgorithm>
|
||||
.findAll({ it instanceof KeyPairBuilderSupplier }) as Collection<SignatureAlgorithm>
|
||||
|
||||
for (SignatureAlgorithm alg : algs) {
|
||||
|
||||
|
@ -288,15 +288,19 @@ class KeysTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAssociateWithKeySupplier() {
|
||||
def pair = TestKeys.ES256.pair
|
||||
def key = new PrivateECKey(pair.private, pair.public.getParams())
|
||||
assertSame key, Keys.wrap(key, pair.public)
|
||||
void testAssociateWithECKey() {
|
||||
def priv = new TestPrivateKey(algorithm: 'EC')
|
||||
def pub = TestKeys.ES256.pair.public as ECPublicKey
|
||||
def result = Keys.builder(priv).publicKey(pub).build()
|
||||
assertTrue result instanceof PrivateECKey
|
||||
def key = result as PrivateECKey
|
||||
assertSame priv, key.getKey()
|
||||
assertSame pub.getParams(), key.getParams()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssociateWithKeyThatDoesntNeedToBeWrapped() {
|
||||
def pair = TestKeys.RS256.pair
|
||||
assertSame pair.private, Keys.wrap(pair.private, pair.public)
|
||||
assertSame pair.private, Keys.builder(pair.private).publicKey(pair.public).build()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue