JWE support. Resolves #113

- impl checkpoint: adjusted @since version, added KeyManagementMode concept w/ supporting interfaces
- KeyManagementMode interfaces and implementations are dummy implementations to enable compiling.  Design still needs to be vetted.
- Added JWE EncryptionAlgorithm and supporting interfaces/implementations, and refactored SignatureAlgorithm to be an interface instead of an enum to enable custom algorithms
- NoneSignatureAlgorithm cleanup. Added UnsupportedKeyExceptionTest.
- testing additions, cleanup cont'd
This commit is contained in:
Les Hazlewood 2018-08-02 17:30:18 -04:00
parent 8ed4ab407e
commit 0f607b547b
238 changed files with 9228 additions and 621 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
*.class
.DS_Store
# Mobile Tools for Java (J2ME)

View File

@ -20,18 +20,18 @@ before_install:
- export MVN_CMD="./mvnw --no-transfer-progress" # hide verbose download messages (log spam)
- |
if [[ "${TRAVIS_JDK_VERSION}" == "openjdk7" ]]; then
export MAVEN_OPTS="-Dhttps.protocols=TLSv1.2 -Xmx512m -XX:MaxPermSize=128m"
export JAVA_HOME="/usr/lib/jvm/java-7-oracle" # Set JAVA_HOME to where we want to install Oracle JDK 7
export PATH="${JAVA_HOME}/bin:${PATH}"
if [[ ! -d "${JAVA_HOME}" ]]; then
# Download and install Oracle JDK 7:
wget https://238dj3282as03k369.s3-us-west-1.amazonaws.com/jdk-7u80-linux-x64.tar.gz -O /tmp/jdk-7u80-linux-x64.tar.gz
tar xvfz /tmp/jdk-7u80-linux-x64.tar.gz -C /tmp
sudo mv /tmp/jdk1.7.0_80 "${JAVA_HOME}"
fi
# Download and install JCE Unlimited Strength Crypto policies for Oracle JDK 7:
curl -q -L -C - https://238dj3282as03k369.s3-us-west-1.amazonaws.com/UnlimitedJCEPolicyJDK7.zip -o /tmp/UnlimitedJCEPolicyJDK7.zip
sudo unzip -oj -d "$JAVA_HOME/jre/lib/security" /tmp/UnlimitedJCEPolicyJDK7.zip \*/\*.jar

View File

