Readme and JavaDoc updates for the upcoming 0.5 release

This commit is contained in:
Les Hazlewood 2015-05-12 18:49:43 -07:00
parent 078dbaa9c2
commit cafbc29a76
7 changed files with 223 additions and 14 deletions

View File

@ -1,6 +1,6 @@
[![Build Status](https://travis-ci.org/jwtk/jjwt.svg?branch=master)](https://travis-ci.org/jwtk/jjwt) [![Build Status](https://travis-ci.org/jwtk/jjwt.svg?branch=master)](https://travis-ci.org/jwtk/jjwt)
# Java JWT: JSON Web Token for Java # Java JWT: JSON Web Token for Java and Android
JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM. JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM.
@ -8,7 +8,7 @@ JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ie
## Installation ## Installation
Use your favorite Maven-compatible build tool to pull the dependency (and its transitive dependencies) from Maven Central. Use your favorite Maven-compatible build tool to pull the dependency (and its transitive dependencies) from Maven Central:
Maven: Maven:
@ -16,7 +16,7 @@ Maven:
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
<version>0.4</version> <version>0.5</version>
</dependency> </dependency>
``` ```
@ -24,7 +24,7 @@ Gradle:
```groovy ```groovy
dependencies { dependencies {
compile 'io.jsonwebtoken:jjwt:0.4' compile 'io.jsonwebtoken:jjwt:0.5'
} }
``` ```
@ -37,13 +37,13 @@ Most complexity is hidden behind a convenient and readable builder-based [fluent
```java ```java
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.MacProvider;
// We need a signing key, so we'll create one just for this example. Usually // 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. // the key would be read from your application configuration instead.
byte[] key = new byte[64]; byte[] key = MacProvider.generateKey();
new SecureRandom().nextBytes(key);
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact(); String s = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS512, key).compact();
``` ```
How easy was that!? How easy was that!?
@ -98,6 +98,20 @@ These feature sets will be implemented in a future release when possible. Commu
## Release Notes ## Release Notes
### 0.5
- Android support! Android's built-in Base64 codec will be used if JJWT detects it is running in an Android environment. Other than Base64, all other parts of JJWT were already Android-compliant. Now it is fully compliant.
- Elliptic Curve signature algorithms! `SignatureAlgorithm.ES256`, `ES384` and `ES512` are now supported.
- Super convenient key generation methods, so you don't have to worry how to do this safely:
-- `MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)`
-- `RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)`
-- `EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)`
The `generate`* methods that accept an `SignatureAlgorithm` argument know to generate a key of sufficient strength that reflects the specified algorithm strength.
- *100% LINE TEST COVERAGE!* every line of JJWT code (excluding generic `lang` package language helper code) is guaranteed to be executed during a build. The `cobertura` maven plugin enforces 100% coverage for all new code in the future too. This means that JJWT will be stable and regression tested for all future releases, ensuring a stable (and cryptographically sound) codebase for the long future.
### 0.4 ### 0.4
- [Issue 8](https://github.com/jwtk/jjwt/issues/8): Add ability to find signing key by inspecting the JWS values before verifying the signature. - [Issue 8](https://github.com/jwtk/jjwt/issues/8): Add ability to find signing key by inspecting the JWS values before verifying the signature.

View File

@ -90,7 +90,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) { public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null."); Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notEmpty(secretKey, "secret key byte array cannot be null or empty."); Assert.notEmpty(secretKey, "secret key byte array cannot be null or empty.");
Assert.isTrue(!alg.isRsa(), "Key bytes cannot be specified for RSA signatures. Please specify an RSAPrivateKey instance."); Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
this.algorithm = alg; this.algorithm = alg;
this.keyBytes = secretKey; this.keyBytes = secretKey;
return this; return this;
@ -99,6 +99,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
@Override @Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) { public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty."); Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey); byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes); return signWith(alg, bytes);
} }

View File

@ -25,6 +25,11 @@ import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* ElliptiCurve crypto provider.
*
* @since 0.5
*/
public abstract class EllipticCurveProvider extends SignatureProvider { public abstract class EllipticCurveProvider extends SignatureProvider {
private static final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames(); private static final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames();
@ -42,19 +47,83 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm."); Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
} }
/**
* Generates a new secure-random key pair assuming strength enough for the {@link
* SignatureAlgorithm#ES512} algorithm. This is a convenience method that immediately delegates to {@link
* #generateKeyPair(SignatureAlgorithm)} using {@link SignatureAlgorithm#ES512} as the method argument.
*
* @return a new secure-randomly-generated key pair assuming strength enough for the {@link
* SignatureAlgorithm#ES512} algorithm.
* @see #generateKeyPair(SignatureAlgorithm)
* @see #generateKeyPair(SignatureAlgorithm, SecureRandom)
* @see #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom)
*/
public static KeyPair generateKeyPair() { public static KeyPair generateKeyPair() {
return generateKeyPair(SignatureAlgorithm.ES512); return generateKeyPair(SignatureAlgorithm.ES512);
} }
/**
* Generates a new secure-random key pair of sufficient strength for the specified Elliptic Curve {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using JJWT's default {@link
* SignatureProvider#DEFAULT_SECURE_RANDOM SecureRandom instance}. This is a convenience method that immediately
* delegates to {@link #generateKeyPair(SignatureAlgorithm, SecureRandom)}.
*
* @param alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or {@code ES512}
* @return a new secure-randomly generated key pair of sufficient strength for the specified {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using JJWT's default {@link
* SignatureProvider#DEFAULT_SECURE_RANDOM SecureRandom instance}.
* @see #generateKeyPair()
* @see #generateKeyPair(SignatureAlgorithm, SecureRandom)
* @see #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom)
*/
public static KeyPair generateKeyPair(SignatureAlgorithm alg) { public static KeyPair generateKeyPair(SignatureAlgorithm alg) {
return generateKeyPair(alg, SignatureProvider.DEFAULT_SECURE_RANDOM); return generateKeyPair(alg, SignatureProvider.DEFAULT_SECURE_RANDOM);
} }
/**
* Generates a new secure-random key pair of sufficient strength for the specified Elliptic Curve {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using the specified {@link
* SecureRandom} random number generator. This is a convenience method that immediately delegates to {@link
* #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom)} using {@code "ECDSA"} as the {@code
* jcaAlgorithmName} and {@code "BC"} as the {@code jcaProviderName} since EllipticCurve requires the use of an
* external JCA provider ({@code BC stands for BouncyCastle}. This will work as expected as long as the
* BouncyCastle dependency is in the runtime classpath.
*
* @param alg alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or {@code
* ES512}
* @param random the SecureRandom generator to use during key generation.
* @return a new secure-randomly generated key pair of sufficient strength for the specified {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using the specified {@link
* SecureRandom} random number generator.
* @see #generateKeyPair()
* @see #generateKeyPair(SignatureAlgorithm)
* @see #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom)
*/
public static KeyPair generateKeyPair(SignatureAlgorithm alg, SecureRandom random) { public static KeyPair generateKeyPair(SignatureAlgorithm alg, SecureRandom random) {
return generateKeyPair("ECDSA", "BC", alg, random); return generateKeyPair("ECDSA", "BC", alg, random);
} }
public static KeyPair generateKeyPair(String jcaAlgorithmName, String jcaProviderName, SignatureAlgorithm alg, SecureRandom random) { /**
* Generates a new secure-random key pair of sufficient strength for the specified Elliptic Curve {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using the specified {@link
* SecureRandom} random number generator via the specified JCA provider and algorithm name.
*
* @param jcaAlgorithmName the JCA name of the algorithm to use for key pair generation, for example, {@code
* ECDSA}.
* @param jcaProviderName the JCA provider name of the algorithm implementation, for example {@code BC} for
* BouncyCastle.
* @param alg alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or
* {@code ES512}
* @param random the SecureRandom generator to use during key generation.
* @return a new secure-randomly generated key pair of sufficient strength for the specified Elliptic Curve {@link
* SignatureAlgorithm} (must be one of {@code ES256}, {@code ES384} or {@code ES512}) using the specified {@link
* SecureRandom} random number generator via the specified JCA provider and algorithm name.
* @see #generateKeyPair()
* @see #generateKeyPair(SignatureAlgorithm)
* @see #generateKeyPair(SignatureAlgorithm, SecureRandom)
*/
public static KeyPair generateKeyPair(String jcaAlgorithmName, String jcaProviderName, SignatureAlgorithm alg,
SecureRandom random) {
Assert.notNull(alg, "SignatureAlgorithm argument cannot be null."); Assert.notNull(alg, "SignatureAlgorithm argument cannot be null.");
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm argument must represent an Elliptic Curve algorithm."); Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm argument must represent an Elliptic Curve algorithm.");
try { try {

View File

@ -22,7 +22,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.security.Key; import java.security.Key;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Signature;
public abstract class MacProvider extends SignatureProvider { public abstract class MacProvider extends SignatureProvider {
@ -31,21 +30,62 @@ public abstract class MacProvider extends SignatureProvider {
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm must be a HMAC SHA algorithm."); Assert.isTrue(alg.isHmac(), "SignatureAlgorithm must be a HMAC SHA algorithm.");
} }
/**
* Generates a new secure-random 512 bit secret key suitable for creating and verifying HMAC signatures. This is a
* convenience method that immediately delegates to {@link #generateKey(SignatureAlgorithm)} using {@link
* SignatureAlgorithm#HS512} as the method argument.
*
* @return a new secure-random 512 bit secret key suitable for creating and verifying HMAC signatures.
* @see #generateKey(SignatureAlgorithm)
* @see #generateKey(SignatureAlgorithm, SecureRandom)
* @since 0.5
*/
public static SecretKey generateKey() { public static SecretKey generateKey() {
return generateKey(SignatureAlgorithm.HS512); return generateKey(SignatureAlgorithm.HS512);
} }
/**
* Generates a new secure-random secret key of a length suitable for creating and verifying HMAC signatures
* according to the specified {@code SignatureAlgorithm} using JJWT's default {@link
* SignatureProvider#DEFAULT_SECURE_RANDOM SecureRandom instance}. This is a convenience method that immediately
* delegates to {@link #generateKey(SignatureAlgorithm, SecureRandom)}.
*
* @param alg the desired signature algorithm
* @return a new secure-random secret key of a length suitable for creating and verifying HMAC signatures according
* to the specified {@code SignatureAlgorithm} using JJWT's default {@link SignatureProvider#DEFAULT_SECURE_RANDOM
* SecureRandom instance}.
* @see #generateKey()
* @see #generateKey(SignatureAlgorithm, SecureRandom)
* @since 0.5
*/
public static SecretKey generateKey(SignatureAlgorithm alg) { public static SecretKey generateKey(SignatureAlgorithm alg) {
return generateKey(alg, SignatureProvider.DEFAULT_SECURE_RANDOM); return generateKey(alg, SignatureProvider.DEFAULT_SECURE_RANDOM);
} }
/**
* Generates a new secure-random secret key of a length suitable for creating and verifying HMAC signatures
* according to the specified {@code SignatureAlgorithm} using the specified SecureRandom number generator. This
* implementation returns secure-random key sizes as follows:
*
* <table> <thead> <tr> <th>Signature Algorithm</th> <th>Generated Key Size</th> </tr> </thead> <tbody> <tr>
* <td>HS256</td> <td>256 bits (32 bytes)</td> </tr> <tr> <td>HS384</td> <td>384 bits (48 bytes)</td> </tr> <tr>
* <td>HS512</td> <td>256 bits (64 bytes)</td> </tr> </tbody> </table>
*
* @param alg the signature algorithm that will be used with the generated key
* @param random the secure random number generator used during key generation
* @return a new secure-random secret key of a length suitable for creating and verifying HMAC signatures according
* to the specified {@code SignatureAlgorithm} using the specified SecureRandom number generator.
* @see #generateKey()
* @see #generateKey(SignatureAlgorithm)
* @since 0.5
*/
public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) { public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) {
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm."); Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
byte[] bytes; byte[] bytes;
switch(alg) { switch (alg) {
case HS256: case HS256:
bytes = new byte[32]; bytes = new byte[32];
break; break;

View File

@ -83,22 +83,72 @@ public abstract class RsaProvider extends SignatureProvider {
sig.setParameter(spec); sig.setParameter(spec);
} }
/**
* Generates a new RSA secure-random 4096 bit key pair. 4096 bits is JJWT's current recommended minimum key size
* for use in modern applications (during or after year 2015). This is a convenience method that immediately
* delegates to {@link #generateKeyPair(int)}.
*
* @return a new RSA secure-random 4096 bit key pair.
* @see #generateKeyPair(int)
* @see #generateKeyPair(int, SecureRandom)
* @see #generateKeyPair(String, int, SecureRandom)
* @since 0.5
*/
public static KeyPair generateKeyPair() { public static KeyPair generateKeyPair() {
return generateKeyPair(4096); return generateKeyPair(4096);
} }
/**
* Generates a new RSA secure-randomly key pair of the specified size using JJWT's default {@link
* SignatureProvider#DEFAULT_SECURE_RANDOM SecureRandom instance}. This is a convenience method that immediately
* delegates to {@link #generateKeyPair(int, SecureRandom)}.
*
* @param keySizeInBits the key size in bits (<em>NOT bytes</em>).
* @return a new RSA secure-random key pair of the specified size.
* @see #generateKeyPair()
* @see #generateKeyPair(int, SecureRandom)
* @see #generateKeyPair(String, int, SecureRandom)
* @since 0.5
*/
public static KeyPair generateKeyPair(int keySizeInBits) { public static KeyPair generateKeyPair(int keySizeInBits) {
return generateKeyPair(keySizeInBits, SignatureProvider.DEFAULT_SECURE_RANDOM); return generateKeyPair(keySizeInBits, SignatureProvider.DEFAULT_SECURE_RANDOM);
} }
/**
* Generates a new RSA secure-random key pair of the specified size using the given SecureRandom number generator.
* This is a convenience method that immediately delegates to {@link #generateKeyPair(String, int, SecureRandom)}
* using {@code RSA} as the {@code jcaAlgorithmName} argument.
*
* @param keySizeInBits the key size in bits (<em>NOT bytes</em>)
* @param random the secure random number generator to use during key generation.
* @return a new RSA secure-random key pair of the specified size using the given SecureRandom number generator.
* @see #generateKeyPair()
* @see #generateKeyPair(int)
* @see #generateKeyPair(String, int, SecureRandom)
* @since 0.5
*/
public static KeyPair generateKeyPair(int keySizeInBits, SecureRandom random) { public static KeyPair generateKeyPair(int keySizeInBits, SecureRandom random) {
return generateKeyPair("RSA", keySizeInBits, random); return generateKeyPair("RSA", keySizeInBits, random);
} }
protected static KeyPair generateKeyPair(String jcaAlgName, int keySizeInBits, SecureRandom random) { /**
* Generates a new secure-random key pair of the specified size using the specified SecureRandom according to the
* specified {@code jcaAlgorithmName}.
*
* @param jcaAlgorithmName the name of the JCA algorithm to use for key pair generation, for example, {@code RSA}.
* @param keySizeInBits the key size in bits (<em>NOT bytes</em>)
* @param random the SecureRandom generator to use during key generation.
* @return a new secure-randomly generated key pair of the specified size using the specified SecureRandom according
* to the specified {@code jcaAlgorithmName}.
* @see #generateKeyPair()
* @see #generateKeyPair(int)
* @see #generateKeyPair(int, SecureRandom)
* @since 0.5
*/
protected static KeyPair generateKeyPair(String jcaAlgorithmName, int keySizeInBits, SecureRandom random) {
KeyPairGenerator keyGenerator; KeyPairGenerator keyGenerator;
try { try {
keyGenerator = KeyPairGenerator.getInstance(jcaAlgName); keyGenerator = KeyPairGenerator.getInstance(jcaAlgorithmName);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to obtain an RSA KeyPairGenerator: " + e.getMessage(), e); throw new IllegalStateException("Unable to obtain an RSA KeyPairGenerator: " + e.getMessage(), e);
} }

View File

@ -24,10 +24,22 @@ import java.security.Key;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Signature; import java.security.Signature;
import java.util.Random;
abstract class SignatureProvider { abstract class SignatureProvider {
/**
* JJWT's default SecureRandom number generator. This RNG is initialized using the JVM default as follows:
*
* <pre><code>
* static {
* DEFAULT_SECURE_RANDOM = new SecureRandom();
* DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]);
* }
* </code></pre>
*
* <p><code>nextBytes</code> is called to force the RNG to initialize itself if not already initialized. The
* byte array is not used and discarded immediately for garbage collection.</p>
*/
public static final SecureRandom DEFAULT_SECURE_RANDOM; public static final SecureRandom DEFAULT_SECURE_RANDOM;
static { static {

View File

@ -21,6 +21,7 @@ import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.crypto.MacProvider import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test import org.junit.Test
import static org.junit.Assert.* import static org.junit.Assert.*
class DefaultJwtBuilderTest { class DefaultJwtBuilderTest {
@ -174,4 +175,26 @@ class DefaultJwtBuilderTest {
} }
} }
@Test
void testSignWithBytesWithoutHmac() {
def bytes = new byte[16];
try {
new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, bytes);
fail()
} catch (IllegalArgumentException iae) {
assertEquals "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message
}
}
@Test
void testSignWithBase64EncodedBytesWithoutHmac() {
try {
new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, 'foo');
fail()
} catch (IllegalArgumentException iae) {
assertEquals "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message
}
}
} }