mirror of https://github.com/jwtk/jjwt.git
New KeyOperation, KeyOperationPolicy and builder concepts (#814)
* Added new KeyOperation and KeyOperationBuilder concepts Changed Jwk#getOperations and JwkBuilder#operations from methods that accepted and returned Strings to use new KeyOperation instances Added Jwks.OP#builder() method to create a KeyOperationBuilder * Changed Jwks.OP#WRAP to WRAP_KEY and UNWRAP to UNWRAP_KEY to match RFC names * Added new KeyOperationPolicy and KeyOperationPolicyBuilder concepts Added Jwks.OP#policy() builder method to create a KeyOperationPolicyBuilder Added JwkBuilder#operationPolicy and JwkParserBuilder#operationPolicy methods for configuring custom KeyOperationPolicy instances during JWK building and parsing, respectively.
This commit is contained in:
parent
a6792d938f
commit
847ad1332c
|
@ -16,7 +16,7 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A {@link JwkBuilder} that builds asymmetric (public or private) JWKs.
|
||||
|
@ -69,7 +69,7 @@ public interface AsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>,
|
|||
*
|
||||
* <p>Per
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>,
|
||||
* the {@code use} (Public Key Use) and {@link #operations(Set) key_ops (Key Operations)} members
|
||||
* the <code>use (Public Key Use)</code> and {@link #operations(Collection) key_ops (Key Operations)} members
|
||||
* <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be
|
||||
* consistent. Applications should specify which of these members they use, if either is to be used by the
|
||||
* application.</p>
|
||||
|
|
|
@ -95,69 +95,15 @@ public interface Jwk<K extends Key> extends Identifiable, Map<String, Object> {
|
|||
String getAlgorithm();
|
||||
|
||||
/**
|
||||
* Returns the JWK
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} (Key Operations)
|
||||
* parameter</a> values or {@code null} if not present. Any values within the returned {@code Set} are
|
||||
* CaSe-SeNsItIvE.
|
||||
*
|
||||
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">defines</a> the
|
||||
* following values:</p>
|
||||
*
|
||||
* <table>
|
||||
* <caption>JWK Key Operations</caption>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Value</th>
|
||||
* <th>Operation</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td><b>{@code sign}</b></td>
|
||||
* <td>compute digital signatures or MAC</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code verify}</b></td>
|
||||
* <td>verify digital signatures or MAC</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code encrypt}</b></td>
|
||||
* <td>encrypt content</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code decrypt}</b></td>
|
||||
* <td>decrypt content and validate decryption, if applicable</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code wrapKey}</b></td>
|
||||
* <td>encrypt key</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code unwrapKey}</b></td>
|
||||
* <td>decrypt key and validate decryption, if applicable</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code deriveKey}</b></td>
|
||||
* <td>derive key</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code deriveBits}</b></td>
|
||||
* <td>derive bits not to be used as a key</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
|
||||
* recommended to use only the values above.</p>
|
||||
*
|
||||
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
|
||||
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
|
||||
* {@code sign} with {@code verify}, {@code encrypt} with {@code decrypt}, and {@code wrapKey} with
|
||||
* {@code unwrapKey} are permitted, but other combinations <em>SHOULD NOT</em> be used.</p>
|
||||
* Returns the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
|
||||
* (Key Operations) parameter</a> values or {@code null} if not present. All JWK standard Key Operations are
|
||||
* available via the {@link Jwks.OP} registry, but other (custom) values <em>MAY</em> be present in the returned
|
||||
* set.
|
||||
*
|
||||
* @return the JWK {@code key_ops} value or {@code null} if not present.
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3"><code>key_ops</code>(Key Operations) Parameter</a>
|
||||
*/
|
||||
Set<String> getOperations();
|
||||
Set<KeyOperation> getOperations();
|
||||
|
||||
/**
|
||||
* Returns the required JWK
|
||||
|
@ -188,6 +134,11 @@ public interface Jwk<K extends Key> extends Identifiable, Map<String, Object> {
|
|||
* <td><b>{@code oct}</b></td>
|
||||
* <td>Octet sequence (used to represent symmetric keys)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code OKP}</b></td>
|
||||
* <td><a href="https://www.rfc-editor.org/rfc/rfc8037#section-2">Octet Key Pair</a> (used to represent Edwards
|
||||
* Elliptic Curve keys)</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
|
|
|
@ -18,7 +18,7 @@ package io.jsonwebtoken.security;
|
|||
import io.jsonwebtoken.lang.MapMutator;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a
|
||||
|
@ -103,6 +103,47 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
|
|||
*/
|
||||
T idFromThumbprint(HashAlgorithm alg);
|
||||
|
||||
/**
|
||||
* Specifies an operation for which the key may be used by adding it to the
|
||||
* JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} (Key Operations)
|
||||
* Parameter</a> values. This method may be called multiple times.
|
||||
*
|
||||
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
|
||||
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
|
||||
* private, or symmetric keys may be present.</p>
|
||||
*
|
||||
* <p><b>Security Vulnerability Notice</b></p>
|
||||
*
|
||||
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
|
||||
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
|
||||
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
|
||||
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
|
||||
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
|
||||
* <em>SHOULD NOT</em> be used. This is enforced by the builder's key operation
|
||||
* {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
|
||||
*
|
||||
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
|
||||
*
|
||||
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
|
||||
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values
|
||||
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
|
||||
*
|
||||
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
|
||||
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying}
|
||||
* an operations policy that includes the custom values (e.g. via
|
||||
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
|
||||
*
|
||||
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
|
||||
* constants.</p>
|
||||
*
|
||||
* @param operation the value to add to the JWK {@code key_ops} value set
|
||||
* @return the builder for method chaining.
|
||||
* @throws IllegalArgumentException if {@code op} is {@code null} or if the operation is not permitted
|
||||
* by the operations {@link #operationPolicy(KeyOperationPolicy) policy}.
|
||||
* @see Jwks.OP
|
||||
*/
|
||||
T operation(KeyOperation operation) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Sets the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
|
||||
* (Key Operations) Parameter</a> values.
|
||||
|
@ -111,68 +152,63 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
|
|||
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
|
||||
* private, or symmetric keys may be present.</p>
|
||||
*
|
||||
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">defines</a> the
|
||||
* following values:</p>
|
||||
*
|
||||
* <table>
|
||||
* <caption>JWK Key Operations</caption>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Value</th>
|
||||
* <th>Operation</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td><b>{@code sign}</b></td>
|
||||
* <td>compute digital signatures or MAC</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code verify}</b></td>
|
||||
* <td>verify digital signatures or MAC</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code encrypt}</b></td>
|
||||
* <td>encrypt content</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code decrypt}</b></td>
|
||||
* <td>decrypt content and validate decryption, if applicable</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code wrapKey}</b></td>
|
||||
* <td>encrypt key</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code unwrapKey}</b></td>
|
||||
* <td>decrypt key and validate decryption, if applicable</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code deriveKey}</b></td>
|
||||
* <td>derive key</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><b>{@code deriveBits}</b></td>
|
||||
* <td>derive bits not to be used as a key</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* <p>(Note that {@code key_ops} values intentionally match the {@code KeyUsage} values defined in the
|
||||
* <a href="https://www.w3.org/TR/WebCryptoAPI/">Web Cryptography API</a> specification.)</p>
|
||||
*
|
||||
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
|
||||
* recommended to use only the values above. Each value is a CaSe-SeNsItIvE string. Use of the
|
||||
* {@code key_ops} member is <em>OPTIONAL</em>, unless the application requires its presence.</p>
|
||||
* <p><b>Security Vulnerability Notice</b></p>
|
||||
*
|
||||
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
|
||||
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
|
||||
* {@code sign} with {@code verify}, {@code encrypt} with {@code decrypt}, and {@code wrapKey} with
|
||||
* {@code unwrapKey} are permitted, but other combinations <em>SHOULD NOT</em> be used.</p>
|
||||
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
|
||||
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
|
||||
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
|
||||
* <em>SHOULD NOT</em> be used. This is enforced by the builder's default
|
||||
* operation {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
|
||||
*
|
||||
* @param ops the JWK {@code key_ops} value set.
|
||||
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
|
||||
*
|
||||
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
|
||||
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values
|
||||
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
|
||||
*
|
||||
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
|
||||
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying}
|
||||
* an operations policy that includes the custom values (e.g. via
|
||||
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
|
||||
*
|
||||
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
|
||||
* constants.</p>
|
||||
*
|
||||
* @param ops the JWK {@code key_ops} value set, or {@code null} if not present.
|
||||
* @return the builder for method chaining.
|
||||
* @throws IllegalArgumentException if {@code ops} is {@code null} or empty.
|
||||
* @throws IllegalArgumentException {@code ops} is {@code null} or empty, or if any of the operations are not
|
||||
* permitted by the operations {@link #operationPolicy(KeyOperationPolicy) policy}.
|
||||
* @see Jwks.OP
|
||||
*/
|
||||
T operations(Set<String> ops) throws IllegalArgumentException;
|
||||
T operations(Collection<KeyOperation> ops) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Sets the builder's {@link KeyOperationPolicy} that determines which key
|
||||
* {@link #operations(Collection) operations} may be assigned to the JWK. Unless overridden by this method, the
|
||||
* builder uses the default RFC-recommended policy where:
|
||||
* <ul>
|
||||
* <li>All {@link Jwks.OP RFC-standard key operations} are supported.</li>
|
||||
* <li>Multiple unrelated operations may not be assigned to the JWK per the
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">RFC 7517, Section 4.3</a> recommendation:
|
||||
* <blockquote><pre>
|
||||
* Multiple unrelated key operations SHOULD NOT be specified for a key
|
||||
* because of the potential vulnerabilities associated with using the
|
||||
* same key with multiple algorithms.
|
||||
* </pre></blockquote></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values,
|
||||
* one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the
|
||||
* {@link KeyOperationPolicy} interface directly.</p>
|
||||
*
|
||||
* @param policy the policy to apply during JWK construction
|
||||
* @return the builder for method chaining.
|
||||
* @throws IllegalArgumentException if the specified policy is null, or the policy's
|
||||
* {@link KeyOperationPolicy#getOperations() operations} collection is null or
|
||||
* empty.
|
||||
* @see Jwks.OP#policy()
|
||||
*/
|
||||
T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException;
|
||||
}
|
||||
|
|
|
@ -58,4 +58,28 @@ public interface JwkParserBuilder extends Builder<JwkParser> {
|
|||
*/
|
||||
JwkParserBuilder deserializeJsonWith(Deserializer<Map<String, ?>> deserializer);
|
||||
|
||||
/**
|
||||
* Sets the parser's key operation policy that determines which {@link KeyOperation}s may be assigned to parsed
|
||||
* JWKs. Unless overridden by this method, the parser uses the default RFC-recommended policy where:
|
||||
* <ul>
|
||||
* <li>All {@link Jwks.OP RFC-standard key operations} are supported.</li>
|
||||
* <li>Multiple unrelated operations may <b>not</b> be assigned to the JWK per the
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">RFC 7517, Section 4.3</a> recommendation:
|
||||
* <blockquote><pre>
|
||||
* Multiple unrelated key operations SHOULD NOT be specified for a key
|
||||
* because of the potential vulnerabilities associated with using the
|
||||
* same key with multiple algorithms.
|
||||
* </pre></blockquote></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values,
|
||||
* one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the
|
||||
* {@link KeyOperationPolicy} interface directly.</p>
|
||||
*
|
||||
* @param policy the policy to use to determine which {@link KeyOperation}s may be assigned to parsed JWKs.
|
||||
* @return the builder for method chaining.
|
||||
* @throws IllegalArgumentException if {@code policy} is null
|
||||
*/
|
||||
JwkParserBuilder operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException;
|
||||
|
||||
}
|
||||
|
|
|
@ -45,12 +45,13 @@ public final class Jwks {
|
|||
private static final String PARSERBUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder";
|
||||
|
||||
/**
|
||||
* Constants for all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}
|
||||
* defined by <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-7.6">RFC 7518, Section 7.6</a>
|
||||
* (for Weierstrass Elliptic Curves) and
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-5">RFC 8037, Section 5</a> (for Edwards Elliptic Curves).
|
||||
* Each standard algorithm is available as a
|
||||
* ({@code public static final}) constant for direct type-safe reference in application code. For example:
|
||||
* Constants for all standard JWK
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1">crv (Curve)</a> parameter values
|
||||
* defined in the <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-7.6">JSON Web Key Elliptic
|
||||
* Curve Registry</a> (including its
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc8037#section-5">Edwards Elliptic Curve additions</a>).
|
||||
* Each standard algorithm is available as a ({@code public static final}) constant for direct type-safe
|
||||
* reference in application code. For example:
|
||||
* <blockquote><pre>
|
||||
* Jwks.CRV.P256.keyPair().build();</pre></blockquote>
|
||||
* <p>They are also available together as a {@link Registry} instance via the {@link #get()} method.</p>
|
||||
|
@ -262,6 +263,137 @@ public final class Jwks {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for all standard JWK
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">key_ops (Key Operations)</a> parameter values
|
||||
* defined in the <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3">JSON Web Key Operations
|
||||
* Registry</a>. Each standard key operation is available as a ({@code public static final}) constant for
|
||||
* direct type-safe reference in application code. For example:
|
||||
* <blockquote><pre>
|
||||
* Jwks.builder()
|
||||
* .operations(Jwks.OP.SIGN)
|
||||
* // ... etc ...
|
||||
* .build();</pre></blockquote>
|
||||
* <p>They are also available together as a {@link Registry} instance via the {@link #get()} method.</p>
|
||||
*
|
||||
* @see #get()
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public static final class OP {
|
||||
|
||||
private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations";
|
||||
private static final Registry<String, KeyOperation> REGISTRY = Classes.newInstance(IMPL_CLASSNAME);
|
||||
|
||||
private static final String BUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder";
|
||||
|
||||
|
||||
private static final String POLICY_BUILDER_CLASSNAME =
|
||||
"io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder";
|
||||
|
||||
/**
|
||||
* Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances.
|
||||
*
|
||||
* @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances.
|
||||
*/
|
||||
public static KeyOperationBuilder builder() {
|
||||
return Classes.newInstance(BUILDER_CLASSNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances.
|
||||
*
|
||||
* @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances.
|
||||
*/
|
||||
public static KeyOperationPolicyBuilder policy() {
|
||||
return Classes.newInstance(POLICY_BUILDER_CLASSNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}
|
||||
* defined by <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3">RFC 7517, Section 8.3</a>.
|
||||
*
|
||||
* @return a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}.
|
||||
*/
|
||||
public static Registry<String, KeyOperation> get() {
|
||||
return REGISTRY;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code sign} operation indicating a key is intended to be used to compute digital signatures or
|
||||
* MACs. It's related operation is {@link #VERIFY}.
|
||||
*
|
||||
* @see #VERIFY
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation SIGN = get().forKey("sign");
|
||||
|
||||
/**
|
||||
* {@code verify} operation indicating a key is intended to be used to verify digital signatures or
|
||||
* MACs. It's related operation is {@link #SIGN}.
|
||||
*
|
||||
* @see #SIGN
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation VERIFY = get().forKey("verify");
|
||||
|
||||
/**
|
||||
* {@code encrypt} operation indicating a key is intended to be used to encrypt content. It's
|
||||
* related operation is {@link #DECRYPT}.
|
||||
*
|
||||
* @see #DECRYPT
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation ENCRYPT = get().forKey("encrypt");
|
||||
|
||||
/**
|
||||
* {@code decrypt} operation indicating a key is intended to be used to decrypt content. It's
|
||||
* related operation is {@link #ENCRYPT}.
|
||||
*
|
||||
* @see #ENCRYPT
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation DECRYPT = get().forKey("decrypt");
|
||||
|
||||
/**
|
||||
* {@code wrapKey} operation indicating a key is intended to be used to encrypt another key. It's
|
||||
* related operation is {@link #UNWRAP_KEY}.
|
||||
*
|
||||
* @see #UNWRAP_KEY
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation WRAP_KEY = get().forKey("wrapKey");
|
||||
|
||||
/**
|
||||
* {@code unwrapKey} operation indicating a key is intended to be used to decrypt another key and validate
|
||||
* decryption, if applicable. It's related operation is
|
||||
* {@link #WRAP_KEY}.
|
||||
*
|
||||
* @see #WRAP_KEY
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation UNWRAP_KEY = get().forKey("unwrapKey");
|
||||
|
||||
/**
|
||||
* {@code deriveKey} operation indicating a key is intended to be used to derive another key. It does not have
|
||||
* a related operation.
|
||||
*
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation DERIVE_KEY = get().forKey("deriveKey");
|
||||
|
||||
/**
|
||||
* {@code deriveBits} operation indicating a key is intended to be used to derive bits that are not to be
|
||||
* used as key. It does not have a related operation.
|
||||
*
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3.2">Key Operation Registry Contents</a>
|
||||
*/
|
||||
public static final KeyOperation DERIVE_BITS = get().forKey("deriveBits");
|
||||
|
||||
//prevent instantiation
|
||||
private OP() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a provided key or key pair.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 io.jsonwebtoken.Identifiable;
|
||||
|
||||
/**
|
||||
* A {@code KeyOperation} identifies a behavior for which a key may be used. Key validation
|
||||
* algorithms may inspect a key's operations and reject the key if it is being used in a manner inconsistent
|
||||
* with its indicated operations.
|
||||
*
|
||||
* <p><b>KeyOperation Identifier</b></p>
|
||||
*
|
||||
* <p>This interface extends {@link Identifiable}; the value returned from {@link #getId()} is a
|
||||
* CaSe-SeNsItIvE value that uniquely identifies the operation among other KeyOperation instances.</p>
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK key_ops (Key Operations) Parameter</a>
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7517#section-8.3">JSON Web Key Operations Registry</a>
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface KeyOperation extends Identifiable {
|
||||
|
||||
/**
|
||||
* Returns a brief description of the key operation behavior.
|
||||
*
|
||||
* @return a brief description of the key operation behavior.
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned
|
||||
* this operation, {@code false} otherwise. As described in the
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK key_ops (Key Operations) Parameter</a>
|
||||
* specification, Key validation algorithms will likely reject keys with inconsistent or unrelated operations
|
||||
* because of the security vulnerabilities that could occur otherwise.
|
||||
*
|
||||
* @param operation the key operation to check if it is related to (consistent or compatible with) this operation.
|
||||
* @return {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned
|
||||
* this operation, {@code false} otherwise.
|
||||
*/
|
||||
boolean isRelated(KeyOperation operation);
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 io.jsonwebtoken.lang.Builder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's
|
||||
* {@link JwkBuilder#operations(Collection) key operations} parameter. This is primarily only useful for creating
|
||||
* custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones
|
||||
* are available already via the {@link Jwks.OP} registry singleton.
|
||||
*
|
||||
* @see Jwks.OP#builder()
|
||||
* @see Jwks.OP#policy()
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface KeyOperationBuilder extends Builder<KeyOperation> {
|
||||
|
||||
/**
|
||||
* Sets the CaSe-SeNsItIvE {@link KeyOperation#getId() id} expected to be unique compared to all other
|
||||
* {@code KeyOperation}s.
|
||||
*
|
||||
* @param id the key operation id
|
||||
* @return the builder for method chaining
|
||||
*/
|
||||
KeyOperationBuilder id(String id);
|
||||
|
||||
/**
|
||||
* Sets the key operation {@link KeyOperation#getDescription() description}.
|
||||
*
|
||||
* @param description the key operation description
|
||||
* @return the builder for method chaining
|
||||
*/
|
||||
KeyOperationBuilder description(String description);
|
||||
|
||||
/**
|
||||
* Indicates that the {@code KeyOperation} with the given {@link KeyOperation#getId() id} is cryptographically
|
||||
* related (and complementary) to this one, and may be specified together in a JWK's
|
||||
* {@link Jwk#getOperations() operations} set.
|
||||
*
|
||||
* <p>More concretely, calling this method will ensure the following:</p>
|
||||
* <blockquote><pre>
|
||||
* KeyOperation built = Jwks.operation()/*...*/.related(otherId).build();
|
||||
* KeyOperation other = getKeyOperation(otherId);
|
||||
* assert built.isRelated(other);</pre></blockquote>
|
||||
*
|
||||
* <p>A {@link JwkBuilder}'s key operation {@link JwkBuilder#operationPolicy(KeyOperationPolicy) policy} is likely
|
||||
* to {@link KeyOperationPolicyBuilder#allowUnrelated(boolean) reject} any <em>un</em>related operations specified
|
||||
* together due to the potential security vulnerabilities that could occur.</p>
|
||||
*
|
||||
* <p>This method may be called multiple times to add/append a related {@code id} to the constructed
|
||||
* {@code KeyOperation}'s total set of related ids.</p>
|
||||
*
|
||||
* @param id the id of a KeyOperation that will be considered cryptographically related to this one.
|
||||
* @return the builder for method chaining.
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
*/
|
||||
KeyOperationBuilder related(String id);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.util.Collection;
|
||||
|
||||
/**
|
||||
* A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK.
|
||||
*
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
*/
|
||||
public interface KeyOperationPolicy {
|
||||
|
||||
/**
|
||||
* Returns all supported {@code KeyOperation}s that may be assigned to a JWK.
|
||||
*
|
||||
* @return all supported {@code KeyOperation}s that may be assigned to a JWK.
|
||||
*/
|
||||
Collection<KeyOperation> getOperations();
|
||||
|
||||
/**
|
||||
* Returns quietly if all of the specified key operations are allowed to be assigned to a JWK,
|
||||
* or throws an {@link IllegalArgumentException} otherwise.
|
||||
*
|
||||
* @param ops the operations to validate
|
||||
*/
|
||||
@SuppressWarnings("GrazieInspection")
|
||||
void validate(Collection<KeyOperation> ops) throws IllegalArgumentException;
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 io.jsonwebtoken.Identifiable;
|
||||
import io.jsonwebtoken.lang.Builder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
/**
|
||||
* A {@code KeyOperationPolicyBuilder} produces a {@link KeyOperationPolicy} that determines
|
||||
* which {@link KeyOperation}s may be assigned to a JWK. Custom {@code KeyOperation}s (such as those created by a
|
||||
* {@link Jwks.OP#builder()}) may be added to a policy via the {@link #add(KeyOperation)} or {@link #add(Collection)}
|
||||
* methods.
|
||||
*
|
||||
* @see Jwks.OP#policy()
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
* @see Jwks.OP#builder()
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
|
||||
|
||||
/**
|
||||
* Sets if a JWK is allowed to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values.
|
||||
* The default value is {@code false} per the JWK
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">RFC 7517, Section 4.3</a> recommendation:
|
||||
*
|
||||
* <blockquote><pre>
|
||||
* Multiple unrelated key operations SHOULD NOT be specified for a key
|
||||
* because of the potential vulnerabilities associated with using the
|
||||
* same key with multiple algorithms.
|
||||
* </pre></blockquote>
|
||||
*
|
||||
* <p>Only set this value to {@code true} if you fully understand the security implications of using the same key
|
||||
* with multiple algorithms in your application. Otherwise it is best not to use this builder method, or
|
||||
* explicitly set it to {@code false}.</p>
|
||||
*
|
||||
* @param allow if a JWK is allowed to have unrelated key {@link KeyOperation}s in its {@code key_ops}
|
||||
* parameter values.
|
||||
* @return the builder for method chaining
|
||||
*/
|
||||
KeyOperationPolicyBuilder allowUnrelated(boolean allow);
|
||||
|
||||
/**
|
||||
* Adds the specified key operation to the policy's total set of supported key operations
|
||||
* used to validate a key's intended usage, replacing any existing one with an identical (CaSe-SeNsItIvE)
|
||||
* {@link Identifiable#getId() id}.
|
||||
*
|
||||
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
|
||||
*
|
||||
* <p>The RFC standard {@link Jwks.OP} key operations are supported by default and do not need
|
||||
* to be added via this method, but beware: <b>If the {@code op} argument has a JWK standard
|
||||
* {@link Identifiable#getId() id}, it will replace the JJWT standard operation implementation</b>.
|
||||
* This is to allow application developers to favor their own implementations over JJWT's default implementations
|
||||
* if necessary (for example, to support legacy or custom behavior).</p>
|
||||
*
|
||||
* <p>If a custom {@code KeyOperation} is desired, one may be easily created with a {@link Jwks.OP#builder()}.</p>
|
||||
*
|
||||
* @param op a key operation to add to the policy's total set of supported operations, replacing any
|
||||
* existing one with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}.
|
||||
* @return the builder for method chaining.
|
||||
* @see Jwks.OP
|
||||
* @see Jwks.OP#builder()
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
* @see JwkBuilder#operations(Collection)
|
||||
*/
|
||||
KeyOperationPolicyBuilder add(KeyOperation op);
|
||||
|
||||
/**
|
||||
* Adds the specified key operations to the policy's total set of supported key operations
|
||||
* used to validate a key's intended usage, replacing any existing ones with identical
|
||||
* {@link Identifiable#getId() id}s.
|
||||
*
|
||||
* <p>There may be only one registered {@code KeyOperation} per CaSe-SeNsItIvE {@code id}, and the
|
||||
* {@code ops} collection is added in iteration order; if a duplicate id is found when iterating the {@code ops}
|
||||
* collection, the later operation will evict any existing operation with the same {@code id}.</p>
|
||||
*
|
||||
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
|
||||
*
|
||||
* <p>The RFC standard {@link Jwks.OP} key operations are supported by default and do not need
|
||||
* to be added via this method, but beware: <b>any operation in the {@code ops} argument with a
|
||||
* JWK standard {@link Identifiable#getId() id} will replace the JJWT standard operation implementation</b>.
|
||||
* This is to allow application developers to favor their own implementations over JJWT's default implementations
|
||||
* if necessary (for example, to support legacy or custom behavior).</p>
|
||||
*
|
||||
* <p>If custom {@code KeyOperation}s are desired, they may be easily created with a {@link Jwks.OP#builder()}.</p>
|
||||
*
|
||||
* @param ops collection of key operations to add to the policy's total set of supported operations, replacing any
|
||||
* existing ones with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}s.
|
||||
* @return the builder for method chaining.
|
||||
* @see Jwks.OP
|
||||
* @see Jwks.OP#builder()
|
||||
* @see JwkBuilder#operationPolicy(KeyOperationPolicy)
|
||||
* @see JwkBuilder#operations(Collection)
|
||||
*/
|
||||
KeyOperationPolicyBuilder add(Collection<KeyOperation> ops);
|
||||
|
||||
}
|
|
@ -15,9 +15,21 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.lang;
|
||||
|
||||
public interface Converter<A,B> {
|
||||
public interface Converter<A, B> {
|
||||
|
||||
/**
|
||||
* Converts the specified (Java idiomatic type) value to the canonical RFC-required data type.
|
||||
*
|
||||
* @param a the preferred idiomatic value
|
||||
* @return the canonical RFC-required data type value.
|
||||
*/
|
||||
B applyTo(A a);
|
||||
|
||||
/**
|
||||
* Converts the specified canonical (RFC-compliant data type) value to the preferred Java idiomatic type.
|
||||
*
|
||||
* @param b the canonical value to convert
|
||||
* @return the preferred Java idiomatic type value.
|
||||
*/
|
||||
A applyFrom(B b);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import io.jsonwebtoken.security.HashAlgorithm;
|
|||
import io.jsonwebtoken.security.Jwk;
|
||||
import io.jsonwebtoken.security.JwkThumbprint;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
|
@ -42,7 +43,9 @@ public abstract class AbstractJwk<K extends Key> implements Jwk<K>, FieldReadabl
|
|||
|
||||
static final Field<String> ALG = Fields.string("alg", "Algorithm");
|
||||
public static final Field<String> KID = Fields.string("kid", "Key ID");
|
||||
static final Field<Set<String>> KEY_OPS = Fields.stringSet("key_ops", "Key Operations");
|
||||
static final Field<Set<KeyOperation>> KEY_OPS =
|
||||
Fields.builder(KeyOperation.class).setConverter(KeyOperationConverter.DEFAULT)
|
||||
.set().setId("key_ops").setName("Key Operations").build();
|
||||
static final Field<String> KTY = Fields.string("kty", "Key Type");
|
||||
static final Set<Field<?>> FIELDS = Collections.setOf(ALG, KID, KEY_OPS, KTY);
|
||||
|
||||
|
@ -124,7 +127,7 @@ public abstract class AbstractJwk<K extends Key> implements Jwk<K>, FieldReadabl
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOperations() {
|
||||
public Set<KeyOperation> getOperations() {
|
||||
return Collections.immutable(this.context.getOperations());
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,18 @@
|
|||
package io.jsonwebtoken.impl.security;
|
||||
|
||||
import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
|
||||
import io.jsonwebtoken.impl.lang.Field;
|
||||
import io.jsonwebtoken.impl.lang.Fields;
|
||||
import io.jsonwebtoken.impl.lang.IdRegistry;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import io.jsonwebtoken.lang.Registry;
|
||||
import io.jsonwebtoken.security.HashAlgorithm;
|
||||
import io.jsonwebtoken.security.Jwk;
|
||||
import io.jsonwebtoken.security.JwkBuilder;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicy;
|
||||
import io.jsonwebtoken.security.MalformedKeyException;
|
||||
import io.jsonwebtoken.security.SecretJwk;
|
||||
import io.jsonwebtoken.security.SecretJwkBuilder;
|
||||
|
@ -29,6 +36,8 @@ import javax.crypto.SecretKey;
|
|||
import java.security.Key;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>>
|
||||
|
@ -37,6 +46,10 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
|
|||
|
||||
protected final JwkFactory<K, J> jwkFactory;
|
||||
|
||||
static final KeyOperationPolicy DEFAULT_OPERATION_POLICY = Jwks.OP.policy().build();
|
||||
|
||||
protected KeyOperationPolicy opsPolicy = DEFAULT_OPERATION_POLICY; // default
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AbstractJwkBuilder(JwkContext<K> jwkContext) {
|
||||
this(jwkContext, (JwkFactory<K, J>) DispatchingJwkFactory.DEFAULT_INSTANCE);
|
||||
|
@ -95,9 +108,39 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
|
|||
}
|
||||
|
||||
@Override
|
||||
public T operations(Set<String> ops) {
|
||||
Assert.notEmpty(ops, "Operations cannot be null or empty.");
|
||||
this.DELEGATE.setOperations(ops);
|
||||
public T operation(KeyOperation operation) throws IllegalArgumentException {
|
||||
Assert.notNull(operation, "KeyOperation cannot be null.");
|
||||
return operations(Collections.setOf(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T operations(Collection<KeyOperation> ops) {
|
||||
Assert.notEmpty(ops, "KeyOperations collection argument cannot be null or empty.");
|
||||
Set<KeyOperation> set = new LinkedHashSet<>(ops); // new ones override existing ones
|
||||
Set<KeyOperation> existing = this.DELEGATE.getOperations();
|
||||
if (!Collections.isEmpty(existing)) {
|
||||
set.addAll(existing);
|
||||
}
|
||||
this.opsPolicy.validate(set);
|
||||
this.DELEGATE.setOperations(set);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException {
|
||||
Assert.notNull(policy, "Policy cannot be null.");
|
||||
Collection<KeyOperation> ops = policy.getOperations();
|
||||
Assert.notEmpty(ops, "Policy operations cannot be null or empty.");
|
||||
this.opsPolicy = policy;
|
||||
|
||||
// update the JWK internal field to enable the policy's values:
|
||||
Registry<String, KeyOperation> registry = new IdRegistry<>("JSON Web Key Operation", ops);
|
||||
Field<Set<KeyOperation>> field = Fields.builder(KeyOperation.class)
|
||||
.setConverter(new KeyOperationConverter(registry)).set()
|
||||
.setId(AbstractJwk.KEY_OPS.getId())
|
||||
.setName(AbstractJwk.KEY_OPS.getName())
|
||||
.build();
|
||||
setDelegate(this.DELEGATE.field(field));
|
||||
return self();
|
||||
}
|
||||
|
||||
|
@ -112,7 +155,9 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
|
|||
String msg = "A " + Key.class.getName() + " or one or more name/value pairs must be provided to create a JWK.";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
this.opsPolicy.validate(this.DELEGATE.get(AbstractJwk.KEY_OPS));
|
||||
return jwkFactory.createJwk(this.DELEGATE);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
//if we get an IAE, it means the builder state wasn't configured enough in order to create
|
||||
|
|
|
@ -47,7 +47,11 @@ public class DefaultDynamicJwkBuilder<K extends Key, J extends Jwk<K>>
|
|||
extends AbstractJwkBuilder<K, J, DynamicJwkBuilder<K, J>> implements DynamicJwkBuilder<K, J> {
|
||||
|
||||
public DefaultDynamicJwkBuilder() {
|
||||
super(new DefaultJwkContext<K>());
|
||||
this(new DefaultJwkContext<K>());
|
||||
}
|
||||
|
||||
public DefaultDynamicJwkBuilder(JwkContext<K> ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,12 +21,16 @@ import io.jsonwebtoken.impl.lang.Fields;
|
|||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import io.jsonwebtoken.security.HashAlgorithm;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -118,6 +122,18 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwkContext<K> field(Field<?> field) {
|
||||
Assert.notNull(field, "Field cannot be null.");
|
||||
Map<String, Field<?>> newFields = new LinkedHashMap<>(this.FIELDS);
|
||||
newFields.remove(field.getId()); // remove old/default
|
||||
newFields.put(field.getId(), field); // add new one
|
||||
Set<Field<?>> fieldSet = new LinkedHashSet<>(newFields.values());
|
||||
return this.key != null ?
|
||||
new DefaultJwkContext<>(fieldSet, this, key) :
|
||||
new DefaultJwkContext<K>(fieldSet, this, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String value = get(AbstractJwk.KTY);
|
||||
|
@ -177,12 +193,12 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOperations() {
|
||||
public Set<KeyOperation> getOperations() {
|
||||
return get(AbstractJwk.KEY_OPS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwkContext<K> setOperations(Set<String> ops) {
|
||||
public JwkContext<K> setOperations(Collection<KeyOperation> ops) {
|
||||
put(AbstractJwk.KEY_OPS, ops);
|
||||
return this;
|
||||
}
|
||||
|
@ -216,11 +232,11 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
|
|||
if ("sig".equals(getPublicKeyUse())) {
|
||||
return true;
|
||||
}
|
||||
Set<String> ops = getOperations();
|
||||
Set<KeyOperation> ops = getOperations();
|
||||
if (Collections.isEmpty(ops)) {
|
||||
return false;
|
||||
}
|
||||
return ops.contains("sign") || ops.contains("verify");
|
||||
return ops.contains(Jwks.OP.SIGN) || ops.contains(Jwks.OP.VERIFY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.jsonwebtoken.security.JwkBuilder;
|
|||
import io.jsonwebtoken.security.JwkParser;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyException;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicy;
|
||||
import io.jsonwebtoken.security.MalformedKeyException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -34,9 +35,14 @@ public class DefaultJwkParser implements JwkParser {
|
|||
|
||||
private final Deserializer<Map<String, ?>> deserializer;
|
||||
|
||||
public DefaultJwkParser(Provider provider, Deserializer<Map<String, ?>> deserializer) {
|
||||
private final KeyOperationPolicy opsPolicy;
|
||||
|
||||
public DefaultJwkParser(Provider provider, Deserializer<Map<String, ?>> deserializer, KeyOperationPolicy policy) {
|
||||
this.provider = provider;
|
||||
this.deserializer = Assert.notNull(deserializer, "Deserializer cannot be null.");
|
||||
Assert.notNull(policy, "KeyOperationPolicy cannot be null.");
|
||||
Assert.notEmpty(policy.getOperations(), "KeyOperationPolicy's operations cannot be null or empty.");
|
||||
this.opsPolicy = policy;
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
|
@ -56,7 +62,7 @@ public class DefaultJwkParser implements JwkParser {
|
|||
throw new MalformedKeyException(msg);
|
||||
}
|
||||
|
||||
JwkBuilder<?, ?, ?> builder = Jwks.builder();
|
||||
JwkBuilder<?, ?, ?> builder = Jwks.builder().operationPolicy(this.opsPolicy);
|
||||
|
||||
if (this.provider != null) {
|
||||
builder.provider(this.provider);
|
||||
|
|
|
@ -17,18 +17,21 @@ package io.jsonwebtoken.impl.security;
|
|||
|
||||
import io.jsonwebtoken.impl.lang.Services;
|
||||
import io.jsonwebtoken.io.Deserializer;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.security.JwkParser;
|
||||
import io.jsonwebtoken.security.JwkParserBuilder;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicy;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unused") //used via reflection by Jwks.parser()
|
||||
public class DefaultJwkParserBuilder implements JwkParserBuilder {
|
||||
|
||||
private Provider provider;
|
||||
|
||||
private Deserializer<Map<String,?>> deserializer;
|
||||
private Deserializer<Map<String, ?>> deserializer;
|
||||
|
||||
private KeyOperationPolicy opsPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY;
|
||||
|
||||
@Override
|
||||
public JwkParserBuilder provider(Provider provider) {
|
||||
|
@ -42,6 +45,14 @@ public class DefaultJwkParserBuilder implements JwkParserBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwkParserBuilder operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException {
|
||||
this.opsPolicy = Assert.notNull(policy, "KeyOperationPolicy may not be null.");
|
||||
Assert.notEmpty(policy.getOperations(), "KeyOperationPolicy's operations may not be null or empty.");
|
||||
this.opsPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwkParser build() {
|
||||
if (this.deserializer == null) {
|
||||
|
@ -50,6 +61,6 @@ public class DefaultJwkParserBuilder implements JwkParserBuilder {
|
|||
this.deserializer = Services.loadFirst(Deserializer.class);
|
||||
}
|
||||
|
||||
return new DefaultJwkParser(this.provider, this.deserializer);
|
||||
return new DefaultJwkParser(this.provider, this.deserializer, this.opsPolicy);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.lang.Collections;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
final class DefaultKeyOperation implements KeyOperation {
|
||||
|
||||
private static final String CUSTOM_DESCRIPTION = "Custom key operation";
|
||||
|
||||
static final KeyOperation SIGN = of("sign", "Compute digital signature or MAC", "verify");
|
||||
static final KeyOperation VERIFY = of("verify", "Verify digital signature or MAC", "sign");
|
||||
static final KeyOperation ENCRYPT = of("encrypt", "Encrypt content", "decrypt");
|
||||
static final KeyOperation DECRYPT =
|
||||
of("decrypt", "Decrypt content and validate decryption, if applicable", "encrypt");
|
||||
static final KeyOperation WRAP = of("wrapKey", "Encrypt key", "unwrapKey");
|
||||
static final KeyOperation UNWRAP =
|
||||
of("unwrapKey", "Decrypt key and validate decryption, if applicable", "wrapKey");
|
||||
static final KeyOperation DERIVE_KEY = of("deriveKey", "Derive key", null);
|
||||
static final KeyOperation DERIVE_BITS =
|
||||
of("deriveBits", "Derive bits not to be used as a key", null);
|
||||
|
||||
final String id;
|
||||
final String description;
|
||||
final Set<String> related;
|
||||
|
||||
static KeyOperation of(String id, String description, String related) {
|
||||
return new DefaultKeyOperation(id, description, Collections.setOf(related));
|
||||
}
|
||||
|
||||
DefaultKeyOperation(String id) {
|
||||
this(id, null, null);
|
||||
}
|
||||
|
||||
DefaultKeyOperation(String id, String description, Set<String> related) {
|
||||
this.id = Assert.hasText(id, "id cannot be null or empty.");
|
||||
this.description = Strings.hasText(description) ? description : CUSTOM_DESCRIPTION;
|
||||
this.related = related != null ? Collections.immutable(related) : Collections.<String>emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelated(KeyOperation operation) {
|
||||
return equals(operation) || (operation != null && this.related.contains(operation.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this ||
|
||||
(obj instanceof KeyOperation && this.id.equals(((KeyOperation) obj).getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "'" + this.id + "' (" + this.description + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.KeyOperation;
|
||||
import io.jsonwebtoken.security.KeyOperationBuilder;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class DefaultKeyOperationBuilder implements KeyOperationBuilder {
|
||||
|
||||
private String id;
|
||||
private String description;
|
||||
private final Set<String> related = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public KeyOperationBuilder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationBuilder description(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationBuilder related(String related) {
|
||||
if (Strings.hasText(related)) {
|
||||
this.related.add(related);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperation build() {
|
||||
return new DefaultKeyOperation(this.id, this.description, this.related);
|
||||
}
|
||||
}
|
|
@ -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.lang.Collections;
|
||||
import io.jsonwebtoken.lang.Objects;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicy;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
final class DefaultKeyOperationPolicy implements KeyOperationPolicy {
|
||||
|
||||
private final Collection<KeyOperation> ops;
|
||||
|
||||
private final boolean allowUnrelated;
|
||||
|
||||
DefaultKeyOperationPolicy(Collection<KeyOperation> ops, boolean allowUnrelated) {
|
||||
Assert.notEmpty(ops, "KeyOperation collection cannot be null or empty.");
|
||||
this.ops = Collections.immutable(ops);
|
||||
this.allowUnrelated = allowUnrelated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<KeyOperation> getOperations() {
|
||||
return this.ops;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Collection<KeyOperation> ops) {
|
||||
if (allowUnrelated || Collections.isEmpty(ops)) return;
|
||||
for (KeyOperation operation : ops) {
|
||||
for (KeyOperation inner : ops) {
|
||||
if (!operation.isRelated(inner)) {
|
||||
String msg = "Unrelated key operations are not allowed. KeyOperation [" + inner +
|
||||
"] is unrelated to [" + operation + "].";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = Boolean.valueOf(this.allowUnrelated).hashCode();
|
||||
KeyOperation[] ops = this.ops.toArray(new KeyOperation[0]);
|
||||
hash = 31 * hash + Objects.nullSafeHashCode((Object[]) ops);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof DefaultKeyOperationPolicy)) {
|
||||
return false;
|
||||
}
|
||||
DefaultKeyOperationPolicy other = (DefaultKeyOperationPolicy) obj;
|
||||
return this.allowUnrelated == other.allowUnrelated &&
|
||||
Collections.size(this.ops) == Collections.size(other.ops) &&
|
||||
this.ops.containsAll(other.ops);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.lang.Collections;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicy;
|
||||
import io.jsonwebtoken.security.KeyOperationPolicyBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultKeyOperationPolicyBuilder implements KeyOperationPolicyBuilder {
|
||||
|
||||
private final Map<String, KeyOperation> ops;
|
||||
private boolean allowUnrelated = false;
|
||||
|
||||
public DefaultKeyOperationPolicyBuilder() {
|
||||
this.ops = new LinkedHashMap<>(Jwks.OP.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationPolicyBuilder allowUnrelated(boolean allow) {
|
||||
this.allowUnrelated = allow;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationPolicyBuilder add(KeyOperation op) {
|
||||
if (op != null) {
|
||||
String id = Assert.hasText(op.getId(), "KeyOperation id cannot be null or empty.");
|
||||
this.ops.remove(id);
|
||||
this.ops.put(id, op);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationPolicyBuilder add(Collection<KeyOperation> ops) {
|
||||
if (!Collections.isEmpty(ops)) {
|
||||
for (KeyOperation op : ops) {
|
||||
add(op);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperationPolicy build() {
|
||||
return new DefaultKeyOperationPolicy(Collections.immutable(this.ops.values()), this.allowUnrelated);
|
||||
}
|
||||
}
|
|
@ -17,20 +17,25 @@ package io.jsonwebtoken.impl.security;
|
|||
|
||||
import io.jsonwebtoken.Identifiable;
|
||||
import io.jsonwebtoken.impl.X509Context;
|
||||
import io.jsonwebtoken.impl.lang.Field;
|
||||
import io.jsonwebtoken.impl.lang.FieldReadable;
|
||||
import io.jsonwebtoken.impl.lang.Nameable;
|
||||
import io.jsonwebtoken.security.HashAlgorithm;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface JwkContext<K extends Key> extends Identifiable, Map<String, Object>, FieldReadable, Nameable,
|
||||
X509Context<JwkContext<K>> {
|
||||
|
||||
JwkContext<K> field(Field<?> field);
|
||||
|
||||
JwkContext<K> setId(String id);
|
||||
|
||||
JwkContext<K> setIdThumbprintAlgorithm(HashAlgorithm alg);
|
||||
|
@ -41,9 +46,9 @@ public interface JwkContext<K extends Key> extends Identifiable, Map<String, Obj
|
|||
|
||||
JwkContext<K> setType(String type);
|
||||
|
||||
Set<String> getOperations();
|
||||
Set<KeyOperation> getOperations();
|
||||
|
||||
JwkContext<K> setOperations(Set<String> operations);
|
||||
JwkContext<K> setOperations(Collection<KeyOperation> operations);
|
||||
|
||||
String getAlgorithm();
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.Converter;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Registry;
|
||||
import io.jsonwebtoken.security.Jwks;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
final class KeyOperationConverter implements Converter<KeyOperation, Object> {
|
||||
|
||||
static final Converter<KeyOperation, Object> DEFAULT = new KeyOperationConverter(Jwks.OP.get());
|
||||
|
||||
private final Registry<String, KeyOperation> registry;
|
||||
|
||||
KeyOperationConverter(Registry<String, KeyOperation> registry) {
|
||||
this.registry = Assert.notEmpty(registry, "KeyOperation registry cannot be null or empty.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applyTo(KeyOperation operation) {
|
||||
Assert.notNull(operation, "KeyOperation cannot be null.");
|
||||
return operation.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyOperation applyFrom(Object o) {
|
||||
if (o instanceof KeyOperation) {
|
||||
return (KeyOperation) o;
|
||||
}
|
||||
String id = Assert.isInstanceOf(String.class, o, "Argument must be a KeyOperation or String.");
|
||||
Assert.hasText(id, "KeyOperation string value cannot be null or empty.");
|
||||
KeyOperation keyOp = this.registry.get(id);
|
||||
return keyOp != null ? keyOp : Jwks.OP.builder().id(id).build(); // custom operations are allowed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright © 2023 jsonwebtoken.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.jsonwebtoken.impl.security;
|
||||
|
||||
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
|
||||
import io.jsonwebtoken.impl.lang.IdRegistry;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import io.jsonwebtoken.security.KeyOperation;
|
||||
|
||||
public class StandardKeyOperations extends DelegatingRegistry<String, KeyOperation> {
|
||||
|
||||
public StandardKeyOperations() {
|
||||
super(new IdRegistry<>("JSON Web Key Operation", Collections.of(
|
||||
DefaultKeyOperation.SIGN,
|
||||
DefaultKeyOperation.VERIFY,
|
||||
DefaultKeyOperation.ENCRYPT,
|
||||
DefaultKeyOperation.DECRYPT,
|
||||
DefaultKeyOperation.WRAP,
|
||||
DefaultKeyOperation.UNWRAP,
|
||||
DefaultKeyOperation.DERIVE_KEY,
|
||||
DefaultKeyOperation.DERIVE_BITS
|
||||
)));
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.security
|
||||
|
||||
|
||||
import io.jsonwebtoken.lang.Collections
|
||||
import io.jsonwebtoken.security.Jwk
|
||||
import io.jsonwebtoken.security.Jwks
|
||||
import io.jsonwebtoken.security.MalformedKeyException
|
||||
|
@ -112,34 +112,137 @@ class AbstractJwkBuilderTest {
|
|||
assertEquals kid, jwk.kid //test raw get via JWA member id
|
||||
}
|
||||
|
||||
@Test
|
||||
//ensures that even if a raw single String value is present, it is represented as a Set per the JWA spec (string array)
|
||||
void testOperationsByPutSingleStringValue() {
|
||||
def s = 'wrapKey'
|
||||
def op = Jwks.OP.get().get(s)
|
||||
def canonical = Collections.setOf(s)
|
||||
def idiomatic = Collections.setOf(op)
|
||||
def jwk = builder().add('key_ops', s).build() // <-- put uses single raw String value, not a set
|
||||
assertEquals idiomatic, jwk.getOperations() // <-- still get an idiomatic set
|
||||
assertEquals canonical, jwk.key_ops // <-- still get a canonical set
|
||||
}
|
||||
|
||||
@Test
|
||||
//ensures that even if a raw single KeyOperation value is present, it is represented as a Set per the JWA spec (string array)
|
||||
void testOperationsByPutSingleIdiomaticValue() {
|
||||
def s = 'wrapKey'
|
||||
def op = Jwks.OP.get().get(s)
|
||||
def canonical = Collections.setOf(s)
|
||||
def idiomatic = Collections.setOf(op)
|
||||
def jwk = builder().add('key_ops', op).build() // <-- put uses single raw KeyOperation value, not a set
|
||||
assertEquals idiomatic, jwk.getOperations() // <-- still get an idiomatic set
|
||||
assertEquals canonical, jwk.key_ops // <-- still get a canonical set
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperation() {
|
||||
def s = 'wrapKey'
|
||||
def op = Jwks.OP.get().get(s)
|
||||
def canonical = Collections.setOf(s)
|
||||
def idiomatic = Collections.setOf(op)
|
||||
def jwk = builder().operation(op).build()
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationCustom() {
|
||||
def s = UUID.randomUUID().toString()
|
||||
def op = Jwks.OP.builder().id(s).build()
|
||||
def canonical = Collections.setOf(s)
|
||||
def idiomatic = Collections.setOf(op)
|
||||
def jwk = builder().operation(op).build()
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationCustomOverridesDefault() {
|
||||
def s = 'sign'
|
||||
def op = Jwks.OP.builder().id(s).related('verify').build()
|
||||
def canonical = Collections.setOf(s)
|
||||
def idiomatic = Collections.setOf(op)
|
||||
def jwk = builder().operation(op).build()
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
assertSame op, jwk.getOperations().iterator().next()
|
||||
|
||||
//now assert that the standard VERIFY operation treats this as related since it has the same ID:
|
||||
canonical = Collections.setOf(s, 'verify')
|
||||
idiomatic = Collections.setOf(op, Jwks.OP.VERIFY)
|
||||
jwk = builder().operation(op).operation(Jwks.OP.VERIFY).build() as Jwk
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperations() {
|
||||
def a = UUID.randomUUID().toString()
|
||||
def b = UUID.randomUUID().toString()
|
||||
def set = [a, b] as Set<String>
|
||||
def jwk = builder().operations(set).build()
|
||||
assertEquals set, jwk.getOperations()
|
||||
assertEquals set, jwk.key_ops
|
||||
def a = 'sign'
|
||||
def b = 'verify'
|
||||
def canonical = Collections.setOf(a, b)
|
||||
def idiomatic = Collections.setOf(Jwks.OP.SIGN, Jwks.OP.VERIFY)
|
||||
def jwk = builder().operations(idiomatic).build()
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationsByPut() {
|
||||
def a = UUID.randomUUID().toString()
|
||||
def b = UUID.randomUUID().toString()
|
||||
def set = [a, b] as Set<String>
|
||||
def jwk = builder().add('key_ops', set).build()
|
||||
assertEquals set, jwk.getOperations()
|
||||
assertEquals set, jwk.key_ops
|
||||
void testOperationsUnrelated() {
|
||||
try {
|
||||
// exception thrown on setter, before calling build:
|
||||
builder().operations(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT))
|
||||
fail()
|
||||
} catch (IllegalArgumentException e) {
|
||||
String msg = 'Unrelated key operations are not allowed. KeyOperation [\'encrypt\' (Encrypt content)] is ' +
|
||||
'unrelated to [\'sign\' (Compute digital signature or MAC)].'
|
||||
assertEquals msg, e.getMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
//ensures that even if a raw single value is present it is represented as a Set per the JWA spec (string array)
|
||||
void testOperationsByPutSingleValue() {
|
||||
def a = UUID.randomUUID().toString()
|
||||
def set = [a] as Set<String>
|
||||
def jwk = builder().add('key_ops', a).build() // <-- put uses single raw value, not a set
|
||||
assertEquals set, jwk.getOperations() // <-- still get a set
|
||||
assertEquals set, jwk.key_ops // <-- still get a set
|
||||
void testOperationsPutUnrelatedStrings() {
|
||||
try {
|
||||
builder().add('key_ops', ['sign', 'encrypt']).build()
|
||||
fail()
|
||||
} catch (MalformedKeyException e) {
|
||||
String msg = 'Unable to create JWK: Unrelated key operations are not allowed. KeyOperation ' +
|
||||
'[\'encrypt\' (Encrypt content)] is unrelated to [\'sign\' (Compute digital signature or MAC)].'
|
||||
assertEquals msg, e.getMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationsByCanonicalPut() {
|
||||
def a = 'encrypt'
|
||||
def b = 'decrypt'
|
||||
def canonical = Collections.setOf(a, b)
|
||||
def idiomatic = Collections.setOf(Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT)
|
||||
def jwk = builder().add('key_ops', canonical).build() // Set of String values, not KeyOperation objects
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationsByIdiomaticPut() {
|
||||
def a = 'encrypt'
|
||||
def b = 'decrypt'
|
||||
def canonical = Collections.setOf(a, b)
|
||||
def idiomatic = Collections.setOf(Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT)
|
||||
def jwk = builder().add('key_ops', idiomatic).build() // Set of KeyOperation values, not strings
|
||||
assertEquals idiomatic, jwk.getOperations()
|
||||
assertEquals canonical, jwk.key_ops
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testCustomOperationOverridesDefault() {
|
||||
def op = Jwks.OP.builder().id('sign').description('Different Description')
|
||||
.related(Jwks.OP.VERIFY.id).build()
|
||||
def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build())
|
||||
def jwk = builder.operations(Collections.setOf(op, Jwks.OP.VERIFY)).build()
|
||||
println jwk
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,6 +262,7 @@ class AbstractJwkBuilderTest {
|
|||
JwkContext newContext(JwkContext src, Key key) {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
Jwk createJwk(JwkContext jwkContext) {
|
||||
throw new IllegalArgumentException("foo")
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
package io.jsonwebtoken.impl.security
|
||||
|
||||
import io.jsonwebtoken.impl.lang.Bytes
|
||||
import io.jsonwebtoken.impl.lang.Field
|
||||
import io.jsonwebtoken.impl.lang.Fields
|
||||
import io.jsonwebtoken.io.Encoders
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class DefaultJwkContextTest {
|
||||
|
||||
|
@ -120,4 +121,24 @@ class DefaultJwkContextTest {
|
|||
String s = '{kty=oct, k=<redacted>}'
|
||||
assertEquals "$s", "${ctx.toString()}"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFieldWithoutKey() {
|
||||
def ctx = new DefaultJwkContext(DefaultSecretJwk.FIELDS)
|
||||
Field field = Fields.string('kid', 'My Key ID')
|
||||
def newCtx = ctx.field(field)
|
||||
assertSame field, newCtx.@FIELDS.get('kid')
|
||||
assertNull newCtx.getKey()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFieldWithKey() {
|
||||
def key = TestKeys.HS256
|
||||
def ctx = new DefaultJwkContext(DefaultSecretJwk.FIELDS)
|
||||
ctx.setKey(key)
|
||||
Field field = Fields.string('kid', 'My Key ID')
|
||||
def newCtx = ctx.field(field)
|
||||
assertSame field, newCtx.@FIELDS.get('kid') // registry created with custom field instead of default
|
||||
assertSame key, newCtx.getKey() // copied over correctly
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
package io.jsonwebtoken.impl.security
|
||||
|
||||
import io.jsonwebtoken.io.Deserializer
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import io.jsonwebtoken.security.Jwks
|
||||
import io.jsonwebtoken.security.MalformedKeyException
|
||||
import org.junit.Test
|
||||
|
||||
import java.security.Provider
|
||||
|
@ -26,6 +28,22 @@ import static org.junit.Assert.*
|
|||
|
||||
class DefaultJwkParserBuilderTest {
|
||||
|
||||
// This JSON was borrowed from RFC7520Section3Test.FIGURE_2 and modified to
|
||||
// replace the 'use' member with 'key_ops` for this test:
|
||||
static String UNRELATED_OPS_JSON = Strings.trimAllWhitespace('''
|
||||
{
|
||||
"kty": "EC",
|
||||
"kid": "bilbo.baggins@hobbiton.example",
|
||||
"key_ops": ["sign", "encrypt"],
|
||||
"crv": "P-521",
|
||||
"x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9
|
||||
A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
|
||||
"y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy
|
||||
SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
|
||||
"d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb
|
||||
KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
|
||||
}''')
|
||||
|
||||
@Test
|
||||
void testDefault() {
|
||||
def builder = Jwks.parser() as DefaultJwkParserBuilder
|
||||
|
@ -50,4 +68,26 @@ class DefaultJwkParserBuilderTest {
|
|||
def parser = Jwks.parser().deserializeJsonWith(deserializer).build() as DefaultJwkParser
|
||||
assertSame deserializer, parser.deserializer
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationPolicy() {
|
||||
def parser = Jwks.parser().build() as DefaultJwkParser
|
||||
|
||||
try {
|
||||
// parse a JWK that has unrelated operations (prevented by default):
|
||||
parser.parse(UNRELATED_OPS_JSON)
|
||||
fail()
|
||||
} catch (MalformedKeyException expected) {
|
||||
String msg = "Unable to create JWK: Unrelated key operations are not allowed. KeyOperation " +
|
||||
"['encrypt' (Encrypt content)] is unrelated to ['sign' (Compute digital signature or MAC)]."
|
||||
assertEquals msg, expected.message
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperationPolicyOverride() {
|
||||
def policy = Jwks.OP.policy().allowUnrelated(true).build()
|
||||
def parser = Jwks.parser().operationPolicy(policy).build() as DefaultJwkParser
|
||||
assertNotNull parser.parse(UNRELATED_OPS_JSON) // no exception because policy allows it
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class DefaultJwkParserTest {
|
|||
|
||||
@Test
|
||||
void testDeserializationFailure() {
|
||||
def parser = new DefaultJwkParser(null, Services.loadFirst(Deserializer)) {
|
||||
def parser = new DefaultJwkParser(null, Services.loadFirst(Deserializer), AbstractJwkBuilder.DEFAULT_OPERATION_POLICY) {
|
||||
@Override
|
||||
protected Map<String, ?> deserialize(String json) {
|
||||
throw new DeserializationException("test")
|
||||
|
|
|
@ -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.security.Jwks
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class DefaultKeyOperationBuilderTest {
|
||||
|
||||
private DefaultKeyOperationBuilder builder
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
this.builder = new DefaultKeyOperationBuilder()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testId() {
|
||||
def id = 'foo'
|
||||
def op = builder.id(id).build() as DefaultKeyOperation
|
||||
assertEquals id, op.id
|
||||
assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.description
|
||||
assertFalse op.isRelated(Jwks.OP.SIGN)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDescription() {
|
||||
def id = 'foo'
|
||||
def description = 'test'
|
||||
def op = builder.id(id).description(description).build()
|
||||
assertEquals id, op.id
|
||||
assertEquals 'test', op.description
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelated() {
|
||||
def id = 'foo'
|
||||
def related = 'related'
|
||||
def opA = builder.id(id).related(related).build()
|
||||
def opB = builder.id(related).related(id).build()
|
||||
assertEquals id, opA.id
|
||||
assertEquals related, opB.id
|
||||
assertTrue opA.isRelated(opB)
|
||||
assertTrue opB.isRelated(opA)
|
||||
assertFalse opA.isRelated(Jwks.OP.SIGN)
|
||||
assertFalse opA.isRelated(Jwks.OP.SIGN)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedNull() {
|
||||
def op = builder.id('foo').related(null).build()
|
||||
assertTrue op.related.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedEmpty() {
|
||||
def op = builder.id('foo').related(' ').build()
|
||||
assertTrue op.related.isEmpty()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.Jwks
|
||||
import io.jsonwebtoken.security.KeyOperation
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class DefaultKeyOperationPolicyBuilderTest {
|
||||
|
||||
DefaultKeyOperationPolicyBuilder builder
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
builder = new DefaultKeyOperationPolicyBuilder()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefault() {
|
||||
def policy = builder.build()
|
||||
assertTrue policy.operations.containsAll(Jwks.OP.get().values())
|
||||
// unrelated operations not allowed:
|
||||
def op = Jwks.OP.builder().id('foo').build()
|
||||
try {
|
||||
policy.validate([op, Jwks.OP.SIGN])
|
||||
fail("Unrelated operations are not allowed by default.")
|
||||
} catch (IllegalArgumentException expected) {
|
||||
String msg = 'Unrelated key operations are not allowed. KeyOperation ' +
|
||||
'[\'sign\' (Compute digital signature or MAC)] is unrelated to [\'foo\' (Custom key operation)].'
|
||||
assertEquals msg, expected.getMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdd() {
|
||||
def op = Jwks.OP.builder().id('foo').build()
|
||||
def policy = builder.add(op).build()
|
||||
assertTrue policy.operations.contains(op)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNull() {
|
||||
def orig = builder.build()
|
||||
def policy = builder.add((KeyOperation) null).build()
|
||||
assertEquals orig, policy
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddCollection() {
|
||||
def foo = Jwks.OP.builder().id('foo').build()
|
||||
def bar = Jwks.OP.builder().id('bar').build()
|
||||
def policy = builder.add([foo, bar]).build()
|
||||
assertTrue policy.operations.contains(foo)
|
||||
assertTrue policy.operations.contains(bar)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNullCollection() {
|
||||
def orig = builder.build()
|
||||
def policy = builder.add((Collection<KeyOperation>) null).build()
|
||||
assertEquals orig, policy
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllowUnrelatedTrue() { // testDefault has it false as expected
|
||||
def foo = Jwks.OP.builder().id('foo').build()
|
||||
def policy = builder.allowUnrelated(true).build()
|
||||
policy.validate([foo, Jwks.OP.SIGN]) // no exception thrown since unrelated == true
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode() {
|
||||
def a = builder.add(Jwks.OP.builder().id('foo').build()).build()
|
||||
def b = builder.build()
|
||||
assertFalse a.is(b) // identity equals is different
|
||||
def ahc = a.hashCode()
|
||||
def bhc = b.hashCode()
|
||||
assertEquals ahc, bhc // still same hashcode
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
def a = builder.add(Jwks.OP.builder().id('foo').build()).build()
|
||||
def b = builder.build()
|
||||
assertFalse a.is(b) // identity equals is different
|
||||
assertEquals a, b // but still equals
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualsIdentity() {
|
||||
def policy = builder.build()
|
||||
assertEquals policy, policy
|
||||
}
|
||||
|
||||
@SuppressWarnings('ChangeToOperator')
|
||||
@Test
|
||||
void testEqualsUnexpectedType() {
|
||||
assertFalse builder.build().equals(new Object())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.Jwks
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class DefaultKeyOperationTest {
|
||||
|
||||
@Test
|
||||
void testCustom() {
|
||||
def op = new DefaultKeyOperation('foo')
|
||||
assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.getDescription()
|
||||
assertNotNull op.related
|
||||
assertTrue op.related.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnrelated() {
|
||||
assertFalse new DefaultKeyOperation('foo').isRelated(Jwks.OP.SIGN)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedNull() {
|
||||
assertFalse Jwks.OP.SIGN.isRelated(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedEquals() {
|
||||
def op = Jwks.OP.SIGN as DefaultKeyOperation
|
||||
assertTrue op.isRelated(op)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedTrue() {
|
||||
def op = Jwks.OP.SIGN as DefaultKeyOperation
|
||||
assertTrue op.isRelated(Jwks.OP.VERIFY)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRelatedFalse() {
|
||||
def op = Jwks.OP.SIGN as DefaultKeyOperation
|
||||
assertFalse op.isRelated(Jwks.OP.ENCRYPT)
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import io.jsonwebtoken.Jwts
|
|||
import io.jsonwebtoken.impl.lang.Converters
|
||||
import io.jsonwebtoken.io.Decoders
|
||||
import io.jsonwebtoken.io.Encoders
|
||||
import io.jsonwebtoken.lang.Collections
|
||||
import io.jsonwebtoken.security.*
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -143,7 +144,9 @@ class JwksTest {
|
|||
|
||||
@Test
|
||||
void testOperations() {
|
||||
testProperty('operations', 'key_ops', ['foo', 'bar'] as Set<String>)
|
||||
def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation>
|
||||
def canonical = Collections.setOf('sign', 'verify')
|
||||
testProperty('operations', 'key_ops', val, canonical)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.Jwks
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertSame
|
||||
|
||||
class KeyOperationConverterTest {
|
||||
|
||||
@Test
|
||||
void testApplyFromStandardId() {
|
||||
Jwks.OP.get().values().each {
|
||||
def id = it.id
|
||||
def op = KeyOperationConverter.DEFAULT.applyFrom(id)
|
||||
assertSame it, op
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testApplyFromCustomId() {
|
||||
def id = 'custom'
|
||||
def op = KeyOperationConverter.DEFAULT.applyFrom(id)
|
||||
assertEquals id, op.id
|
||||
assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.description
|
||||
}
|
||||
}
|
|
@ -34,5 +34,6 @@ class PrivateConstructorsTest {
|
|||
new Jwts.ZIP()
|
||||
new Jwks.CRV()
|
||||
new Jwks.HASH()
|
||||
new Jwks.OP()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ import static org.junit.Assert.*
|
|||
*/
|
||||
class SecretJwkFactoryTest {
|
||||
|
||||
@Test // if a jwk does not have an 'alg' or 'use' field, we default to an AES key
|
||||
@Test
|
||||
// if a jwk does not have an 'alg' or 'use' field, we default to an AES key
|
||||
void testNoAlgNoSigJcaName() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build()
|
||||
SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk
|
||||
|
@ -47,7 +48,7 @@ class SecretJwkFactoryTest {
|
|||
@Test
|
||||
void testSignOpSetsKeyHmacSHA256() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build()
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations(["sign"] as Set<String>).build() as SecretJwk
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
|
||||
assertNull result.getAlgorithm()
|
||||
assertNull result.get('use')
|
||||
assertEquals 'HmacSHA256', result.toKey().getAlgorithm()
|
||||
|
@ -63,7 +64,7 @@ class SecretJwkFactoryTest {
|
|||
@Test
|
||||
void testSignOpSetsKeyHmacSHA384() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).build()
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations(["sign"] as Set<String>).build() as SecretJwk
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
|
||||
assertNull result.getAlgorithm()
|
||||
assertNull result.get('use')
|
||||
assertEquals 'HmacSHA384', result.toKey().getAlgorithm()
|
||||
|
@ -79,13 +80,14 @@ class SecretJwkFactoryTest {
|
|||
@Test
|
||||
void testSignOpSetsKeyHmacSHA512() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).build()
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations(["sign"] as Set<String>).build() as SecretJwk
|
||||
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk
|
||||
assertNull result.getAlgorithm()
|
||||
assertNull result.get('use')
|
||||
assertEquals 'HmacSHA512', result.toKey().getAlgorithm()
|
||||
}
|
||||
|
||||
@Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA256
|
||||
@Test
|
||||
// no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA256
|
||||
void testNoAlgAndSigUseForHS256() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build()
|
||||
assertFalse jwk.containsKey('alg')
|
||||
|
@ -94,7 +96,8 @@ class SecretJwkFactoryTest {
|
|||
assertEquals 'HmacSHA256', result.toKey().getAlgorithm() // jcaName has been changed to a sig algorithm
|
||||
}
|
||||
|
||||
@Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA384
|
||||
@Test
|
||||
// no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA384
|
||||
void testNoAlgAndSigUseForHS384() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).build()
|
||||
assertFalse jwk.containsKey('alg')
|
||||
|
@ -103,7 +106,8 @@ class SecretJwkFactoryTest {
|
|||
assertEquals 'HmacSHA384', result.toKey().getAlgorithm()
|
||||
}
|
||||
|
||||
@Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA512
|
||||
@Test
|
||||
// no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA512
|
||||
void testNoAlgAndSigUseForHS512() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).build()
|
||||
assertFalse jwk.containsKey('alg')
|
||||
|
@ -112,7 +116,8 @@ class SecretJwkFactoryTest {
|
|||
assertEquals 'HmacSHA512', result.toKey().getAlgorithm()
|
||||
}
|
||||
|
||||
@Test // no 'alg' jwk property, but 'use' is something other than 'sig', so jcaName should default to AES
|
||||
@Test
|
||||
// no 'alg' jwk property, but 'use' is something other than 'sig', so jcaName should default to AES
|
||||
void testNoAlgAndNonSigUse() {
|
||||
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build()
|
||||
assertFalse jwk.containsKey('alg')
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.jsonwebtoken
|
||||
package io.jsonwebtoken.security
|
||||
|
||||
import io.jsonwebtoken.impl.security.ECCurve
|
||||
import io.jsonwebtoken.impl.security.EdwardsCurve
|
||||
import io.jsonwebtoken.impl.security.StandardCurves
|
||||
import io.jsonwebtoken.security.Jwks
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertSame
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 io.jsonwebtoken.impl.security.StandardKeyOperations
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class JwksOPTest {
|
||||
|
||||
@Test
|
||||
void testRegistry() {
|
||||
assertTrue Jwks.OP.get() instanceof StandardKeyOperations
|
||||
}
|
||||
|
||||
static void testInstance(KeyOperation op, String id, String description, KeyOperation related) {
|
||||
assertEquals id, op.getId()
|
||||
assertEquals description, op.getDescription()
|
||||
if (related) {
|
||||
assertTrue op.isRelated(related)
|
||||
}
|
||||
assertEquals id.hashCode(), op.hashCode()
|
||||
assertEquals "'$id' ($description)" as String, op.toString()
|
||||
assertTrue op.equals(op)
|
||||
assertTrue op.is(op)
|
||||
assertTrue op == op
|
||||
assertEquals op, Jwks.OP.get().get(id)
|
||||
assertSame op, Jwks.OP.get().get(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInstances() {
|
||||
testInstance(Jwks.OP.SIGN, 'sign', 'Compute digital signature or MAC', Jwks.OP.VERIFY)
|
||||
testInstance(Jwks.OP.VERIFY, 'verify', 'Verify digital signature or MAC', Jwks.OP.SIGN)
|
||||
testInstance(Jwks.OP.ENCRYPT, 'encrypt', 'Encrypt content', Jwks.OP.DECRYPT)
|
||||
testInstance(Jwks.OP.DECRYPT, 'decrypt', 'Decrypt content and validate decryption, if applicable', Jwks.OP.ENCRYPT)
|
||||
testInstance(Jwks.OP.WRAP_KEY, 'wrapKey', 'Encrypt key', Jwks.OP.UNWRAP_KEY)
|
||||
testInstance(Jwks.OP.UNWRAP_KEY, 'unwrapKey', 'Decrypt key and validate decryption, if applicable', Jwks.OP.WRAP_KEY)
|
||||
|
||||
testInstance(Jwks.OP.DERIVE_KEY, 'deriveKey', 'Derive key', null)
|
||||
assertFalse Jwks.OP.DERIVE_KEY.isRelated(Jwks.OP.DERIVE_BITS)
|
||||
|
||||
testInstance(Jwks.OP.DERIVE_BITS, 'deriveBits', 'Derive bits not to be used as a key', null)
|
||||
assertFalse Jwks.OP.DERIVE_BITS.isRelated(Jwks.OP.DERIVE_KEY)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue