However, a non-trivial number of JJWT users were confused by the method signature and attempted to
- * use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is
+ * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is
* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.
*
* {
JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException;
/**
+ * Deprecation Notice
+ * This has been deprecated since JJWT_RELEASE_VERSION. Use
+ * {@link #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)} instead.. Standard JWA algorithms
+ * are represented as instances of this new interface in the {@link io.jsonwebtoken.security.SignatureAlgorithms}
+ * enum class.
+ *
* Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.
*
* It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
@@ -465,9 +497,29 @@ public interface JwtBuilder extends ClaimsMutator {
* the specified algorithm.
* @see #signWith(Key)
* @since 0.10.0
+ * @deprecated since JJWT_RELEASE_VERSION to use a more the more flexible {@link io.jsonwebtoken.security.SignatureAlgorithm}.
*/
+ @Deprecated
JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException;
+ /**
+ * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.
+ *
+ * It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
+ * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if
+ * you want explicit control over the signature algorithm used with the specified key.
+ *
+ * @param key the signing key to use to digitally sign the JWT.
+ * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS.
+ * @return the builder for method chaining.
+ * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for
+ * the specified algorithm.
+ * @see #signWith(Key)
+ * @see SignatureAlgorithms#forSigningKey(Key)
+ * @since JJWT_RELEASE_VERSION
+ */
+ JwtBuilder signWith(Key key, io.jsonwebtoken.security.SignatureAlgorithm alg) throws InvalidKeyException;
+
/**
* Compresses the JWT body using the specified {@link CompressionCodec}.
*
@@ -477,7 +529,7 @@ public interface JwtBuilder extends ClaimsMutator {
*
* Compatibility Warning
*
- * The JWT family of specifications defines compression only for JWE (Json Web Encryption)
+ *
The JWT family of specifications defines compression only for JWE (JSON Web Encryption)
* tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it.
* However, be aware that if you use compression when creating a JWS token, other libraries may not be able to
* parse that JWS token. When using compression for JWS tokens, be sure that all parties accessing the
diff --git a/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java b/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
index 2e8dd8794..0e9d85ae9 100644
--- a/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
+++ b/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
@@ -19,6 +19,8 @@
import io.jsonwebtoken.io.Deserializer;
import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
import java.util.Date;
import java.util.Map;
@@ -35,6 +37,17 @@
*/
public interface JwtParserBuilder {
+ /**
+ * Sets the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} if the
+ * JCA subsystem preferred provider should be used.
+ *
+ * @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null}
+ * if the JCA subsystem preferred provider should be used.
+ * @return the builder for method chaining.
+ * @since JJWT_RELEASE_VERSION
+ */
+ JwtParserBuilder setProvider(Provider provider);
+
/**
* Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
diff --git a/api/src/main/java/io/jsonwebtoken/Jwts.java b/api/src/main/java/io/jsonwebtoken/Jwts.java
index 80de65a44..d9f62db44 100644
--- a/api/src/main/java/io/jsonwebtoken/Jwts.java
+++ b/api/src/main/java/io/jsonwebtoken/Jwts.java
@@ -76,6 +76,30 @@ public static JwsHeader jwsHeader(Map header) {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader", MAP_ARG, header);
}
+ /**
+ * Returns a new {@link JweHeader} instance suitable for encrypted JWTs (aka 'JWE's).
+ *
+ * @return a new {@link JweHeader} instance suitable for encrypted JWTs (aka 'JWE's).
+ * @see JwtBuilder#setHeader(Header)
+ * @since JJWT_RELEASE_VERSION
+ */
+ public static JweHeader jweHeader() {
+ return Classes.newInstance("io.jsonwebtoken.impl.DefaultJweHeader");
+ }
+
+ /**
+ * Returns a new {@link JweHeader} instance suitable for encrypted JWTs (aka 'JWE's), populated with the
+ * specified name/value pairs.
+ *
+ * @return a new {@link JweHeader} instance suitable for encrypted JWTs (aka 'JWE's), populated with the
+ * specified name/value pairs.
+ * @see JwtBuilder#setHeader(Header)
+ * @since JJWT_RELEASE_VERSION
+ */
+ public static JweHeader jweHeader(Map header) {
+ return Classes.newInstance("io.jsonwebtoken.impl.DefaultJweHeader", MAP_ARG, header);
+ }
+
/**
* Returns a new {@link Claims} instance to be used as a JWT body.
*
diff --git a/api/src/main/java/io/jsonwebtoken/Named.java b/api/src/main/java/io/jsonwebtoken/Named.java
new file mode 100644
index 000000000..0bd6064ce
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/Named.java
@@ -0,0 +1,14 @@
+package io.jsonwebtoken;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface Named {
+
+ /**
+ * Returns the string name of the associated object.
+ *
+ * @return the string name of the associated object.
+ */
+ String getName();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/RequiredTypeException.java b/api/src/main/java/io/jsonwebtoken/RequiredTypeException.java
index eeb60d308..44124615d 100644
--- a/api/src/main/java/io/jsonwebtoken/RequiredTypeException.java
+++ b/api/src/main/java/io/jsonwebtoken/RequiredTypeException.java
@@ -16,12 +16,13 @@
package io.jsonwebtoken;
/**
- * Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the
- * {@code Class} argument.
+ * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the
+ * expected type.
*
* @since 0.6
*/
public class RequiredTypeException extends JwtException {
+
public RequiredTypeException(String message) {
super(message);
}
diff --git a/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java b/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
index 9f646502d..1bdafddaf 100644
--- a/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
+++ b/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
@@ -34,7 +34,9 @@
* JSON Web Algorithms specification.
*
* @since 0.1
+ * @deprecated since JJWT_RELEASE_VERSION; use {@link io.jsonwebtoken.security.SignatureAlgorithms} instead.
*/
+@Deprecated
public enum SignatureAlgorithm {
/**
diff --git a/api/src/main/java/io/jsonwebtoken/SignatureException.java b/api/src/main/java/io/jsonwebtoken/SignatureException.java
index e98b4c722..12a2e92d7 100644
--- a/api/src/main/java/io/jsonwebtoken/SignatureException.java
+++ b/api/src/main/java/io/jsonwebtoken/SignatureException.java
@@ -15,7 +15,7 @@
*/
package io.jsonwebtoken;
-import io.jsonwebtoken.security.SecurityException;
+import io.jsonwebtoken.security.CryptoException;
/**
* Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed.
@@ -24,7 +24,7 @@
* @deprecated in favor of {@link io.jsonwebtoken.security.SecurityException}; this class will be removed before 1.0
*/
@Deprecated
-public class SignatureException extends SecurityException {
+public class SignatureException extends CryptoException {
public SignatureException(String message) {
super(message);
diff --git a/api/src/main/java/io/jsonwebtoken/lang/Assert.java b/api/src/main/java/io/jsonwebtoken/lang/Assert.java
index 7c9e7c367..ab6dd2fd4 100644
--- a/api/src/main/java/io/jsonwebtoken/lang/Assert.java
+++ b/api/src/main/java/io/jsonwebtoken/lang/Assert.java
@@ -77,10 +77,11 @@ public static void isNull(Object object) {
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is null
*/
- public static void notNull(Object object, String message) {
+ public static T notNull(T object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
+ return object;
}
/**
@@ -196,10 +197,11 @@ public static void notEmpty(Object[] array) {
notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
}
- public static void notEmpty(byte[] array, String msg) {
+ public static byte[] notEmpty(byte[] array, String msg) {
if (Objects.isEmpty(array)) {
throw new IllegalArgumentException(msg);
}
+ return array;
}
/**
diff --git a/api/src/main/java/io/jsonwebtoken/lang/Collections.java b/api/src/main/java/io/jsonwebtoken/lang/Collections.java
index d775a002a..167f23f2b 100644
--- a/api/src/main/java/io/jsonwebtoken/lang/Collections.java
+++ b/api/src/main/java/io/jsonwebtoken/lang/Collections.java
@@ -28,6 +28,17 @@ public final class Collections {
private Collections(){} //prevent instantiation
+ public static List emptyList() {
+ return java.util.Collections.emptyList();
+ }
+
+ public static List of(T... elements) {
+ if (elements == null || elements.length == 0) {
+ return java.util.Collections.emptyList();
+ }
+ return java.util.Collections.unmodifiableList(Arrays.asList(elements));
+ }
+
/**
* Return true
if the supplied Collection is null
* or empty. Otherwise, return false
.
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadDecryptionRequest.java b/api/src/main/java/io/jsonwebtoken/security/AeadDecryptionRequest.java
new file mode 100644
index 000000000..4f9fa8261
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadDecryptionRequest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadDecryptionRequest extends AeadRequest, AuthenticationTagSource {
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionAlgorithm.java
new file mode 100644
index 000000000..f903377f1
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionAlgorithm.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadEncryptionAlgorithm, ERes extends AeadEncryptionResult, DReq extends AeadDecryptionRequest> extends EncryptionAlgorithm {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionResult.java b/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionResult.java
new file mode 100644
index 000000000..292810620
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadEncryptionResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadEncryptionResult extends EncryptionResult, AuthenticationTagSource {
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadIvEncryptionResult.java b/api/src/main/java/io/jsonwebtoken/security/AeadIvEncryptionResult.java
new file mode 100644
index 000000000..8524f1d57
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadIvEncryptionResult.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadIvEncryptionResult extends IvEncryptionResult, AeadEncryptionResult {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadIvRequest.java b/api/src/main/java/io/jsonwebtoken/security/AeadIvRequest.java
new file mode 100644
index 000000000..05159ef23
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadIvRequest.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadIvRequest extends IvRequest, AeadDecryptionRequest {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java b/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java
new file mode 100644
index 000000000..8112813fe
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadRequest extends CryptoRequest, AssociatedDataSource {
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadSymmetricEncryptionAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/AeadSymmetricEncryptionAlgorithm.java
new file mode 100644
index 000000000..4e74987f7
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AeadSymmetricEncryptionAlgorithm.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+import javax.crypto.SecretKey;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AeadSymmetricEncryptionAlgorithm extends
+ SymmetricEncryptionAlgorithm, AeadIvEncryptionResult, AeadIvRequest>,
+ AeadEncryptionAlgorithm, AeadIvEncryptionResult, AeadIvRequest> {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSource.java b/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSource.java
new file mode 100644
index 000000000..0acd5555d
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSource.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AssociatedDataSource {
+
+ byte[] getAssociatedData();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeyAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeyAlgorithm.java
new file mode 100644
index 000000000..84624225f
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeyAlgorithm.java
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.security;
+
+import java.security.KeyPair;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AsymmetricKeyAlgorithm {
+
+ /**
+ * Generates a new secure-random key pair with a key length suitable for this Algorithm.
+ *
+ * @return a new secure-random key pair with a key length suitable for this Algorithm.
+ */
+ KeyPair generateKeyPair();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeySignatureAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeySignatureAlgorithm.java
new file mode 100644
index 000000000..93279d698
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AsymmetricKeySignatureAlgorithm.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AsymmetricKeySignatureAlgorithm extends SignatureAlgorithm, AsymmetricKeyAlgorithm {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/AuthenticationTagSource.java b/api/src/main/java/io/jsonwebtoken/security/AuthenticationTagSource.java
new file mode 100644
index 000000000..b7e1f55ec
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/AuthenticationTagSource.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface AuthenticationTagSource {
+
+ byte[] getAuthenticationTag();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/CryptoException.java b/api/src/main/java/io/jsonwebtoken/security/CryptoException.java
new file mode 100644
index 000000000..6d3b26fb3
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/CryptoException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class CryptoException extends SecurityException {
+
+ public CryptoException(String message) {
+ super(message);
+ }
+
+ public CryptoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/CryptoMessage.java b/api/src/main/java/io/jsonwebtoken/security/CryptoMessage.java
new file mode 100644
index 000000000..9d904c471
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/CryptoMessage.java
@@ -0,0 +1,10 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface CryptoMessage {
+
+ T getData(); //plaintext, ciphertext, or Key for key wrap algorithms
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/CryptoRequest.java b/api/src/main/java/io/jsonwebtoken/security/CryptoRequest.java
new file mode 100644
index 000000000..489dfe01b
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/CryptoRequest.java
@@ -0,0 +1,36 @@
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface CryptoRequest extends CryptoMessage {
+
+ /**
+ * Returns the JCA provider that should be used for cryptographic operations during the request or
+ * {@code null} if the JCA subsystem preferred provider should be used.
+ *
+ * @return the JCA provider that should be used for cryptographic operations during the request or
+ * {@code null} if the JCA subsystem preferred provider should be used.
+ */
+ Provider getProvider();
+
+ /**
+ * Returns the {@code SecureRandom} to use when performing cryptographic operations during the request, or
+ * {@code null} if a default {@link SecureRandom} should be used.
+ *
+ * @return the {@code SecureRandom} to use when performing cryptographic operations during the request, or
+ * {@code null} if a default {@link SecureRandom} should be used.
+ */
+ SecureRandom getSecureRandom();
+
+ /**
+ * Returns the key to use for signing, wrapping, encryption or decryption depending on the type of request.
+ *
+ * @return the key to use for signing, wrapping, encryption or decryption depending on the type of request.
+ */
+ K getKey();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/CurveId.java b/api/src/main/java/io/jsonwebtoken/security/CurveId.java
new file mode 100644
index 000000000..415783359
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/CurveId.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface CurveId {
+
+ String toString();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/CurveIds.java b/api/src/main/java/io/jsonwebtoken/security/CurveIds.java
new file mode 100644
index 000000000..fffefe506
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/CurveIds.java
@@ -0,0 +1,44 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Maps;
+import io.jsonwebtoken.lang.Strings;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class CurveIds {
+
+ public static final CurveId P256 = new DefaultCurveId("P-256");
+ public static final CurveId P384 = new DefaultCurveId("P-384");
+ public static final CurveId P521 = new DefaultCurveId("P-521"); // yes, this is supposed to be 521 and not 512
+
+ private static final Map STANDARD_IDS = Collections.unmodifiableMap(Maps
+ .of(P256.toString(), P256)
+ .and(P384.toString(), P384)
+ .and(P521.toString(), P521)
+ .build());
+
+ private static final Set STANDARD_IDS_SET =
+ Collections.unmodifiableSet(new LinkedHashSet<>(STANDARD_IDS.values()));
+
+ public static Set values() {
+ return STANDARD_IDS_SET;
+ }
+
+ public static boolean isStandard(CurveId curveId) {
+ return curveId != null && STANDARD_IDS.containsKey(curveId.toString());
+ }
+
+ public static CurveId forValue(String value) {
+ value = Strings.clean(value);
+ Assert.hasText(value, "value argument cannot be null or empty.");
+ CurveId std = STANDARD_IDS.get(value);
+ return std != null ? std : new DefaultCurveId(value);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyResolver.java b/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyResolver.java
new file mode 100644
index 000000000..9e2cbce78
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyResolver.java
@@ -0,0 +1,19 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.JweHeader;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface DecryptionKeyResolver {
+
+ /**
+ * Returns the decryption key that should be used to decrypt a corresponding JWE's Ciphertext (payload).
+ *
+ * @param header the JWE header to inspect to determine which decryption key should be used
+ * @return the decryption key that should be used to decrypt a corresponding JWE's Ciphertext (payload).
+ */
+ Key resolveDecryptionKey(JweHeader header);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/DefaultCurveId.java b/api/src/main/java/io/jsonwebtoken/security/DefaultCurveId.java
new file mode 100644
index 000000000..479f768fb
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/DefaultCurveId.java
@@ -0,0 +1,33 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Strings;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+final class DefaultCurveId implements CurveId {
+
+ private final String id;
+
+ DefaultCurveId(String id) {
+ id = Strings.clean(id);
+ Assert.hasText(id, "id argument cannot be null or empty.");
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || (obj instanceof CurveId && obj.toString().equals(this.id));
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EcJwk.java b/api/src/main/java/io/jsonwebtoken/security/EcJwk.java
new file mode 100644
index 000000000..eba5b1045
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EcJwk.java
@@ -0,0 +1,13 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EcJwk extends Jwk, EcJwkMutator {
+
+ CurveId getCurveId();
+
+ String getX();
+
+ String getY();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilder.java b/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilder.java
new file mode 100644
index 000000000..c0ae666f8
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilder.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EcJwkBuilder extends JwkBuilder, EcJwkMutator {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilderFactory.java b/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilderFactory.java
new file mode 100644
index 000000000..f6666e02f
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EcJwkBuilderFactory.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EcJwkBuilderFactory {
+
+ PublicEcJwkBuilder publicKey();
+
+ PrivateEcJwkBuilder privateKey();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EcJwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/EcJwkMutator.java
new file mode 100644
index 000000000..fad155ad9
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EcJwkMutator.java
@@ -0,0 +1,13 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EcJwkMutator extends JwkMutator {
+
+ T setCurveId(CurveId curveId);
+
+ T setX(String x);
+
+ T setY(String y);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithm.java
new file mode 100644
index 000000000..a3ba789c2
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithm.java
@@ -0,0 +1,15 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.Named;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EncryptionAlgorithm, ERes extends EncryptionResult, DReq extends CryptoRequest> extends Named {
+
+ ERes encrypt(EReq request) throws CryptoException, KeyException;
+
+ byte[] decrypt(DReq request) throws CryptoException, KeyException;
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmLocator.java b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmLocator.java
new file mode 100644
index 000000000..ec44fd8b9
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmLocator.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.JweHeader;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EncryptionAlgorithmLocator {
+
+ EncryptionAlgorithm getEncryptionAlgorithm(JweHeader jweHeader);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmName.java b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmName.java
new file mode 100644
index 000000000..9b482d3bf
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithmName.java
@@ -0,0 +1,75 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public enum EncryptionAlgorithmName {
+
+ A128CBC_HS256("A128CBC-HS256", "AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.3", "AES/CBC/PKCS5Padding"),
+ A192CBC_HS384("A192CBC-HS384", "AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.4", "AES/CBC/PKCS5Padding"),
+ A256CBC_HS512("A256CBC-HS512", "AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.5", "AES/CBC/PKCS5Padding"),
+ A128GCM("A128GCM", "AES GCM using 128-bit key", "AES/GCM/NoPadding"),
+ A192GCM("A192GCM", "AES GCM using 192-bit key", "AES/GCM/NoPadding"),
+ A256GCM("A256GCM", "AES GCM using 256-bit key", "AES/GCM/NoPadding");
+
+ private final String name;
+ private final String description;
+ private final String jcaName;
+
+ EncryptionAlgorithmName(String name, String description, String jcaName) {
+ this.name = name;
+ this.description = description;
+ this.jcaName = jcaName;
+ }
+
+ /**
+ * Returns the JWA algorithm name constant.
+ *
+ * @return the JWA algorithm name constant.
+ */
+ public String getValue() {
+ return name;
+ }
+
+ /**
+ * Returns the JWA algorithm description.
+ *
+ * @return the JWA algorithm description.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns the name of the JCA algorithm used to encrypt or decrypt JWE content.
+ *
+ * @return the name of the JCA algorithm used to encrypt or decrypt JWE content.
+ */
+ public String getJcaName() {
+ return jcaName;
+ }
+
+ /**
+ * Returns the corresponding {@code EncryptionAlgorithmName} enum instance based on a
+ * case-insensitive name comparison of the specified JWE enc
value.
+ *
+ * @param name the case-insensitive JWE enc
header value.
+ * @return Returns the corresponding {@code EncryptionAlgorithmName} enum instance based on a
+ * case-insensitive name comparison of the specified JWE enc
value.
+ * @throws IllegalArgumentException if the specified value does not match any JWE {@code EncryptionAlgorithmName} value.
+ */
+ public static EncryptionAlgorithmName forName(String name) throws IllegalArgumentException {
+ for (EncryptionAlgorithmName enc : values()) {
+ if (enc.getValue().equalsIgnoreCase(name)) {
+ return enc;
+ }
+ }
+
+ throw new IllegalArgumentException("Unsupported JWE Content Encryption Algorithm name: " + name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithms.java b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithms.java
new file mode 100644
index 000000000..dbaeecb58
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EncryptionAlgorithms.java
@@ -0,0 +1,105 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Classes;
+import io.jsonwebtoken.lang.Maps;
+import io.jsonwebtoken.lang.Strings;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public final class EncryptionAlgorithms {
+
+ //prevent instantiation
+ private EncryptionAlgorithms() {
+ }
+
+ private static final Class MAC_CLASS = Classes.forName("io.jsonwebtoken.impl.security.MacSignatureAlgorithm");
+ private static final String HMAC = "io.jsonwebtoken.impl.security.HmacAesEncryptionAlgorithm";
+ private static final Class[] HMAC_ARGS = new Class[]{String.class, MAC_CLASS};
+
+ private static final String GCM = "io.jsonwebtoken.impl.security.GcmAesEncryptionAlgorithm";
+ private static final Class[] GCM_ARGS = new Class[]{String.class, int.class};
+
+ private static AeadSymmetricEncryptionAlgorithm hmac(int keyLength) {
+ int digestLength = keyLength * 2;
+ String name = "A" + keyLength + "CBC-HS" + digestLength;
+ SignatureAlgorithm macSigAlg = Classes.newInstance(SignatureAlgorithms.HMAC, SignatureAlgorithms.HMAC_ARGS, name, "HmacSHA" + digestLength, keyLength);
+ return Classes.newInstance(HMAC, HMAC_ARGS, name, macSigAlg);
+ }
+
+ private static AeadSymmetricEncryptionAlgorithm gcm(int keyLength) {
+ String name = "A" + keyLength + "GCM";
+ return Classes.newInstance(GCM, GCM_ARGS, name, keyLength);
+ }
+
+ /**
+ * AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm, as defined by
+ * RFC 7518, Section 5.2.3. This algorithm
+ * requires a 256 bit (32 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A128CBC_HS256 = hmac(128);
+
+ /**
+ * AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, as defined by
+ * RFC 7518, Section 5.2.4. This algorithm
+ * requires a 384 bit (48 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A192CBC_HS384 = hmac(192);
+
+ /**
+ * AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, as defined by
+ * RFC 7518, Section 5.2.5. This algorithm
+ * requires a 512 bit (64 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A256CBC_HS512 = hmac(256);
+
+ /**
+ * "AES GCM using 128-bit key" as defined by
+ * RFC 7518, Section 5.3. This algorithm requires
+ * a 128 bit (16 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A128GCM = gcm(128);
+
+ /**
+ * "AES GCM using 192-bit key" as defined by
+ * RFC 7518, Section 5.3. This algorithm requires
+ * a 192 bit (24 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A192GCM = gcm(192);
+
+ /**
+ * "AES GCM using 256-bit key" as defined by
+ * RFC 7518, Section 5.3. This algorithm requires
+ * a 256 bit (32 byte) key.
+ */
+ public static final AeadSymmetricEncryptionAlgorithm A256GCM = gcm(256);
+
+ private static final Map SYMMETRIC_VALUES_BY_NAME = Collections.unmodifiableMap(Maps
+ .of(A128CBC_HS256.getName(), A128CBC_HS256)
+ .and(A192CBC_HS384.getName(), A192CBC_HS384)
+ .and(A256CBC_HS512.getName(), A256CBC_HS512)
+ .and(A128GCM.getName(), A128GCM)
+ .and(A192GCM.getName(), A192GCM)
+ .and(A256GCM.getName(), A256GCM)
+ .build());
+
+ public static EncryptionAlgorithm forName(String name) {
+ Assert.hasText(name, "name cannot be null or empty.");
+ EncryptionAlgorithm alg = SYMMETRIC_VALUES_BY_NAME.get(name.toUpperCase());
+ if (alg == null) {
+ String msg = "'" + name + "' is not a JWE specification standard name. The standard names are: " +
+ Strings.collectionToCommaDelimitedString(SYMMETRIC_VALUES_BY_NAME.keySet());
+ throw new IllegalArgumentException(msg);
+ }
+ return alg;
+ }
+
+ public static Collection symmetric() {
+ return SYMMETRIC_VALUES_BY_NAME.values();
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/EncryptionResult.java b/api/src/main/java/io/jsonwebtoken/security/EncryptionResult.java
new file mode 100644
index 000000000..62b76f6ca
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/EncryptionResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface EncryptionResult {
+
+ byte[] getCiphertext();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/InitializationVectorSource.java b/api/src/main/java/io/jsonwebtoken/security/InitializationVectorSource.java
new file mode 100644
index 000000000..47f1032ed
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/InitializationVectorSource.java
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface InitializationVectorSource {
+
+ /**
+ * Returns the secure-random initialization vector used during encryption that must be presented in order
+ * to decrypt.
+ *
+ * @return the secure-random initialization vector used during encryption that must be presented in order
+ * to decrypt.
+ */
+ byte[] getInitializationVector();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java b/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
index 2e3b84b8a..5d6b4183d 100644
--- a/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
+++ b/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
@@ -23,4 +23,8 @@ public class InvalidKeyException extends KeyException {
public InvalidKeyException(String message) {
super(message);
}
+
+ public InvalidKeyException(String msg, Exception cause) {
+ super(msg, cause);
+ }
}
diff --git a/api/src/main/java/io/jsonwebtoken/security/IvEncryptionResult.java b/api/src/main/java/io/jsonwebtoken/security/IvEncryptionResult.java
new file mode 100644
index 000000000..ac75f30fb
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/IvEncryptionResult.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface IvEncryptionResult extends EncryptionResult, InitializationVectorSource {
+
+ byte[] compact();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/IvRequest.java b/api/src/main/java/io/jsonwebtoken/security/IvRequest.java
new file mode 100644
index 000000000..e4dfbbcc1
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/IvRequest.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface IvRequest extends CryptoRequest, InitializationVectorSource {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/Jwk.java b/api/src/main/java/io/jsonwebtoken/security/Jwk.java
new file mode 100644
index 000000000..f527978cf
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/Jwk.java
@@ -0,0 +1,30 @@
+package io.jsonwebtoken.security;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface Jwk extends Map, JwkMutator {
+
+ String getType();
+
+ String getUse();
+
+ Set getOperations();
+
+ String getAlgorithm();
+
+ String getId();
+
+ URI getX509Url();
+
+ List getX509CertficateChain();
+
+ String getX509CertificateSha1Thumbprint();
+
+ String getX509CertificateSha256Thumbprint();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkBuilder.java b/api/src/main/java/io/jsonwebtoken/security/JwkBuilder.java
new file mode 100644
index 000000000..c5b09062f
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkBuilder.java
@@ -0,0 +1,10 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkBuilder extends JwkMutator {
+
+ K build();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkBuilderFactory.java b/api/src/main/java/io/jsonwebtoken/security/JwkBuilderFactory.java
new file mode 100644
index 000000000..406408dff
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkBuilderFactory.java
@@ -0,0 +1,12 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkBuilderFactory {
+
+ EcJwkBuilderFactory ellipticCurve();
+
+ SymmetricJwkBuilder symmetric();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/JwkMutator.java
new file mode 100644
index 000000000..ba8ab1cc1
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkMutator.java
@@ -0,0 +1,27 @@
+package io.jsonwebtoken.security;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkMutator {
+
+ T setUse(String use);
+
+ T setOperations(Set ops);
+
+ T setAlgorithm(String alg);
+
+ T setId(String id);
+
+ T setX509Url(URI uri);
+
+ T setX509CertificateChain(List chain);
+
+ T setX509CertificateSha1Thumbprint(String thumbprint);
+
+ T setX509CertificateSha256Thumbprint(String thumbprint);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfo.java b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfo.java
new file mode 100644
index 000000000..55eb75030
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfo.java
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.security;
+
+import java.util.Map;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkRsaPrimeInfo extends Map, JwkRsaPrimeInfoMutator {
+
+ String getPrime();
+
+ String getCrtExponent();
+
+ String getCrtCoefficient();
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoBuilder.java b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoBuilder.java
new file mode 100644
index 000000000..4307aa57d
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoBuilder.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkRsaPrimeInfoBuilder extends JwkRsaPrimeInfoMutator {
+
+ JwkRsaPrimeInfo build();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoMutator.java b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoMutator.java
new file mode 100644
index 000000000..36a956122
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/JwkRsaPrimeInfoMutator.java
@@ -0,0 +1,13 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface JwkRsaPrimeInfoMutator {
+
+ T setPrime(String r);
+
+ T setCrtExponent(String d);
+
+ T setCrtCoefficient(String t);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/Jwks.java b/api/src/main/java/io/jsonwebtoken/security/Jwks.java
new file mode 100644
index 000000000..08cf45bf3
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/Jwks.java
@@ -0,0 +1,14 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Classes;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class Jwks {
+
+ public static T builder() {
+ return Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkBuilderFactory");
+ }
+
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyException.java b/api/src/main/java/io/jsonwebtoken/security/KeyException.java
index 0db219245..da44f9ad8 100644
--- a/api/src/main/java/io/jsonwebtoken/security/KeyException.java
+++ b/api/src/main/java/io/jsonwebtoken/security/KeyException.java
@@ -23,4 +23,8 @@ public class KeyException extends SecurityException {
public KeyException(String message) {
super(message);
}
+
+ public KeyException(String msg, Exception cause) {
+ super(msg, cause);
+ }
}
diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyManagementAlgorithmName.java b/api/src/main/java/io/jsonwebtoken/security/KeyManagementAlgorithmName.java
new file mode 100644
index 000000000..bb1b169b3
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/KeyManagementAlgorithmName.java
@@ -0,0 +1,106 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Collections;
+
+import java.util.List;
+
+/**
+ * Type-safe representation of standard JWE encryption key management algorithm names as defined in the
+ * JSON Web Algorithms specification.
+ *
+ * @since JJWT_RELEASE_VERSION
+ */
+public enum KeyManagementAlgorithmName {
+
+ RSA1_5("RSA1_5", "RSAES-PKCS1-v1_5", Collections.emptyList(), "RSA/ECB/PKCS1Padding"),
+ RSA_OAEP("RSA-OAEP", "RSAES OAEP using default parameters", Collections.emptyList(), "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"),
+ RSA_OAEP_256("RSA-OAEP-256", "RSAES OAEP using SHA-256 and MGF1 with SHA-256", Collections.emptyList(), "RSA/ECB/OAEPWithSHA-256AndMGF1Padding & MGF1ParameterSpec.SHA256"),
+ A128KW("A128KW", "AES Key Wrap with default initial value using 128-bit key", Collections.emptyList(), "AESWrap"),
+ A192KW("A192KW", "AES Key Wrap with default initial value using 192-bit key", Collections.emptyList(), "AESWrap"),
+ A256KW("A256KW", "AES Key Wrap with default initial value using 256-bit key", Collections.emptyList(), "AESWrap"),
+ dir("dir", "Direct use of a shared symmetric key as the CEK", Collections.emptyList(), "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"),
+ ECDH_ES("ECDH-ES", "Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF", Collections.of("epk", "apu", "apv"), "ECDH"),
+ ECDH_ES_A128KW("ECDH-ES+A128KW", "ECDH-ES using Concat KDF and CEK wrapped with \"A128KW\"", Collections.of("epk", "apu", "apv"), "ECDH???"),
+ ECDH_ES_A192KW("ECDH-ES+A192KW", "ECDH-ES using Concat KDF and CEK wrapped with \"A192KW\"", Collections.of("epk", "apu", "apv"), "ECDH???"),
+ ECDH_ES_A256KW("ECDH-ES+A256KW", "ECDH-ES using Concat KDF and CEK wrapped with \"A256KW\"", Collections.of("epk", "apu", "apv"), "ECDH???"),
+ A128GCMKW("A128GCMKW", "Key wrapping with AES GCM using 128-bit key", Collections.of("iv", "tag"), "???"),
+ A192GCMKW("A192GCMKW", "Key wrapping with AES GCM using 192-bit key", Collections.of("iv", "tag"), "???"),
+ A256GCMKW("A256GCMKW", "Key wrapping with AES GCM using 256-bit key", Collections.of("iv", "tag"), "???"),
+ PBES2_HS256_A128KW("PBES2-HS256+A128KW", "PBES2 with HMAC SHA-256 and \"A128KW\" wrapping", Collections.of("p2s", "p2c"), "???"),
+ PBES2_HS384_A192KW("PBES2-HS384+A192KW", "PBES2 with HMAC SHA-384 and \"A192KW\" wrapping", Collections.of("p2s", "p2c"), "???"),
+ PBES2_HS512_A256KW("PBES2-HS512+A256KW", "PBES2 with HMAC SHA-512 and \"A256KW\" wrapping", Collections.of("p2s", "p2c"), "???");
+
+ private final String value;
+ private final String description;
+ private final List moreHeaderParams;
+ private final String jcaName;
+
+ KeyManagementAlgorithmName(String value, String description, List moreHeaderParams, String jcaName) {
+ this.value = value;
+ this.description = description;
+ this.moreHeaderParams = moreHeaderParams;
+ this.jcaName = jcaName;
+ }
+
+ /**
+ * Returns the JWA algorithm name constant.
+ *
+ * @return the JWA algorithm name constant.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the JWA algorithm description.
+ *
+ * @return the JWA algorithm description.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns a list of header parameters that must exist in the JWE header when evaluating the key management
+ * algorithm. The list will be empty for algorithms that do not require additional header parameters.
+ *
+ * @return a list of header parameters that must exist in the JWE header when evaluating the key management
+ * algorithm.
+ */
+ public List getMoreHeaderParams() {
+ return moreHeaderParams;
+ }
+
+ /**
+ * Returns the name of the JCA algorithm used to create or validate the Content Encryption Key (CEK).
+ *
+ * @return the name of the JCA algorithm used to create or validate the Content Encryption Key (CEK).
+ */
+ public String getJcaName() {
+ return jcaName;
+ }
+
+ /**
+ * Returns the corresponding {@code KeyManagementAlgorithmName} enum instance based on a
+ * case-insensitive name comparison of the specified JWE alg
value.
+ *
+ * @param name the case-insensitive JWE alg
header value.
+ * @return Returns the corresponding {@code KeyManagementAlgorithmName} enum instance based on a
+ * case-insensitive name comparison of the specified JWE alg
value.
+ * @throws IllegalArgumentException if the specified value does not match any JWE {@code KeyManagementAlgorithmName} value.
+ */
+ public static KeyManagementAlgorithmName forName(String name) throws IllegalArgumentException {
+ for (KeyManagementAlgorithmName alg : values()) {
+ if (alg.getValue().equalsIgnoreCase(name)) {
+ return alg;
+ }
+ }
+
+ throw new IllegalArgumentException("Unsupported JWE Key Management Algorithm name: " + name);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyManagementModeName.java b/api/src/main/java/io/jsonwebtoken/security/KeyManagementModeName.java
new file mode 100644
index 000000000..4f4968c0f
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/KeyManagementModeName.java
@@ -0,0 +1,42 @@
+package io.jsonwebtoken.security;
+
+/**
+ * An enum representing the {@code Key Management Mode} names defined in
+ * RFC 7516, Section 2.
+ *
+ * @since JJWT_RELEASE_VERSION
+ */
+public enum KeyManagementModeName {
+
+ KEY_ENCRYPTION("Key Encryption",
+ "The CEK value is encrypted to the intended recipient using an asymmetric encryption algorithm"),
+
+ KEY_WRAPPING("Key Wrapping",
+ "The CEK value is encrypted to the intended recipient using a symmetric key wrapping algorithm."),
+
+ DIRECT_KEY_AGREEMENT("Direct Key Agreement",
+ "A key agreement algorithm is used to agree upon the CEK value."),
+
+ KEY_AGREEMENT_WITH_KEY_WRAPPING("Key Agreement with Key Wrapping",
+ "A key agreement algorithm is used to agree upon a symmetric key used to encrypt the CEK value to the " +
+ "intended recipient using a symmetric key wrapping algorithm."),
+
+ DIRECT_ENCRYPTION("Direct Encryption",
+ "The CEK value used is the secret symmetric key value shared between the parties.");
+
+ private final String name;
+ private final String desc;
+
+ KeyManagementModeName(String name, String desc) {
+ this.name = name;
+ this.desc = desc;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return desc;
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/Keys.java b/api/src/main/java/io/jsonwebtoken/security/Keys.java
index cb4784248..088be5e9b 100644
--- a/api/src/main/java/io/jsonwebtoken/security/Keys.java
+++ b/api/src/main/java/io/jsonwebtoken/security/Keys.java
@@ -15,16 +15,11 @@
*/
package io.jsonwebtoken.security;
-import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
-import io.jsonwebtoken.lang.Classes;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyPair;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
/**
* Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s.
@@ -33,36 +28,10 @@
*/
public final class Keys {
- private static final String MAC = "io.jsonwebtoken.impl.crypto.MacProvider";
- private static final String RSA = "io.jsonwebtoken.impl.crypto.RsaProvider";
- private static final String EC = "io.jsonwebtoken.impl.crypto.EllipticCurveProvider";
-
- private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class};
-
- //purposefully ordered higher to lower:
- private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
- SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256));
-
//prevent instantiation
private Keys() {
}
- /*
- public static final int bitLength(Key key) throws IllegalArgumentException {
- Assert.notNull(key, "Key cannot be null.");
- if (key instanceof SecretKey) {
- byte[] encoded = key.getEncoded();
- return Arrays.length(encoded) * 8;
- } else if (key instanceof RSAKey) {
- return ((RSAKey)key).getModulus().bitLength();
- } else if (key instanceof ECKey) {
- return ((ECKey)key).getParams().getOrder().bitLength();
- }
-
- throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName());
- }
- */
-
/**
* Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array.
*
@@ -80,23 +49,37 @@ public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException {
int bitLength = bytes.length * 8;
- for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) {
- if (bitLength >= alg.getMinKeyLength()) {
- return new SecretKeySpec(bytes, alg.getJcaName());
- }
+ //Purposefully ordered higher to lower to ensure the strongest key possible can be generated.
+ if (bitLength >= 512) {
+ return new SecretKeySpec(bytes, "HmacSHA512");
+ } else if (bitLength >= 384) {
+ return new SecretKeySpec(bytes, "HmacSHA384");
+ } else if (bitLength >= 256) {
+ return new SecretKeySpec(bytes, "HmacSHA256");
}
String msg = "The specified key byte array is " + bitLength + " bits which " +
"is not secure enough for any JWT HMAC-SHA algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " +
"size >= 256 bits (the key size must be greater than or equal to the hash " +
- "output size). Consider using the " + Keys.class.getName() + "#secretKeyFor(SignatureAlgorithm) method " +
- "to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See " +
- "https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
+ "output size). Consider using the SignatureAlgorithms.HS256.generateKey() method (or HS384.generateKey() " +
+ "or HS512.generateKey()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " +
+ "algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
throw new WeakKeyException(msg);
}
/**
+ * Deprecation Notice
+ * As of JJWT JJWT_RELEASE_VERSION, symmetric (secret) key algorithm instances can generate a key of suitable
+ * length for that specific algorithm by calling their {@code generateKey()} method directly. For example:
+ *
+ * {@link SignatureAlgorithms#HS256}.generateKey();
+ * {@link SignatureAlgorithms#HS384}.generateKey();
+ * {@link SignatureAlgorithms#HS512}.generateKey();
+ *
+ * Call those methods as needed instead of this {@code secretKeyFor} helper method. This helper method will be
+ * removed before the 1.0 final release.
+ *
* Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.
*
*
JWA Specification (RFC 7518), Section 3.2
@@ -124,23 +107,36 @@ public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException {
*
* @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use.
* @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}.
- * @throws IllegalArgumentException for any input value other than {@link SignatureAlgorithm#HS256},
- * {@link SignatureAlgorithm#HS384}, or {@link SignatureAlgorithm#HS512}
+ * @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256},
+ * {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512}
+ * @deprecated since JJWT_RELEASE_VERSION. Use your preferred {@link SymmetricKeySignatureAlgorithm} instance's
+ * {@link SymmetricKeySignatureAlgorithm#generateKey() generateKey()} method directly.
*/
- public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgumentException {
+ @Deprecated
+ public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
- switch (alg) {
- case HS256:
- case HS384:
- case HS512:
- return Classes.invokeStatic(MAC, "generateKey", SIG_ARG_TYPES, alg);
- default:
- String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
- throw new IllegalArgumentException(msg);
+ SignatureAlgorithm salg = SignatureAlgorithms.forName(alg.name());
+ if (!(salg instanceof SymmetricKeySignatureAlgorithm)) {
+ String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
+ throw new IllegalArgumentException(msg);
}
+ return ((SymmetricKeySignatureAlgorithm) salg).generateKey();
}
/**
+ *
Deprecation Notice
+ * As of JJWT JJWT_RELEASE_VERSION, asymmetric key algorithm instances can generate KeyPairs of suitable strength
+ * for that specific algorithm by calling their {@code generateKeyPair()} method directly. For example:
+ *
+ * {@link SignatureAlgorithms#RS256}.generateKeyPair();
+ * {@link SignatureAlgorithms#RS384}.generateKeyPair();
+ * {@link SignatureAlgorithms#RS256}.generateKeyPair();
+ * ... etc ...
+ * {@link SignatureAlgorithms#ES512}.generateKeyPair();
+ *
+ * Call those methods as needed instead of this {@code keyPairFor} helper method. This helper method will be
+ * removed before the 1.0 final release.
+ *
* Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
*
*
If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:
@@ -199,7 +195,7 @@ public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgum
*
*
* EC512 |
- * 512 bits |
+ * 521 bits |
* {@code P-521} |
* {@code secp521r1} |
*
@@ -208,24 +204,17 @@ public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgum
* @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use.
* @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
* @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm
+ * @deprecated since JJWT_RELEASE_VERSION. Use your preferred {@link AsymmetricKeySignatureAlgorithm} instance's
+ * {@link AsymmetricKeySignatureAlgorithm#generateKeyPair() generateKeyPair()} method directly.
*/
- public static KeyPair keyPairFor(SignatureAlgorithm alg) throws IllegalArgumentException {
+ @Deprecated
+ public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
- switch (alg) {
- case RS256:
- case PS256:
- case RS384:
- case PS384:
- case RS512:
- case PS512:
- return Classes.invokeStatic(RSA, "generateKeyPair", SIG_ARG_TYPES, alg);
- case ES256:
- case ES384:
- case ES512:
- return Classes.invokeStatic(EC, "generateKeyPair", SIG_ARG_TYPES, alg);
- default:
- String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
- throw new IllegalArgumentException(msg);
+ SignatureAlgorithm salg = SignatureAlgorithms.forName(alg.name());
+ if (!(salg instanceof AsymmetricKeySignatureAlgorithm)) {
+ String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
+ throw new IllegalArgumentException(msg);
}
+ return ((AsymmetricKeySignatureAlgorithm) salg).generateKeyPair();
}
}
diff --git a/api/src/main/java/io/jsonwebtoken/security/MalformedKeyException.java b/api/src/main/java/io/jsonwebtoken/security/MalformedKeyException.java
new file mode 100644
index 000000000..a89bbc3df
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/MalformedKeyException.java
@@ -0,0 +1,15 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class MalformedKeyException extends InvalidKeyException {
+
+ public MalformedKeyException(String message) {
+ super(message);
+ }
+
+ public MalformedKeyException(String msg, Exception cause) {
+ super(msg, cause);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwk.java b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwk.java
new file mode 100644
index 000000000..2130d45d0
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwk.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PrivateEcJwk extends EcJwk, PrivateEcJwkMutator {
+
+ String getD();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkBuilder.java b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkBuilder.java
new file mode 100644
index 000000000..543e785db
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkBuilder.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PrivateEcJwkBuilder extends EcJwkBuilder {
+
+ PrivateEcJwkBuilder setD(String d);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkMutator.java
new file mode 100644
index 000000000..f6e5f8a23
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PrivateEcJwkMutator.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PrivateEcJwkMutator extends EcJwkMutator {
+
+ T setD(String d);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwk.java b/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwk.java
new file mode 100644
index 000000000..28976a93c
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwk.java
@@ -0,0 +1,23 @@
+package io.jsonwebtoken.security;
+
+import java.util.List;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PrivateRsaJwk extends RsaJwk, PrivateRsaJwkMutator {
+
+ String getD();
+
+ String getP();
+
+ String getQ();
+
+ String getDP();
+
+ String getDQ();
+
+ String getQI();
+
+ List getOtherPrimesInfo();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwkMutator.java
new file mode 100644
index 000000000..6ffb3edc6
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PrivateRsaJwkMutator.java
@@ -0,0 +1,23 @@
+package io.jsonwebtoken.security;
+
+import java.util.List;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PrivateRsaJwkMutator extends RsaJwkMutator {
+
+ T setD(String d);
+
+ T setP(String p);
+
+ T setQ(String q);
+
+ T setDP(String dp);
+
+ T setDQ(String dq);
+
+ T setQI(String qi);
+
+ T setOtherPrimesInfo(List infos);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PublicEcJwk.java b/api/src/main/java/io/jsonwebtoken/security/PublicEcJwk.java
new file mode 100644
index 000000000..be532e268
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PublicEcJwk.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PublicEcJwk extends EcJwk {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PublicEcJwkBuilder.java b/api/src/main/java/io/jsonwebtoken/security/PublicEcJwkBuilder.java
new file mode 100644
index 000000000..ed893ed35
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PublicEcJwkBuilder.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PublicEcJwkBuilder extends EcJwkBuilder {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/PublicRsaJwk.java b/api/src/main/java/io/jsonwebtoken/security/PublicRsaJwk.java
new file mode 100644
index 000000000..99e121368
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/PublicRsaJwk.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface PublicRsaJwk extends RsaJwk {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/RsaJwk.java b/api/src/main/java/io/jsonwebtoken/security/RsaJwk.java
new file mode 100644
index 000000000..0a5c2393a
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/RsaJwk.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface RsaJwk extends Jwk, RsaJwkMutator {
+
+ String getModulus();
+
+ String getExponent();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/RsaJwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/RsaJwkMutator.java
new file mode 100644
index 000000000..0df8a74da
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/RsaJwkMutator.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface RsaJwkMutator extends JwkMutator {
+
+ T setModulus(String n);
+
+ T setExponent(String e);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithm.java
new file mode 100644
index 000000000..095fe5047
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithm.java
@@ -0,0 +1,15 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.Named;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SignatureAlgorithm extends Named {
+
+ byte[] sign(CryptoRequest request) throws SignatureException, KeyException;
+
+ boolean verify(VerifySignatureRequest request) throws SignatureException, KeyException;
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithms.java b/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithms.java
new file mode 100644
index 000000000..deac14210
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithms.java
@@ -0,0 +1,223 @@
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Classes;
+
+import javax.crypto.SecretKey;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public final class SignatureAlgorithms {
+
+ // Prevent instantiation
+ private SignatureAlgorithms() {
+ }
+
+ static final String HMAC = "io.jsonwebtoken.impl.security.MacSignatureAlgorithm";
+ static final Class>[] HMAC_ARGS = new Class[]{String.class, String.class, int.class};
+
+ private static final String RSA = "io.jsonwebtoken.impl.security.RsaSignatureAlgorithm";
+ private static final Class>[] RSA_ARGS = new Class[]{String.class, String.class, int.class};
+ private static final Class>[] PSS_ARGS = new Class[]{String.class, String.class, int.class, int.class};
+
+ private static final String EC = "io.jsonwebtoken.impl.security.EllipticCurveSignatureAlgorithm";
+ private static final Class>[] EC_ARGS = new Class[]{String.class, String.class, String.class, int.class, int.class};
+
+ private static SymmetricKeySignatureAlgorithm hmacSha(int minKeyLength) {
+ return Classes.newInstance(HMAC, HMAC_ARGS, "HS" + minKeyLength, "HmacSHA" + minKeyLength, minKeyLength);
+ }
+
+ private static AsymmetricKeySignatureAlgorithm rsa(int digestLength, int preferredKeyLength) {
+ return Classes.newInstance(RSA, RSA_ARGS, "RS" + digestLength, "SHA" + digestLength + "withRSA", preferredKeyLength);
+ }
+
+ private static AsymmetricKeySignatureAlgorithm pss(int digestLength, int preferredKeyLength) {
+ return Classes.newInstance(RSA, PSS_ARGS, "PS" + digestLength, "RSASSA-PSS", preferredKeyLength, digestLength);
+ }
+
+ private static AsymmetricKeySignatureAlgorithm ec(int keySize, int signatureLength) {
+ int shaSize = keySize == 521 ? 512 : keySize;
+ return Classes.newInstance(EC, EC_ARGS, "ES" + shaSize, "SHA" + shaSize + "withECDSA", "secp" + keySize + "r1", keySize, signatureLength);
+ }
+
+ public static final SignatureAlgorithm NONE = Classes.newInstance("io.jsonwebtoken.impl.security.NoneSignatureAlgorithm");
+ public static final SymmetricKeySignatureAlgorithm HS256 = hmacSha(256);
+ public static final SymmetricKeySignatureAlgorithm HS384 = hmacSha(384);
+ public static final SymmetricKeySignatureAlgorithm HS512 = hmacSha(512);
+ public static final AsymmetricKeySignatureAlgorithm RS256 = rsa(256, 2048);
+ public static final AsymmetricKeySignatureAlgorithm RS384 = rsa(384, 3072);
+ public static final AsymmetricKeySignatureAlgorithm RS512 = rsa(512, 4096);
+ public static final AsymmetricKeySignatureAlgorithm PS256 = pss(256, 2048);
+ public static final AsymmetricKeySignatureAlgorithm PS384 = pss(384, 3072);
+ public static final AsymmetricKeySignatureAlgorithm PS512 = pss(512, 4096);
+ public static final AsymmetricKeySignatureAlgorithm ES256 = ec(256, 64);
+ public static final AsymmetricKeySignatureAlgorithm ES384 = ec(384, 96);
+ public static final AsymmetricKeySignatureAlgorithm ES512 = ec(521, 132);
+
+ private static Map toMap(SignatureAlgorithm... algs) {
+ Map m = new LinkedHashMap<>();
+ for (SignatureAlgorithm alg : algs) {
+ m.put(alg.getName(), alg);
+ }
+ return Collections.unmodifiableMap(m);
+ }
+
+ private static final Map STANDARD_ALGORITHMS = toMap(
+ NONE, HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512
+ );
+
+ public static Collection extends SignatureAlgorithm> values() {
+ return STANDARD_ALGORITHMS.values();
+ }
+
+ /**
+ * Looks up and returns the corresponding JWA standard {@code SignatureAlgorithm} instance based on a
+ * case-insensitive name comparison.
+ *
+ * @param name The case-insensitive name of the JWA standard {@code SignatureAlgorithm} instance to return
+ * @return the corresponding JWA standard {@code SignatureAlgorithm} enum instance based on a
+ * case-insensitive name comparison.
+ * @throws SignatureException if the specified value does not match any JWA standard {@code SignatureAlgorithm}
+ * name.
+ */
+ public static SignatureAlgorithm forName(String name) {
+ Assert.notNull(name, "name argument cannot be null.");
+ //try constant time lookup first. This will satisfy 99% of invocations:
+ SignatureAlgorithm alg = STANDARD_ALGORITHMS.get(name);
+ if (alg != null) {
+ return alg;
+ }
+ //fall back to case-insensitive lookup:
+ for (SignatureAlgorithm salg : STANDARD_ALGORITHMS.values()) {
+ if (name.equalsIgnoreCase(salg.getName())) {
+ return salg;
+ }
+ }
+ // still no result - error:
+ throw new SignatureException("Unsupported signature algorithm '" + name + "'");
+ }
+
+ /**
+ * Returns the recommended signature algorithm to be used with the specified key according to the following
+ * heuristics:
+ *
+ *
+ * Key Signature Algorithm
+ *
+ *
+ * If the Key is a: |
+ * And: |
+ * With a key size of: |
+ * The returned SignatureAlgorithm will be: |
+ *
+ *
+ *
+ *
+ * {@link SecretKey} |
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256") 1 |
+ * 256 <= size <= 383 2 |
+ * {@link SignatureAlgorithms#HS256 HS256} |
+ *
+ *
+ * {@link SecretKey} |
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384") 1 |
+ * 384 <= size <= 511 |
+ * {@link SignatureAlgorithms#HS384 HS384} |
+ *
+ *
+ * {@link SecretKey} |
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512") 1 |
+ * 512 <= size |
+ * {@link SignatureAlgorithms#HS512 HS512} |
+ *
+ *
+ * {@link ECKey} |
+ * instanceof {@link PrivateKey} |
+ * 256 <= size <= 383 3 |
+ * {@link SignatureAlgorithms#ES256 ES256} |
+ *
+ *
+ * {@link ECKey} |
+ * instanceof {@link PrivateKey} |
+ * 384 <= size <= 520 4 |
+ * {@link SignatureAlgorithms#ES384 ES384} |
+ *
+ *
+ * {@link ECKey} |
+ * instanceof {@link PrivateKey} |
+ * 521 <= size 4 |
+ * {@link SignatureAlgorithms#ES512 ES512} |
+ *
+ *
+ * {@link RSAKey} |
+ * instanceof {@link PrivateKey} |
+ * 2048 <= size <= 3071 5,6 |
+ * {@link SignatureAlgorithms#RS256 RS256} |
+ *
+ *
+ * {@link RSAKey} |
+ * instanceof {@link PrivateKey} |
+ * 3072 <= size <= 4095 6 |
+ * {@link SignatureAlgorithms#RS384 RS384} |
+ *
+ *
+ * {@link RSAKey} |
+ * instanceof {@link PrivateKey} |
+ * 4096 <= size 5 |
+ * {@link SignatureAlgorithms#RS512 RS512} |
+ *
+ *
+ *
+ * Notes:
+ *
+ * - {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal
+ * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be
+ * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
+ * - The JWT JWA Specification (RFC 7518,
+ * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater.
+ * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an
+ * {@link WeakKeyException}.
+ * - The JWT JWA Specification (RFC 7518,
+ * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater.
+ * {@code ECKey}s with key lengths less than 256 bits will be rejected with a
+ * {@link WeakKeyException}.
+ * - The ECDSA {@code P-521} curve does indeed use keys of 521 bits, not 512 as might be expected. ECDSA
+ * keys of 384 < size <= 520 are suitable for ES384, while ES512 requires keys >= 521 bits. The '512' part of the
+ * ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less
+ * than 521 bits will be rejected with a {@link WeakKeyException}.
+ * - The JWT JWA Specification (RFC 7518,
+ * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater.
+ * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a
+ * {@link WeakKeyException}.
+ * - Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and
+ * {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to
+ * parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms.
+ * This is not required - just a convenience.
+ *
+ * This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variants for any
+ * specified {@link RSAKey} because the the {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are
+ * available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA
+ * Provider (like BouncyCastle).
+ * Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the
+ * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or
+ * explicitly disallowed by the JWT specification.
+ *
+ * @param key the key to inspect
+ * @return the recommended signature algorithm to be used with the specified key
+ * @throws InvalidKeyException for any key that does not match the heuristics and requirements documented above,
+ * since that inevitably means the Key is either insufficient or explicitly disallowed by the JWT specification.
+ */
+ public static SignatureAlgorithm forSigningKey(Key key) {
+ io.jsonwebtoken.SignatureAlgorithm alg = io.jsonwebtoken.SignatureAlgorithm.forSigningKey(key);
+ return forName(alg.getValue());
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SignatureException.java b/api/src/main/java/io/jsonwebtoken/security/SignatureException.java
index 7cddb2cac..93253a01b 100644
--- a/api/src/main/java/io/jsonwebtoken/security/SignatureException.java
+++ b/api/src/main/java/io/jsonwebtoken/security/SignatureException.java
@@ -18,6 +18,7 @@
/**
* @since 0.10.0
*/
+@SuppressWarnings("deprecation")
public class SignatureException extends io.jsonwebtoken.SignatureException {
public SignatureException(String message) {
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricEncryptionAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricEncryptionAlgorithm.java
new file mode 100644
index 000000000..a140da83c
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricEncryptionAlgorithm.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+import javax.crypto.SecretKey;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricEncryptionAlgorithm, ERes extends IvEncryptionResult, DReq extends IvRequest> extends EncryptionAlgorithm, SymmetricKeyAlgorithm {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricJwk.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwk.java
new file mode 100644
index 000000000..6b81fcde1
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwk.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricJwk extends Jwk, SymmetricJwkMutator {
+
+ String getK();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkBuilder.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkBuilder.java
new file mode 100644
index 000000000..af63d4238
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkBuilder.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricJwkBuilder extends JwkBuilder, SymmetricJwkMutator {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkMutator.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkMutator.java
new file mode 100644
index 000000000..0ab48505e
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricJwkMutator.java
@@ -0,0 +1,9 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricJwkMutator extends JwkMutator {
+
+ T setK(String k);
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricKeyAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricKeyAlgorithm.java
new file mode 100644
index 000000000..fb074973f
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricKeyAlgorithm.java
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.security;
+
+import javax.crypto.SecretKey;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricKeyAlgorithm {
+
+ /**
+ * Creates and returns a new secure-random key with a length sufficient to be used by this Algorithm.
+ *
+ * @return a new secure-random key with a length sufficient to be used by this Algorithm.
+ */
+ SecretKey generateKey();
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SymmetricKeySignatureAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/SymmetricKeySignatureAlgorithm.java
new file mode 100644
index 000000000..451b6c645
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SymmetricKeySignatureAlgorithm.java
@@ -0,0 +1,7 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface SymmetricKeySignatureAlgorithm extends SignatureAlgorithm, SymmetricKeyAlgorithm {
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/UnsupportedKeyException.java b/api/src/main/java/io/jsonwebtoken/security/UnsupportedKeyException.java
new file mode 100644
index 000000000..890dec20a
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/UnsupportedKeyException.java
@@ -0,0 +1,15 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class UnsupportedKeyException extends KeyException {
+
+ public UnsupportedKeyException(String message) {
+ super(message);
+ }
+
+ public UnsupportedKeyException(String msg, Exception cause) {
+ super(msg, cause);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/VerifySignatureRequest.java b/api/src/main/java/io/jsonwebtoken/security/VerifySignatureRequest.java
new file mode 100644
index 000000000..b71a6c1d7
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/VerifySignatureRequest.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+import java.security.Key;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public interface VerifySignatureRequest extends CryptoRequest {
+
+ byte[] getSignature();
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/EncryptionAlgorithmNameTest.groovy b/api/src/test/groovy/io/jsonwebtoken/EncryptionAlgorithmNameTest.groovy
new file mode 100644
index 000000000..26fd5d0b3
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/EncryptionAlgorithmNameTest.groovy
@@ -0,0 +1,61 @@
+package io.jsonwebtoken
+
+import io.jsonwebtoken.security.EncryptionAlgorithmName
+import org.junit.Test
+import static org.junit.Assert.*
+
+class EncryptionAlgorithmNameTest {
+
+ @Test
+ void testGetValue() {
+ assertEquals 'A128CBC-HS256', EncryptionAlgorithmName.A128CBC_HS256.getValue()
+ assertEquals 'A192CBC-HS384', EncryptionAlgorithmName.A192CBC_HS384.getValue()
+ assertEquals 'A256CBC-HS512', EncryptionAlgorithmName.A256CBC_HS512.getValue()
+ assertEquals 'A128GCM', EncryptionAlgorithmName.A128GCM.getValue()
+ assertEquals 'A192GCM', EncryptionAlgorithmName.A192GCM.getValue()
+ assertEquals 'A256GCM', EncryptionAlgorithmName.A256GCM.getValue()
+ }
+
+ @Test
+ void testGetDescription() {
+ assertEquals 'AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.3', EncryptionAlgorithmName.A128CBC_HS256.getDescription()
+ assertEquals 'AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.4', EncryptionAlgorithmName.A192CBC_HS384.getDescription()
+ assertEquals 'AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.5', EncryptionAlgorithmName.A256CBC_HS512.getDescription()
+ assertEquals 'AES GCM using 128-bit key', EncryptionAlgorithmName.A128GCM.getDescription()
+ assertEquals 'AES GCM using 192-bit key', EncryptionAlgorithmName.A192GCM.getDescription()
+ assertEquals 'AES GCM using 256-bit key', EncryptionAlgorithmName.A256GCM.getDescription()
+ }
+
+ @Test
+ void testGetJcaName() {
+ for( def name : EncryptionAlgorithmName.values() ) {
+ if (name.getValue().contains("GCM")) {
+ assertEquals 'AES/GCM/NoPadding', name.getJcaName()
+ } else {
+ assertEquals 'AES/CBC/PKCS5Padding', name.getJcaName()
+ }
+ }
+ }
+
+ @Test
+ void testToString() {
+ for( def name : EncryptionAlgorithmName.values() ) {
+ assertEquals name.toString(), name.getValue()
+ }
+ }
+
+ @Test
+ void testForName() {
+ def name = EncryptionAlgorithmName.forName('A128GCM')
+ assertSame name, EncryptionAlgorithmName.A128GCM
+ }
+
+ @Test
+ void testForNameFailure() {
+ try {
+ EncryptionAlgorithmName.forName('foo')
+ fail()
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/lang/ArraysTest.groovy b/api/src/test/groovy/io/jsonwebtoken/lang/ArraysTest.groovy
new file mode 100644
index 000000000..2702003e6
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/lang/ArraysTest.groovy
@@ -0,0 +1,45 @@
+package io.jsonwebtoken.lang
+
+import org.junit.Test
+
+import java.nio.charset.StandardCharsets
+
+import static org.junit.Assert.*
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+class ArraysTest {
+
+ @Test
+ void testCleanWithNull() {
+ assertNull Arrays.clean(null)
+ }
+
+ @Test
+ void testCleanWithEmpty() {
+ assertNull Arrays.clean(new byte[0])
+ }
+
+ @Test
+ void testCleanWithElements() {
+ byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8)
+ assertSame bytes, Arrays.clean(bytes)
+ }
+
+ @Test
+ void testByteArrayLengthWithNull() {
+ assertEquals 0, Arrays.length(null)
+ }
+
+ @Test
+ void testByteArrayLengthWithEmpty() {
+ assertEquals 0, Arrays.length(new byte[0])
+ }
+
+ @Test
+ void testByteArrayLengthWithElements() {
+ byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8)
+ assertEquals 5, Arrays.length(bytes)
+ }
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/CurveIdsTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/CurveIdsTest.groovy
new file mode 100644
index 000000000..7a1af2dd7
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/security/CurveIdsTest.groovy
@@ -0,0 +1,24 @@
+package io.jsonwebtoken.security
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+class CurveIdsTest {
+
+ @Test(expected=IllegalArgumentException)
+ void testNullId() {
+ CurveIds.forValue(null)
+ }
+
+ @Test(expected=IllegalArgumentException)
+ void testEmptyId() {
+ CurveIds.forValue(' ')
+ }
+
+ @Test
+ void testNonStandardId() {
+ CurveId id = CurveIds.forValue("NonStandard")
+ assertNotNull id
+ assertEquals 'NonStandard', id.toString()
+ }
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementAlgorithmNameTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementAlgorithmNameTest.groovy
new file mode 100644
index 000000000..3a4366b7f
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementAlgorithmNameTest.groovy
@@ -0,0 +1,54 @@
+package io.jsonwebtoken.security
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+class KeyManagementAlgorithmNameTest {
+
+ @Test
+ void testToString() {
+ for( def name : KeyManagementAlgorithmName.values()) {
+ assertEquals name.value, name.toString()
+ }
+ }
+
+ @Test
+ void testGetDescription() {
+ for( def name : KeyManagementAlgorithmName.values()) {
+ assertNotNull name.getDescription() //TODO improve this for actual value testing
+ }
+ }
+
+ @Test
+ void testGetMoreHeaderParams() {
+ for( def name : KeyManagementAlgorithmName.values()) {
+ assertNotNull name.getMoreHeaderParams() //TODO improve this for actual value testing
+ }
+ }
+
+ @Test
+ void testGetJcaName() {
+ for( def name : KeyManagementAlgorithmName.values()) {
+ assertNotNull name.getJcaName() //TODO improve this for actual value testing
+ }
+ }
+
+ @Test
+ void testForName() {
+ def name = KeyManagementAlgorithmName.forName('A128KW')
+ assertSame name, KeyManagementAlgorithmName.A128KW
+ }
+
+ @Test
+ void testForNameFailure() {
+ try {
+ KeyManagementAlgorithmName.forName('foo')
+ fail()
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
+
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementModeNameTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementModeNameTest.groovy
new file mode 100644
index 000000000..900894dca
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/security/KeyManagementModeNameTest.groovy
@@ -0,0 +1,19 @@
+package io.jsonwebtoken.security
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+class KeyManagementModeNameTest {
+
+ @Test
+ void test() {
+ //todo, write a real test:
+ for(KeyManagementModeName modeName : KeyManagementModeName.values()) {
+ assertNotNull modeName.getName()
+ assertNotNull modeName.getDescription()
+ }
+ }
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
index 7635491cc..8fa9148f3 100644
--- a/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
+++ b/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
@@ -16,18 +16,19 @@
package io.jsonwebtoken.security
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.lang.Classes
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
+import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor
import org.powermock.modules.junit4.PowerMockRunner
import javax.crypto.SecretKey
+import javax.crypto.spec.SecretKeySpec
import java.security.KeyPair
+import java.security.SecureRandom
import static org.easymock.EasyMock.eq
import static org.easymock.EasyMock.expect
-import static org.easymock.EasyMock.same
import static org.junit.Assert.*
import static org.powermock.api.easymock.PowerMock.*
@@ -37,9 +38,18 @@ import static org.powermock.api.easymock.PowerMock.*
* The actual implementation assertions are done in KeysImplTest in the impl module.
*/
@RunWith(PowerMockRunner)
-@PrepareForTest([Classes, Keys])
+@PrepareForTest([SignatureAlgorithms, Keys])
+@SuppressStaticInitializationFor("io.jsonwebtoken.security.SignatureAlgorithms")
class KeysTest {
+ private static final Random RANDOM = new SecureRandom()
+
+ static byte[] bytes(int sizeInBits) {
+ byte[] bytes = new byte[sizeInBits / Byte.SIZE]
+ RANDOM.nextBytes(bytes)
+ return bytes
+ }
+
@Test
void testPrivateCtor() { //for code coverage only
new Keys()
@@ -65,14 +75,35 @@ class KeysTest {
"is not secure enough for any JWT HMAC-SHA algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " +
"size >= 256 bits (the key size must be greater than or equal to the hash " +
- "output size). Consider using the " + Keys.class.getName() + "#secretKeyFor(SignatureAlgorithm) method " +
- "to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See " +
+ "output size). Consider using the SignatureAlgorithms.HS256.generateKey() method (or " +
+ "HS384.generateKey() or HS512.generateKey()) to create a key guaranteed to be secure enough " +
+ "for your preferred HMAC-SHA algorithm. See " +
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information." as String, expected.message
}
}
+ @Test
+ void testHmacShaWithValidSizes() {
+ for (int i : [256, 384, 512]) {
+ byte[] bytes = bytes(i)
+ def key = Keys.hmacShaKeyFor(bytes)
+ assertTrue key instanceof SecretKeySpec
+ assertEquals "HmacSHA$i" as String, key.getAlgorithm()
+ assertTrue Arrays.equals(bytes, key.getEncoded())
+ }
+ }
+
+ @Test
+ void testHmacShaLargerThan512() {
+ def key = Keys.hmacShaKeyFor(bytes(520))
+ assertTrue key instanceof SecretKeySpec
+ assertEquals 'HmacSHA512', key.getAlgorithm()
+ assertTrue key.getEncoded().length * Byte.SIZE >= 512
+ }
+
@Test
void testSecretKeyFor() {
+ mockStatic(SignatureAlgorithms)
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
@@ -80,61 +111,66 @@ class KeysTest {
if (name.startsWith('H')) {
- mockStatic(Classes)
-
def key = createMock(SecretKey)
- expect(Classes.invokeStatic(eq(Keys.MAC), eq("generateKey"), same(Keys.SIG_ARG_TYPES), same(alg))).andReturn(key)
+ def salg = createMock(SymmetricKeySignatureAlgorithm)
- replay Classes, key
+ expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
+ expect(salg.generateKey()).andReturn(key)
+ replay SignatureAlgorithms, salg, key
assertSame key, Keys.secretKeyFor(alg)
- verify Classes, key
-
- reset Classes, key
+ verify SignatureAlgorithms, salg, key
+ reset SignatureAlgorithms, salg, key
} else {
+ def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(AsymmetricKeySignatureAlgorithm)
+ expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
+ replay SignatureAlgorithms, salg
try {
Keys.secretKeyFor(alg)
fail()
} catch (IllegalArgumentException expected) {
assertEquals "The $name algorithm does not support shared secret keys." as String, expected.message
}
-
+ verify SignatureAlgorithms, salg
+ reset SignatureAlgorithms, salg
}
}
-
}
@Test
void testKeyPairFor() {
+ mockStatic SignatureAlgorithms
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
String name = alg.name()
if (name.equals('NONE') || name.startsWith('H')) {
+ def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(SymmetricKeySignatureAlgorithm)
+ expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
+ replay SignatureAlgorithms, salg
try {
Keys.keyPairFor(alg)
fail()
} catch (IllegalArgumentException expected) {
assertEquals "The $name algorithm does not support Key Pairs." as String, expected.message
}
+ verify SignatureAlgorithms, salg
+ reset SignatureAlgorithms, salg
} else {
- String fqcn = name.startsWith('E') ? Keys.EC : Keys.RSA
-
- mockStatic Classes
-
def pair = createMock(KeyPair)
- expect(Classes.invokeStatic(eq(fqcn), eq("generateKeyPair"), same(Keys.SIG_ARG_TYPES), same(alg))).andReturn(pair)
+ def salg = createMock(AsymmetricKeySignatureAlgorithm)
- replay Classes, pair
+ expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
+ expect(salg.generateKeyPair()).andReturn(pair)
+ replay SignatureAlgorithms, pair, salg
assertSame pair, Keys.keyPairFor(alg)
- verify Classes, pair
-
- reset Classes, pair
+ verify SignatureAlgorithms, pair, salg
+ reset SignatureAlgorithms, pair, salg
}
}
}
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/UnsupportedKeyExceptionTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/UnsupportedKeyExceptionTest.groovy
new file mode 100644
index 000000000..1738defb9
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/security/UnsupportedKeyExceptionTest.groovy
@@ -0,0 +1,17 @@
+package io.jsonwebtoken.security
+
+import org.junit.Test
+
+import static org.junit.Assert.*
+
+class UnsupportedKeyExceptionTest {
+
+ @Test
+ void testCauseWithMessage() {
+ def cause = new IllegalStateException()
+ def msg = 'foo'
+ def ex = new UnsupportedKeyException(msg, cause)
+ assertEquals msg, ex.getMessage()
+ assertSame cause, ex.getCause()
+ }
+}
diff --git a/extensions/gson/pom.xml b/extensions/gson/pom.xml
index 6b6985e83..ed554b0e0 100644
--- a/extensions/gson/pom.xml
+++ b/extensions/gson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 0.11.3-SNAPSHOT
+ 0.12.0-SNAPSHOT
../../pom.xml
@@ -44,4 +44,13 @@
+
+
+
+ com.github.siom79.japicmp
+ japicmp-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonDeserializerTest.groovy b/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonDeserializerTest.groovy
index ae1223965..581de42ca 100644
--- a/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonDeserializerTest.groovy
+++ b/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonDeserializerTest.groovy
@@ -21,6 +21,9 @@ import io.jsonwebtoken.io.Deserializer
import io.jsonwebtoken.lang.Strings
import org.junit.Test
+import java.text.DecimalFormat
+import java.text.NumberFormat
+
import static org.easymock.EasyMock.*
import static org.junit.Assert.*
import static org.hamcrest.CoreMatchers.instanceOf
diff --git a/extensions/jackson/pom.xml b/extensions/jackson/pom.xml
index 79b0959ab..ae26aec49 100644
--- a/extensions/jackson/pom.xml
+++ b/extensions/jackson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 0.11.3-SNAPSHOT
+ 0.12.0-SNAPSHOT
../../pom.xml
@@ -66,14 +66,6 @@
com.github.siom79.japicmp
japicmp-maven-plugin
-
-
-
-
- ${project.build.directory}/${project.artifactId}-${project.version}-deprecated.${project.packaging}
-
-
-
diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java
index a9ea111ea..c208ed7eb 100644
--- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java
+++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java
@@ -37,7 +37,6 @@ public class JacksonDeserializer implements Deserializer {
private final Class returnType;
private final ObjectMapper objectMapper;
- @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
public JacksonDeserializer() {
this(JacksonSerializer.DEFAULT_OBJECT_MAPPER);
}
diff --git a/extensions/orgjson/pom.xml b/extensions/orgjson/pom.xml
index f0ea88409..afe5b6bfb 100644
--- a/extensions/orgjson/pom.xml
+++ b/extensions/orgjson/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 0.11.3-SNAPSHOT
+ 0.12.0-SNAPSHOT
../../pom.xml
@@ -66,14 +66,6 @@
com.github.siom79.japicmp
japicmp-maven-plugin
-
-
-
-
- ${project.build.directory}/${project.artifactId}-${project.version}-deprecated.${project.packaging}
-
-
-
diff --git a/extensions/pom.xml b/extensions/pom.xml
index ee13b0f20..9259cd713 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 0.11.3-SNAPSHOT
+ 0.12.0-SNAPSHOT
../pom.xml
diff --git a/impl/pom.xml b/impl/pom.xml
index b1aaf92e1..ef51219f2 100644
--- a/impl/pom.xml
+++ b/impl/pom.xml
@@ -21,7 +21,7 @@
io.jsonwebtoken
jjwt-root
- 0.11.3-SNAPSHOT
+ 0.12.0-SNAPSHOT
../pom.xml
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
index 3560afc4a..7dba1763d 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
@@ -53,14 +53,26 @@ public T setContentType(String cty) {
return (T)this;
}
+ @Override
+ public String getAlgorithm() {
+ return getString(ALGORITHM);
+ }
+
+ @Override
+ public T setAlgorithm(String alg) {
+ setValue(ALGORITHM, alg);
+ return (T)this;
+ }
+
@SuppressWarnings("deprecation")
@Override
public String getCompressionAlgorithm() {
- String alg = getString(COMPRESSION_ALGORITHM);
- if (!Strings.hasText(alg)) {
- alg = getString(DEPRECATED_COMPRESSION_ALGORITHM);
+ String s = getString(COMPRESSION_ALGORITHM);
+ if (!Strings.hasText(s)) {
+ //backwards compatibility TODO: remove when releasing 1.0
+ s = getString(DEPRECATED_COMPRESSION_ALGORITHM);
}
- return alg;
+ return s;
}
@Override
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeader.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeader.java
new file mode 100644
index 000000000..db69ab44b
--- /dev/null
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeader.java
@@ -0,0 +1,30 @@
+package io.jsonwebtoken.impl;
+
+import io.jsonwebtoken.JweHeader;
+
+import java.util.Map;
+
+/**
+ * @since JJWT_RELEASE_VERSION
+ */
+public class DefaultJweHeader extends DefaultHeader implements JweHeader {
+
+ public DefaultJweHeader() {
+ super();
+ }
+
+ public DefaultJweHeader(Map map) {
+ super(map);
+ }
+
+ @Override
+ public String getEncryptionAlgorithm() {
+ return getString(ENCRYPTION_ALGORITHM);
+ }
+
+ @Override
+ public JweHeader setEncryptionAlgorithm(String enc) {
+ setValue(ENCRYPTION_ALGORITHM, enc);
+ return this;
+ }
+}
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwsHeader.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwsHeader.java
index cb747e3dd..bb23dff15 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwsHeader.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwsHeader.java
@@ -19,7 +19,7 @@
import java.util.Map;
-public class DefaultJwsHeader extends DefaultHeader implements JwsHeader {
+public class DefaultJwsHeader extends DefaultHeader implements JwsHeader {
public DefaultJwsHeader() {
super();
@@ -29,17 +29,6 @@ public DefaultJwsHeader(Map map) {
super(map);
}
- @Override
- public String getAlgorithm() {
- return getString(ALGORITHM);
- }
-
- @Override
- public JwsHeader setAlgorithm(String alg) {
- setValue(ALGORITHM, alg);
- return this;
- }
-
@Override
public String getKeyId() {
return getString(KEY_ID);
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
index 2c7ab1177..78421cfc5 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
@@ -21,10 +21,8 @@
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
-import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
-import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.impl.lang.LegacyServices;
+import io.jsonwebtoken.impl.security.DefaultCryptoRequest;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
@@ -33,31 +31,55 @@
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
+import io.jsonwebtoken.security.CryptoRequest;
import io.jsonwebtoken.security.InvalidKeyException;
+import io.jsonwebtoken.security.SignatureAlgorithm;
+import io.jsonwebtoken.security.SignatureAlgorithms;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
import java.util.Date;
import java.util.Map;
public class DefaultJwtBuilder implements JwtBuilder {
+ private static final byte[] TEST_MESSAGE_BYTES = "Test message".getBytes(StandardCharsets.UTF_8);
+
+ private Provider provider;
+ private SecureRandom secureRandom;
+
private Header header;
private Claims claims;
private String payload;
- private SignatureAlgorithm algorithm;
+ private SignatureAlgorithm algorithm = SignatureAlgorithms.NONE;
+
private Key key;
- private Serializer