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)
# 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.
@ -8,7 +8,7 @@ JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ie
## 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:
@ -16,7 +16,7 @@ Maven:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.4</version>
<version>0.5</version>
</dependency>
```
@ -24,7 +24,7 @@ Gradle:
```groovy
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
import io.jsonwebtoken.Jwts;
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
// the key would be read from your application configuration instead.
byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
byte[] key = MacProvider.generateKey();
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!?
@ -98,6 +98,20 @@ These feature sets will be implemented in a future release when possible. Commu
## 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
- [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) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
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.keyBytes = secretKey;
return this;
@ -99,6 +99,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) {
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);
return signWith(alg, bytes);
}

View File

@ -25,6 +25,11 @@ import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
/**
* ElliptiCurve crypto provider.
*
* @since 0.5
*/
public abstract class EllipticCurveProvider extends SignatureProvider {
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.");
}
/**
* 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() {
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) {
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) {
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.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm argument must represent an Elliptic Curve algorithm.");
try {

View File

@ -22,7 +22,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Signature;
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.");
}
/**
* 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() {
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) {
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) {
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
byte[] bytes;
switch(alg) {
switch (alg) {
case HS256:
bytes = new byte[32];
break;

View File

@ -83,22 +83,72 @@ public abstract class RsaProvider extends SignatureProvider {
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() {
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) {
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) {
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;
try {
keyGenerator = KeyPairGenerator.getInstance(jcaAlgName);
keyGenerator = KeyPairGenerator.getInstance(jcaAlgorithmName);
} catch (NoSuchAlgorithmException 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.SecureRandom;
import java.security.Signature;
import java.util.Random;
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;
static {

View File

@ -21,6 +21,7 @@ import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
import static org.junit.Assert.*
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
}
}
}