@ -71,6 +71,7 @@ enforcement.
* [Custom Clock](#jws-read-clock-custom)
* [Decompression](#jws-read-decompression)
<!-- * [Error Handling](#jws-read-errors) -->
* [Encrypted JWTs](#jwe)
* [Compression](#compression)
* [Custom Compression Codec](#compression-custom)
* [JSON Processor](#json)
@ -372,13 +373,13 @@ Most complexity is hidden behind a convenient and readable builder-based [fluent
```java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.SignatureAlgorithms;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
Key key = SignatureAlgorithms.HS256.generateKey();
String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();
```
@ -528,7 +529,7 @@ key algorithms - identified by the following names:
* `PS384`: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
* `PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
These are all represented in the `io.jsonwebtoken.SignatureAlgorithm` enum.
These are all represented in the `io.jsonwebtoken.security.SignatureAlgorithms` enum class.
What's really important about these algorithms - other than their security properties - is that the JWT specification
[RFC 7518, Sections 3.2 through 3.5](https://tools.ietf.org/html/rfc7518#section-3)
@ -1183,7 +1184,10 @@ how to resolve your `CompressionCodec` to decompress the JWT.
Please see the [Compression](#compression) section below to see how to decompress JWTs during parsing.
<!-- TODO: ## Encrypted JWTs -->
<a name="jwe"></a>
## Encrypted JWTs
TODO: NOTE: A128GCM, A192GCM, A256GCM algorithms require JDK 8 or BouncyCastle.
<a name="compression"></a>
## Compression

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -25,9 +25,11 @@ package io.jsonwebtoken;
public interface CompressionCodec {
/**
* The compression algorithm name to use as the JWT's {@code zip} header value.
* The algorithm name to use as the JWT's
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a> header value.
*
* @return the compression algorithm name to use as the JWT's {@code zip} header value.
* @return the algorithm name to use as the JWT's
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a> header value.
*/
String getAlgorithmName();

View File

@ -33,7 +33,7 @@ import java.util.Map;
* <h3>Creation</h3>
*
* <p>It is easiest to create a {@code Header} instance by calling one of the
* {@link Jwts#header() JWTs.header()} factory methods.</p>
* {@link Jwts#header() Jwts.header()} factory methods.</p>
*
* @since 0.1
*/
@ -48,6 +48,14 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
public static final String CONTENT_TYPE = "cty";
/**
* JWT {@code Algorithm} header parameter name: <code>"alg"</code>.
*
* @see <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">JWS Algorithm Header</a>
* @see <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">JWE Algorithm Header</a>
*/
public static final String ALGORITHM = "alg";
/** JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code> */
public static final String COMPRESSION_ALGORITHM = "zip";
@ -109,7 +117,60 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
T setContentType(String cty);
/**
* Returns the JWT <code>zip</code> (Compression Algorithm) header value or {@code null} if not present.
* Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present.
*
* <ul>
* <li>If the JWT is a Signed JWT (a JWS), the <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">
* <code>alg</code></a> (Algorithm) header parameter identifies the cryptographic algorithm used to secure the
* JWS. Consider using
* {@link io.jsonwebtoken.security.SignatureAlgorithms#forName(String) SignatureAlgorithms.forName} to
* convert this string value to a type-safe enum instance.</li>
* <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (Algorithm) header parameter
* identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content
* Encryption Key (CEK). The encrypted content is not usable if the <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm</li>
* </ul>
*
* @return the {@code alg} header value or {@code null} if not present. This will always be
* {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction.
* @since JJWT_RELEASE_VERSION
*/
String getAlgorithm();
/**
* Sets the JWT <code>alg</code></a> (Algorithm) header value. A {@code null} value will remove the property
* from the JSON map.
* <ul>
* <li>If the JWT is a Signed JWT (a JWS), the <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">
* <code>alg</code></a> (Algorithm) header parameter identifies the cryptographic algorithm used to secure the
* JWS. Consider using
* {@link io.jsonwebtoken.security.SignatureAlgorithms#forName(String) SignatureAlgorithms.forName} to
* convert this string value to a type-safe enum instance.</li>
* <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (Algorithm) header parameter
* identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content
* Encryption Key (CEK). The encrypted content is not usable if the <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm</li>
* </ul>
*
* @param alg the {@code alg} header value
* @return this header for method chaining
* @since JJWT_RELEASE_VERSION
*/
T setAlgorithm(String alg);
/**
* Returns the JWT <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a>
* (Compression Algorithm) header parameter value or {@code null} if not present.
*
* <h3>Compatiblity Note</h3>
*
* <p>While the JWT family of specifications only defines the <code>zip</code> header in the JWE
* (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it.
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse the JWS</b>. However, compression when creating JWE tokens should be universally accepted for any library
* that supports JWE.</p>
*
* @return the {@code zip} header parameter value or {@code null} if not present.
* @since 0.6.0
@ -117,14 +178,23 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
String getCompressionAlgorithm();
/**
* Sets the JWT <code>zip</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
* Sets the JWT <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a>
* (Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
* <p>
* <p>The compression algorithm is NOT part of the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25">JWT specification</a>
* and must be used carefully since, is not expected that other libraries (including previous versions of this one)
* be able to deserialize a compressed JTW body correctly. </p>
* be able to deserialize a compressed JWT body correctly. </p>
*
* @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map.
* <h3>Compatibility Note</h3>
*
* <p>While the JWT family of specifications only defines the <code>zip</code> header in the JWE
* (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it.
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse the JWS</b>. However, Compression when creating JWE tokens should be universally accepted for any library
* that supports JWE.</p>
*
* @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the
* JSON map.
* @since 0.6.0
*/
T setCompressionAlgorithm(String zip);

View File

@ -0,0 +1,12 @@
package io.jsonwebtoken;
/**
* @param <B> payload type
* @since JJWT_RELEASE_VERSION
*/
public interface Jwe<B> extends Jwt<JweHeader,B> {
byte[] getInitializationVector();
byte[] getAadTag();
}

View File

@ -0,0 +1,88 @@
package io.jsonwebtoken;
/**
* A <a href="https://tools.ietf.org/html/rfc7516">JWE</a> header.
*
* @since JJWT_RELEASE_VERSION
*/
public interface JweHeader extends Header<JweHeader> {
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">Algorithm Header</a> name: the string literal <b><code>alg</code></b>
*/
public static final String ALGORITHM = "alg";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.2">Encryption Algorithm Header</a> name: the string literal <b><code>enc</code></b>
*/
public static final String ENCRYPTION_ALGORITHM = "enc";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3">Compression Algorithm Header</a> name: the string literal <b><code>zip</code></b>
*/
public static final String COMPRESSION_ALGORITHM = "zip";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">JWK Set URL Header</a> name: the string literal <b><code>jku</code></b>
*/
public static final String JWK_SET_URL = "jku";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.5">JSON Web Key Header</a> name: the string literal <b><code>jwk</code></b>
*/
public static final String JSON_WEB_KEY = "jwk";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.6">Key ID Header</a> name: the string literal <b><code>kid</code></b>
*/
public static final String KEY_ID = "kid";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.7">X.509 URL Header</a> name: the string literal <b><code>x5u</code></b>
*/
public static final String X509_URL = "x5u";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.8">X.509 Certificate Chain Header</a> name: the string literal <b><code>x5c</code></b>
*/
public static final String X509_CERT_CHAIN = "x5c";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.9">X.509 Certificate SHA-1 Thumbprint Header</a> name: the string literal <b><code>x5t</code></b>
*/
public static final String X509_CERT_SHA1_THUMBPRINT = "x5t";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.10">X.509 Certificate SHA-256 Thumbprint Header</a> name: the string literal <b><code>x5t#S256</code></b>
*/
public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
/**
* JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.13">Critical Header</a> name: the string literal <b><code>crit</code></b>
*/
public static final String CRITICAL = "crit";
/**
* Returns the JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.2"><code>enc</code></a> (Encryption
* Algorithm) header value or {@code null} if not present.
* <p>The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm
* used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE
* {@code Authentication Tag}.</p>
*
* @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will
* always be {@code non-null} on validly constructed JWE instances, but could be {@code null} during construction.
*/
String getEncryptionAlgorithm();
/**
* Sets the JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.2"><code>enc</code></a> (Encryption
* Algorithm) header value. A {@code null} value will remove the property from the JSON map.
* <p>The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm
* used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE
* {@code Authentication Tag}.</p>
*
* @param enc the encryption algorithm identifier
* @return this header for method chaining
*/
JweHeader setEncryptionAlgorithm(String enc);
}

View File

@ -16,74 +16,63 @@
package io.jsonwebtoken;
/**
* A <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31">JWS</a> header.
* A <a href="https://tools.ietf.org/html/rfc7515">JWS</a> header.
*
* @param <T> header type
* @since 0.1
*/
public interface JwsHeader<T extends JwsHeader<T>> extends Header<T> {
public interface JwsHeader extends Header<JwsHeader> {
/** JWS {@code Algorithm} header parameter name: <code>"alg"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">Algorithm Header</a> name: the string literal <b><code>alg</code></b>
*/
public static final String ALGORITHM = "alg";
/** JWS {@code JWT Set URL} header parameter name: <code>"jku"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.2">JWK Set URL Header</a> name: the string literal <b><code>jku</code></b>
*/
public static final String JWK_SET_URL = "jku";
/** JWS {@code JSON Web Key} header parameter name: <code>"jwk"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.3">JSON Web Key Header</a> name: the string literal <b><code>jwk</code></b>
*/
public static final String JSON_WEB_KEY = "jwk";
/** JWS {@code Key ID} header parameter name: <code>"kid"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">Key ID Header</a> name: the string literal <b><code>kid</code></b>
*/
public static final String KEY_ID = "kid";
/** JWS {@code X.509 URL} header parameter name: <code>"x5u"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.5">X.509 URL Header</a> name: the string literal <b><code>x5u</code></b>
*/
public static final String X509_URL = "x5u";
/** JWS {@code X.509 Certificate Chain} header parameter name: <code>"x5c"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.6">X.509 Certificate Chain Header</a> name: the string literal <b><code>x5c</code></b>
*/
public static final String X509_CERT_CHAIN = "x5c";
/** JWS {@code X.509 Certificate SHA-1 Thumbprint} header parameter name: <code>"x5t"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.7">X.509 Certificate SHA-1 Thumbprint Header</a> name: the string literal <b><code>x5t</code></b>
*/
public static final String X509_CERT_SHA1_THUMBPRINT = "x5t";
/** JWS {@code X.509 Certificate SHA-256 Thumbprint} header parameter name: <code>"x5t#S256"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.8">X.509 Certificate SHA-256 Thumbprint Header</a> name: the string literal <b><code>x5t#S256</code></b>
*/
public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
/** JWS {@code Critical} header parameter name: <code>"crit"</code> */
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.11">Critical Header</a> name: the string literal <b><code>crit</code></b>
*/
public static final String CRITICAL = "crit";
/**
* Returns the JWS <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.1">
* <code>alg</code></a> (algorithm) header value or {@code null} if not present.
*
* <p>The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
* using {@link io.jsonwebtoken.SignatureAlgorithm#forName(String) SignatureAlgorithm.forName} to convert this
* string value to a type-safe enum instance.</p>
*
* @return the JWS {@code alg} header value or {@code null} if not present. This will always be
* {@code non-null} on validly constructed JWS instances, but could be {@code null} during construction.
*/
String getAlgorithm();
/**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.1">
* <code>alg</code></a> (Algorithm) header value. A {@code null} value will remove the property from the JSON map.
*
* <p>The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
* using a type-safe {@link io.jsonwebtoken.SignatureAlgorithm SignatureAlgorithm} instance and using its
* {@link io.jsonwebtoken.SignatureAlgorithm#getValue() value} as the argument to this method.</p>
*
* @param alg the JWS {@code alg} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/
T setAlgorithm(String alg);
/**
* Returns the JWS <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.4">
* Returns the JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">
* <code>kid</code></a> (Key ID) header value or {@code null} if not present.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
* originators to explicitly signal a change of key to recipients. The structure of the keyId value is
* unspecified.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @return the JWS {@code kid} header value or {@code null} if not present.
@ -91,17 +80,15 @@ public interface JwsHeader<T extends JwsHeader<T>> extends Header<T> {
String getKeyId();
/**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.4">
* Sets the JWT <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">
* <code>kid</code></a> (Key ID) header value. A {@code null} value will remove the property from the JSON map.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
* originators to explicitly signal a change of key to recipients. The structure of the keyId value is
* unspecified.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @param kid the JWS {@code kid} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/
T setKeyId(String kid);
JwsHeader setKeyId(String kid);
}

View File

@ -21,7 +21,11 @@ import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureAlgorithms;
import java.security.Key;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Map;
@ -32,7 +36,27 @@ import java.util.Map;
*/
public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
//replaces any existing header with the specified header.
/**
* Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
*
* @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setProvider(Provider provider);
/**
* Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if
* a default {@link SecureRandom} should be used.
*
* @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or
* {@code null} if a default {@link SecureRandom} should be used.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setSecureRandom(SecureRandom secureRandom);
/**
* Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing
@ -41,7 +65,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @param header the header to set (and potentially replace any existing header).
* @return the builder for method chaining.
*/
JwtBuilder setHeader(Header header);
JwtBuilder setHeader(Header header); //replaces any existing header with the specified header.
/**
* Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing
@ -345,9 +369,9 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
/**
* Signs the constructed JWT with the specified key using the key's
* {@link SignatureAlgorithm#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the
* {@link SignatureAlgorithms#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the
* recommended signature algorithm isn't sufficient for your needs, consider using
* {@link #signWith(Key, SignatureAlgorithm)} instead.
* {@link #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)} instead.
*
* <p>If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA
* algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
@ -356,8 +380,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @param key the key to use for signing
* @return the builder instance for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
* @see #signWith(Key, SignatureAlgorithm)
* described by {@link SignatureAlgorithms#forSigningKey(Key)}.
* @see #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)
* @since 0.10.0
*/
JwtBuilder signWith(Key key) throws InvalidKeyException;
@ -368,17 +392,19 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <h4>Deprecation Notice: Deprecated as of 0.10.0</h4>
*
* <p>Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.</p>
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or
* {@link #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)}.</p>
*
* <p>This method will be removed in the 1.0 release.</p>
*
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param secretKey the algorithm-specific signing key to use to digitally sign the JWT.
* @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
* @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by
* the JWT specification.
* @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or
* {@link #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)}.
* This method will be removed in the 1.0 release.
*/
@Deprecated
@ -402,7 +428,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
*
* <p>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.</p>
*
* <p>See this
@ -414,7 +440,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <pre><code>
* byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
* Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
* jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
* jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
* </code></pre>
* </p>
*
@ -452,6 +478,12 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException;
/**
* <h3>Deprecation Notice</h3>
* <p><b>This has been deprecated since JJWT_RELEASE_VERSION. Use
* {@link #signWith(Key, io.jsonwebtoken.security.SignatureAlgorithm)} instead.</b>. Standard JWA algorithms
* are represented as instances of this new interface in the {@link io.jsonwebtoken.security.SignatureAlgorithms}
* enum class.</p>
*
* Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.
*
* <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
@ -465,9 +497,29 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* 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.
*
* <p>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.</p>
*
* @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,10 +529,10 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*
* <h3>Compatibility Warning</h3>
*
* <p>The JWT family of specifications defines compression only for JWE (Json Web Encryption)
* <p>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 <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse that JWS token</b>. When using compression for JWS tokens, be sure that that all parties accessing the
* parse that JWS token</b>. When using compression for JWS tokens, be sure that that all parties accessing the
* JWS token support compression for JWS.</p>
*
* <p>Compression when creating JWE tokens however should be universally accepted for any

View File

@ -19,6 +19,8 @@ import io.jsonwebtoken.io.Decoder;
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 @@ import java.util.Map;
*/
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

View File

@ -76,6 +76,30 @@ public final class Jwts {
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<String, Object> header) {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJweHeader", MAP_ARG, header);
}
/**
* Returns a new {@link Claims} instance to be used as a JWT body.
*

View File

@ -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();
}

View File

@ -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);
}

View File

@ -34,7 +34,9 @@ import java.util.List;
* <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31">JSON Web Algorithms</a> specification.
*
* @since 0.1
* @deprecated since JJWT_RELEASE_VERSION; use {@link io.jsonwebtoken.security.SignatureAlgorithms} instead.
*/
@Deprecated
public enum SignatureAlgorithm {
/**

View File

@ -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 @@ import io.jsonwebtoken.security.SecurityException;
* @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);

View File

@ -77,10 +77,11 @@ public final class Assert {
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static void notNull(Object object, String message) {
public static <T> T notNull(T object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
return object;
}
/**
@ -196,10 +197,11 @@ public final class Assert {
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;
}
/**

View File

@ -28,6 +28,17 @@ public final class Collections {
private Collections(){} //prevent instantiation
public static <T> List<T> emptyList() {
return java.util.Collections.emptyList();
}
public static <T> List<T> of(T... elements) {
if (elements == null || elements.length == 0) {
return java.util.Collections.emptyList();
}
return java.util.Collections.unmodifiableList(Arrays.asList(elements));
}
/**
* Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>.

View File

@ -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<T, K extends Key> extends AeadRequest<T, K>, AuthenticationTagSource {
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AeadEncryptionAlgorithm<T, EK extends Key, DK extends Key, EReq extends AeadRequest<T, EK>, ERes extends AeadEncryptionResult, DReq extends AeadDecryptionRequest<T, DK>> extends EncryptionAlgorithm<T, EK, DK, EReq, ERes, DReq> {
}

View File

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

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AeadIvEncryptionResult extends IvEncryptionResult, AeadEncryptionResult {
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AeadIvRequest<T, K extends Key> extends IvRequest<T, K>, AeadDecryptionRequest<T, K> {
}

View File

@ -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<T, K extends Key> extends CryptoRequest<T, K>, AssociatedDataSource {
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AeadSymmetricEncryptionAlgorithm<T> extends
SymmetricEncryptionAlgorithm<T, AeadRequest<T, SecretKey>, AeadIvEncryptionResult, AeadIvRequest<T, SecretKey>>,
AeadEncryptionAlgorithm<T, SecretKey, SecretKey, AeadRequest<T, SecretKey>, AeadIvEncryptionResult, AeadIvRequest<T, SecretKey>> {
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AsymmetricKeySignatureAlgorithm extends SignatureAlgorithm, AsymmetricKeyAlgorithm {
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface CryptoMessage<T> {
T getData(); //plaintext, ciphertext, or Key for key wrap algorithms
}

View File

@ -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<T, K extends Key> extends CryptoMessage<T> {
/**
* 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();
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface CurveId {
String toString();
}

View File

@ -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<String, CurveId> STANDARD_IDS = Collections.unmodifiableMap(Maps
.of(P256.toString(), P256)
.and(P384.toString(), P384)
.and(P521.toString(), P521)
.build());
private static final Set<CurveId> STANDARD_IDS_SET =
Collections.unmodifiableSet(new LinkedHashSet<>(STANDARD_IDS.values()));
public static Set<CurveId> 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);
}
}

View File

@ -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);
}

View File

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

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwk<T extends EcJwk> extends Jwk<T>, EcJwkMutator<T> {
CurveId getCurveId();
String getX();
String getY();
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkBuilder<T extends EcJwkBuilder, K extends EcJwk> extends JwkBuilder<T, K>, EcJwkMutator<T> {
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkBuilderFactory {
PublicEcJwkBuilder publicKey();
PrivateEcJwkBuilder privateKey();
}

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkMutator<T extends EcJwkMutator> extends JwkMutator<T> {
T setCurveId(CurveId curveId);
T setX(String x);
T setY(String y);
}

View File

@ -0,0 +1,15 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Named;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EncryptionAlgorithm<T, EK extends Key, DK extends Key, EReq extends CryptoRequest<T, EK>, ERes extends EncryptionResult, DReq extends CryptoRequest<T, DK>> extends Named {
ERes encrypt(EReq request) throws CryptoException, KeyException;
byte[] decrypt(DReq request) throws CryptoException, KeyException;
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.JweHeader;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EncryptionAlgorithmLocator {
EncryptionAlgorithm getEncryptionAlgorithm(JweHeader jweHeader);
}

View File

@ -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-<em>insensitive</em> name comparison of the specified JWE <code>enc</code> value.
*
* @param name the case-insensitive JWE <code>enc</code> header value.
* @return Returns the corresponding {@code EncryptionAlgorithmName} enum instance based on a
* case-<em>insensitive</em> name comparison of the specified JWE <code>enc</code> 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;
}
}

View File

@ -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
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.3">RFC 7518, Section 5.2.3</a>. 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
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.4">RFC 7518, Section 5.2.4</a>. 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
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.5">RFC 7518, Section 5.2.5</a>. This algorithm
* requires a 512 bit (64 byte) key.
*/
public static final AeadSymmetricEncryptionAlgorithm A256CBC_HS512 = hmac(256);
/**
* &quot;AES GCM using 128-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 128 bit (16 byte) key.
*/
public static final AeadSymmetricEncryptionAlgorithm A128GCM = gcm(128);
/**
* &quot;AES GCM using 192-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 192 bit (24 byte) key.
*/
public static final AeadSymmetricEncryptionAlgorithm A192GCM = gcm(192);
/**
* &quot;AES GCM using 256-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 256 bit (32 byte) key.
*/
public static final AeadSymmetricEncryptionAlgorithm A256GCM = gcm(256);
private static final Map<String, AeadSymmetricEncryptionAlgorithm> 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<AeadSymmetricEncryptionAlgorithm> symmetric() {
return SYMMETRIC_VALUES_BY_NAME.values();
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -23,4 +23,8 @@ public class InvalidKeyException extends KeyException {
public InvalidKeyException(String message) {
super(message);
}
public InvalidKeyException(String msg, Exception cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface IvEncryptionResult extends EncryptionResult, InitializationVectorSource {
byte[] compact();
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface IvRequest<T, K extends Key> extends CryptoRequest<T, K>, InitializationVectorSource {
}

View File

@ -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<T extends Jwk> extends Map<String, Object>, JwkMutator<T> {
String getType();
String getUse();
Set<String> getOperations();
String getAlgorithm();
String getId();
URI getX509Url();
List<String> getX509CertficateChain();
String getX509CertificateSha1Thumbprint();
String getX509CertificateSha256Thumbprint();
}

View File

@ -0,0 +1,10 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilder<T extends JwkBuilder, K extends Jwk> extends JwkMutator<T> {
K build();
}

View File

@ -0,0 +1,12 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilderFactory {
EcJwkBuilderFactory ellipticCurve();
SymmetricJwkBuilder symmetric();
}

View File

@ -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 extends JwkMutator> {
T setUse(String use);
T setOperations(Set<String> ops);
T setAlgorithm(String alg);
T setId(String id);
T setX509Url(URI uri);
T setX509CertificateChain(List<String> chain);
T setX509CertificateSha1Thumbprint(String thumbprint);
T setX509CertificateSha256Thumbprint(String thumbprint);
}

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.security;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfo extends Map<String,Object>, JwkRsaPrimeInfoMutator<JwkRsaPrimeInfo> {
String getPrime();
String getCrtExponent();
String getCrtCoefficient();
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfoBuilder extends JwkRsaPrimeInfoMutator<JwkRsaPrimeInfoBuilder> {
JwkRsaPrimeInfo build();
}

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfoMutator<T extends JwkRsaPrimeInfoMutator> {
T setPrime(String r);
T setCrtExponent(String d);
T setCrtCoefficient(String t);
}

View File

@ -0,0 +1,14 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Classes;
/**
* @since JJWT_RELEASE_VERSION
*/
public class Jwks {
public static <T extends JwkBuilderFactory> T builder() {
return Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkBuilderFactory");
}
}

View File

@ -23,4 +23,8 @@ public class KeyException extends SecurityException {
public KeyException(String message) {
super(message);
}
public KeyException(String msg, Exception cause) {
super(msg, cause);
}
}

View File

@ -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
* <a href="https://tools.ietf.org/html/rfc7518">JSON Web Algorithms</a> specification.
*
* @since JJWT_RELEASE_VERSION
*/
public enum KeyManagementAlgorithmName {
RSA1_5("RSA1_5", "RSAES-PKCS1-v1_5", Collections.<String>emptyList(), "RSA/ECB/PKCS1Padding"),
RSA_OAEP("RSA-OAEP", "RSAES OAEP using default parameters", Collections.<String>emptyList(), "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"),
RSA_OAEP_256("RSA-OAEP-256", "RSAES OAEP using SHA-256 and MGF1 with SHA-256", Collections.<String>emptyList(), "RSA/ECB/OAEPWithSHA-256AndMGF1Padding & MGF1ParameterSpec.SHA256"),
A128KW("A128KW", "AES Key Wrap with default initial value using 128-bit key", Collections.<String>emptyList(), "AESWrap"),
A192KW("A192KW", "AES Key Wrap with default initial value using 192-bit key", Collections.<String>emptyList(), "AESWrap"),
A256KW("A256KW", "AES Key Wrap with default initial value using 256-bit key", Collections.<String>emptyList(), "AESWrap"),
dir("dir", "Direct use of a shared symmetric key as the CEK", Collections.<String>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<String> moreHeaderParams;
private final String jcaName;
KeyManagementAlgorithmName(String value, String description, List<String> 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<String> 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-<em>insensitive</em> name comparison of the specified JWE <code>alg</code> value.
*
* @param name the case-insensitive JWE <code>alg</code> header value.
* @return Returns the corresponding {@code KeyManagementAlgorithmName} enum instance based on a
* case-<em>insensitive</em> name comparison of the specified JWE <code>alg</code> 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;
}
}

View File

@ -0,0 +1,42 @@
package io.jsonwebtoken.security;
/**
* An enum representing the {@code Key Management Mode} names defined in
* <a href="https://tools.ietf.org/html/rfc7516#section-2">RFC 7516, Section 2</a>.
*
* @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;
}
}

View File

@ -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 @@ import java.util.List;
*/
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<SignatureAlgorithm> 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 final class Keys {
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);
}
/**
* <h3>Deprecation Notice</h3>
* <p>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:
* <code><pre>
* {@link SignatureAlgorithms#HS256}.generateKey();
* {@link SignatureAlgorithms#HS384}.generateKey();
* {@link SignatureAlgorithms#HS512}.generateKey();
* </pre></code>
* Call those methods as needed instead of this {@code secretKeyFor} helper method. This helper method will be
* removed before the 1.0 final release.</p>
* <p>
* Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.
*
* <p><a href="https://tools.ietf.org/html/rfc7518#section-3.2">JWA Specification (RFC 7518), Section 3.2</a>
@ -124,23 +107,36 @@ public final class Keys {
*
* @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();
}
/**
* <h3>Deprecation Notice</h3>
* <p>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:
* <code><pre>
* {@link SignatureAlgorithms#RS256}.generateKeyPair();
* {@link SignatureAlgorithms#RS384}.generateKeyPair();
* {@link SignatureAlgorithms#RS256}.generateKeyPair();
* ... etc ...
* {@link SignatureAlgorithms#ES512}.generateKeyPair();
* </pre></code>
* Call those methods as needed instead of this {@code keyPairFor} helper method. This helper method will be
* removed before the 1.0 final release.</p>
* <p>
* Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
*
* <p>If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:</p>
@ -199,7 +195,7 @@ public final class Keys {
* </tr>
* <tr>
* <td>EC512</td>
* <td>512 bits</td>
* <td><b>521</b> bits</td>
* <td>{@code P-521}</td>
* <td>{@code secp521r1}</td>
* </tr>
@ -208,24 +204,17 @@ public final class Keys {
* @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();
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwk extends EcJwk<PrivateEcJwk>, PrivateEcJwkMutator<PrivateEcJwk> {
String getD();
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwkBuilder extends EcJwkBuilder<PrivateEcJwkBuilder, PrivateEcJwk> {
PrivateEcJwkBuilder setD(String d);
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwkMutator<T extends PrivateEcJwkMutator> extends EcJwkMutator<T> {
T setD(String d);
}

View File

@ -0,0 +1,23 @@
package io.jsonwebtoken.security;
import java.util.List;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateRsaJwk extends RsaJwk<PrivateRsaJwk>, PrivateRsaJwkMutator<PrivateRsaJwk> {
String getD();
String getP();
String getQ();
String getDP();
String getDQ();
String getQI();
List<JwkRsaPrimeInfo> getOtherPrimesInfo();
}

View File

@ -0,0 +1,23 @@
package io.jsonwebtoken.security;
import java.util.List;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateRsaJwkMutator<T extends PrivateRsaJwkMutator> extends RsaJwkMutator<T> {
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<JwkRsaPrimeInfo> infos);
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicEcJwk extends EcJwk<PublicEcJwk> {
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicEcJwkBuilder extends EcJwkBuilder<PublicEcJwkBuilder, PublicEcJwk> {
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicRsaJwk extends RsaJwk {
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface RsaJwk<T extends RsaJwk> extends Jwk<T>, RsaJwkMutator<T> {
String getModulus();
String getExponent();
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface RsaJwkMutator<T extends RsaJwkMutator> extends JwkMutator<T> {
T setModulus(String n);
T setExponent(String e);
}

View File

@ -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<byte[], Key> request) throws SignatureException, KeyException;
boolean verify(VerifySignatureRequest request) throws SignatureException, KeyException;
}

View File

@ -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<String, SignatureAlgorithm> toMap(SignatureAlgorithm... algs) {
Map<String, SignatureAlgorithm> m = new LinkedHashMap<>();
for (SignatureAlgorithm alg : algs) {
m.put(alg.getName(), alg);
}
return Collections.unmodifiableMap(m);
}
private static final Map<String, SignatureAlgorithm> 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-<em>insensitive</em> 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-<em>insensitive</em> 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:
*
* <table>
* <caption>Key Signature Algorithm</caption>
* <thead>
* <tr>
* <th>If the Key is a:</th>
* <th>And:</th>
* <th>With a key size of:</th>
* <th>The returned SignatureAlgorithm will be:</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")</code><sup>1</sup></td>
* <td>256 &lt;= size &lt;= 383 <sup>2</sup></td>
* <td>{@link SignatureAlgorithms#HS256 HS256}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")</code><sup>1</sup></td>
* <td>384 &lt;= size &lt;= 511</td>
* <td>{@link SignatureAlgorithms#HS384 HS384}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")</code><sup>1</sup></td>
* <td>512 &lt;= size</td>
* <td>{@link SignatureAlgorithms#HS512 HS512}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>256 &lt;= size &lt;= 383 <sup>3</sup></td>
* <td>{@link SignatureAlgorithms#ES256 ES256}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>384 &lt;= size &lt;= 520 <sup>4</sup></td>
* <td>{@link SignatureAlgorithms#ES384 ES384}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td><b>521</b> &lt;= size <sup>4</sup></td>
* <td>{@link SignatureAlgorithms#ES512 ES512}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>2048 &lt;= size &lt;= 3071 <sup>5,6</sup></td>
* <td>{@link SignatureAlgorithms#RS256 RS256}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>3072 &lt;= size &lt;= 4095 <sup>6</sup></td>
* <td>{@link SignatureAlgorithms#RS384 RS384}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>4096 &lt;= size <sup>5</sup></td>
* <td>{@link SignatureAlgorithms#RS512 RS512}</td>
* </tr>
* </tbody>
* </table>
* <p>Notes:</p>
* <ol>
* <li>{@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}. </li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.2">JWA Specification (RFC 7518,
* Section 3.2)</a> mandates that HMAC-SHA-* signing keys <em>MUST</em> be 256 bits or greater.
* {@code SecretKey}s with key lengths less than 256 bits will be rejected with an
* {@link WeakKeyException}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.4">JWA Specification (RFC 7518,
* Section 3.4)</a> mandates that ECDSA signing key lengths <em>MUST</em> be 256 bits or greater.
* {@code ECKey}s with key lengths less than 256 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>The ECDSA {@code P-521} curve does indeed use keys of <b>521</b> 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}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.3">JWA Specification (RFC 7518,
* Section 3.3)</a> mandates that RSA signing key lengths <em>MUST</em> be 2048 bits or greater.
* {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>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.</li>
* </ol>
* <p>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).</p>
* <p>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.</p>
*
* @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());
}
}

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.security;
/**
* @since 0.10.0
*/
@SuppressWarnings("deprecation")
public class SignatureException extends io.jsonwebtoken.SignatureException {
public SignatureException(String message) {

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricEncryptionAlgorithm<T, EReq extends CryptoRequest<T, SecretKey>, ERes extends IvEncryptionResult, DReq extends IvRequest<T, SecretKey>> extends EncryptionAlgorithm<T, SecretKey, SecretKey, EReq, ERes, DReq>, SymmetricKeyAlgorithm {
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwk extends Jwk<SymmetricJwk>, SymmetricJwkMutator<SymmetricJwk> {
String getK();
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwkBuilder extends JwkBuilder<SymmetricJwkBuilder, SymmetricJwk>, SymmetricJwkMutator<SymmetricJwkBuilder> {
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwkMutator<T extends SymmetricJwkMutator> extends JwkMutator<T> {
T setK(String k);
}

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricKeySignatureAlgorithm extends SignatureAlgorithm, SymmetricKeyAlgorithm {
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface VerifySignatureRequest extends CryptoRequest<byte[], Key> {
byte[] getSignature();
}

View File

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

View File

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

View File

@ -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()
}
}

View File

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

View File

@ -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()
}
}
}

View File

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

View File

@ -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()
}
}

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
@ -44,4 +44,13 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
@ -66,14 +66,6 @@
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<newVersion>
<file>
<!-- compare the previous version with the new 'deprecated' package -->
<path>${project.build.directory}/${project.artifactId}-${project.version}-deprecated.${project.packaging}</path>
</file>
</newVersion>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -37,7 +37,6 @@ public class JacksonDeserializer<T> implements Deserializer<T> {
private final Class<T> returnType;
private final ObjectMapper objectMapper;
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
public JacksonDeserializer() {
this(JacksonSerializer.DEFAULT_OBJECT_MAPPER);
}

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
@ -66,14 +66,6 @@
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<newVersion>
<file>
<!-- compare the previous version with the new 'deprecated' package -->
<path>${project.build.directory}/${project.artifactId}-${project.version}-deprecated.${project.packaging}</path>
</file>
</newVersion>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<version>0.12.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -53,14 +53,26 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
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

View File

@ -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<JweHeader> implements JweHeader {
public DefaultJweHeader() {
super();
}
public DefaultJweHeader(Map<String, Object> map) {
super(map);
}
@Override
public String getEncryptionAlgorithm() {
return getString(ENCRYPTION_ALGORITHM);
}
@Override
public JweHeader setEncryptionAlgorithm(String enc) {
setValue(ENCRYPTION_ALGORITHM, enc);
return this;
}
}

View File

@ -19,7 +19,7 @@ import io.jsonwebtoken.JwsHeader;
import java.util.Map;
public class DefaultJwsHeader extends DefaultHeader implements JwsHeader {
public class DefaultJwsHeader extends DefaultHeader<JwsHeader> implements JwsHeader {
public DefaultJwsHeader() {
super();
@ -29,17 +29,6 @@ public class DefaultJwsHeader extends DefaultHeader implements JwsHeader {
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);

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