Skip to content

Commit

Permalink
Readme and JavaDoc updates for the upcoming 0.5 release
Browse files Browse the repository at this point in the history
  • Loading branch information
lhazlewood committed May 13, 2015
1 parent 078dbaa commit cafbc29
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 14 deletions.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
[![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 is a 'clean room' implementation based solely on the [JWT](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25), [JWS](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31), [JWE](https://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-31) and [JWA](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) RFC draft specifications.

## 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:

```xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.4</version>
<version>0.5</version>
</dependency>
```

Gradle:

```groovy
dependencies {
compile 'io.jsonwebtoken:jjwt:0.4'
compile 'io.jsonwebtoken:jjwt:0.5'
}
```

Expand All @@ -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!?
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public JwtBuilder setHeaderParam(String name, Object value) {
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;
Expand All @@ -99,6 +99,7 @@ public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
@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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
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();
Expand All @@ -42,19 +47,83 @@ protected EllipticCurveProvider(SignatureAlgorithm alg, Key key) {
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 {
Expand Down
44 changes: 42 additions & 2 deletions src/main/java/io/jsonwebtoken/impl/crypto/MacProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Signature;

public abstract class MacProvider extends SignatureProvider {

Expand All @@ -31,21 +30,62 @@ protected MacProvider(SignatureAlgorithm alg, Key key) {
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;
Expand Down
54 changes: 52 additions & 2 deletions src/main/java/io/jsonwebtoken/impl/crypto/RsaProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,72 @@ protected void doSetParameter(Signature sig, PSSParameterSpec spec) throws Inval
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);
}
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,22 @@
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 {
Expand Down
23 changes: 23 additions & 0 deletions src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

}
}

0 comments on commit cafbc29

Please sign in to comment.