Merge pull request #373 from jwtk/375-bc-optional

Ensure BouncyCastle is optional
This commit is contained in:
Les Hazlewood 2018-08-02 17:27:42 -04:00 committed by GitHub
commit 1520ae8a21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 107 additions and 113 deletions

View File

@ -3,13 +3,16 @@
#sudo: required #sudo: required
language: java language: java
jdk: jdk:
- openjdk7 - oraclejdk7
- oraclejdk8 - oraclejdk8
- oraclejdk9 - oraclejdk9
- oraclejdk10 - oraclejdk10
- openjdk10 - openjdk10
before_install: before_install:
- if [ "${TRAVIS_JDK_VERSION}" == "oraclejdk7" ]; then export MAVEN_OPTS="-Dhttps.protocols=TLSv1.2 -Xmx512m -XX:MaxPermSize=128m"; fi
- if [ "${TRAVIS_JDK_VERSION}" == "oraclejdk7" ]; then export JAVA_HOME="/usr/lib/jvm/java-7-oracle"; export PATH="${JAVA_HOME}/bin:${PATH}"; fi
- if [ "${TRAVIS_JDK_VERSION}" == "oraclejdk7" ]; then test ! -d "${JAVA_HOME}" && (curl http://ftp.osuosl.org/pub/funtoo/distfiles/oracle-java/jdk-7u80-linux-x64.tar.gz | sudo tar xz -C /usr/lib/jvm; sudo mv /usr/lib/jvm/jdk1.7.0_80 "${JAVA_HOME}"); fi
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'oraclejdk8' ] && echo 'true')" - export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'oraclejdk8' ] && echo 'true')"
install: true install: true

View File

@ -1,5 +1,16 @@
## Release Notes ## Release Notes
### 0.10.1
This is a minor point release that ensures the BouncyCastle dependency is optional and not pulled in as a transitive
dependency into projects.
Internal implementation code (not impacting the JJWT API) and documentation was also updated to reflect that all
Elliptic Curve algorithms are standard on the JDK and do not require Bouncy Castle.
Bouncy Castle is only needed when using PS256, PS384, and PS512 signature algorithms on < JDK 11.
[JDK 11 and later](https://bugs.openjdk.java.net/browse/JDK-8146293) supports these algorithms natively.
### 0.10.0 ### 0.10.0
This is a fairly large feature enhancement release that enables the following: This is a fairly large feature enhancement release that enables the following:

View File

@ -84,17 +84,17 @@ enforcement.
* HS256: HMAC using SHA-256 * HS256: HMAC using SHA-256
* HS384: HMAC using SHA-384 * HS384: HMAC using SHA-384
* HS512: HMAC using SHA-512 * HS512: HMAC using SHA-512
* ES256: ECDSA using P-256 and SHA-256
* ES384: ECDSA using P-384 and SHA-384
* ES512: ECDSA using P-521 and SHA-512
* RS256: RSASSA-PKCS-v1_5 using SHA-256 * RS256: RSASSA-PKCS-v1_5 using SHA-256
* RS384: RSASSA-PKCS-v1_5 using SHA-384 * RS384: RSASSA-PKCS-v1_5 using SHA-384
* RS512: RSASSA-PKCS-v1_5 using SHA-512 * RS512: RSASSA-PKCS-v1_5 using SHA-512
* PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256<sup>1</sup> * PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256<sup>1</sup>
* PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384<sup>1</sup> * PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384<sup>1</sup>
* PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512<sup>1</sup> * PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512<sup>1</sup>
* ES256: ECDSA using P-256 and SHA-256<sup>1</sup>
* ES384: ECDSA using P-384 and SHA-384<sup>1</sup>
* ES512: ECDSA using P-521 and SHA-512<sup>1</sup>
<sup>1. Requires a compatible JCA Provider (like BouncyCastle) in the runtime classpath.</sup> <sup>1. Requires JDK 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.</sup>
* Convenience enhancements beyond the specification such as * Convenience enhancements beyond the specification such as
* Body compression for any large JWT, not just JWEs * Body compression for any large JWT, not just JWEs
* Claims assertions (requiring specific values) * Claims assertions (requiring specific values)
@ -179,22 +179,21 @@ If you're building a (non-Android) JDK project, you will want to define the foll
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
<version>0.10.0</version> <version>0.10.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId> <artifactId>jjwt-impl</artifactId>
<version>0.10.0</version> <version>0.10.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <artifactId>jjwt-jackson</artifactId>
<version>0.10.0</version> <version>0.10.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- Uncomment this next dependency if you want to use Elliptic Curve (ES256, ES384, ES512) <!-- Uncomment this next dependency if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
or RSASSA-PSS (PS256, PS384, PS512) algorithms:
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
@ -205,18 +204,16 @@ If you're building a (non-Android) JDK project, you will want to define the foll
``` ```
Note: The above `jjwt-jackson` dependency requires Jackson 2.x.
<a name="install-jdk-gradle"></a> <a name="install-jdk-gradle"></a>
#### Gradle #### Gradle
```groovy ```groovy
dependencies { dependencies {
compile 'io.jsonwebtoken:jjwt-api:0.10.0' compile 'io.jsonwebtoken:jjwt-api:0.10.1'
runtime 'io.jsonwebtoken:jjwt-impl:0.10.0', runtime 'io.jsonwebtoken:jjwt-impl:0.10.1',
// Uncomment the next line if you want to use Elliptic Curve or RSASSA-PSS algorithms: // Uncomment the next line if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
//'org.bouncycastle:bcprov-jdk15on:1.60', //'org.bouncycastle:bcprov-jdk15on:1.60',
'io.jsonwebtoken:jjwt-jackson:0.10.0' 'io.jsonwebtoken:jjwt-jackson:0.10.1'
} }
``` ```
@ -232,12 +229,12 @@ Add the dependencies to your project:
```groovy ```groovy
dependencies { dependencies {
compile 'io.jsonwebtoken:jjwt-api:0.10.0' compile 'io.jsonwebtoken:jjwt-api:0.10.1'
runtime 'io.jsonwebtoken:jjwt-impl:0.10.0' runtime 'io.jsonwebtoken:jjwt-impl:0.10.1'
runtime('io.jsonwebtoken:jjwt-orgjson:0.10.0') { runtime('io.jsonwebtoken:jjwt-orgjson:0.10.1') {
exclude group: 'org.json', module: 'json' //provided by Android natively exclude group: 'org.json', module: 'json' //provided by Android natively
} }
// Uncomment the next line if you want to use Elliptic Curve or RSASSA-PSS algorithms: // Uncomment the next line if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
//runtime 'org.bouncycastle:bcprov-jdk15on:1.60' //runtime 'org.bouncycastle:bcprov-jdk15on:1.60'
} }
``` ```
@ -433,15 +430,15 @@ key algorithms - identified by the following names:
* `HS256`: HMAC using SHA-256 * `HS256`: HMAC using SHA-256
* `HS384`: HMAC using SHA-384 * `HS384`: HMAC using SHA-384
* `HS512`: HMAC using SHA-512 * `HS512`: HMAC using SHA-512
* `ES256`: ECDSA using P-256 and SHA-256
* `ES384`: ECDSA using P-384 and SHA-384
* `ES512`: ECDSA using P-521 and SHA-512
* `RS256`: RSASSA-PKCS-v1_5 using SHA-256 * `RS256`: RSASSA-PKCS-v1_5 using SHA-256
* `RS384`: RSASSA-PKCS-v1_5 using SHA-384 * `RS384`: RSASSA-PKCS-v1_5 using SHA-384
* `RS512`: RSASSA-PKCS-v1_5 using SHA-512 * `RS512`: RSASSA-PKCS-v1_5 using SHA-512
* `PS256`: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 * `PS256`: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
* `PS384`: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 * `PS384`: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
* `PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 * `PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
* `ES256`: ECDSA using P-256 and SHA-256
* `ES384`: ECDSA using P-384 and SHA-384
* `ES512`: ECDSA using P-521 and SHA-512
These are all represented in the `io.jsonwebtoken.SignatureAlgorithm` enum. These are all represented in the `io.jsonwebtoken.SignatureAlgorithm` enum.
@ -549,10 +546,10 @@ KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); //or RS384, RS512,
You use the private key (`keyPair.getPrivate()`) to create a JWS and the public key (`keyPair.getPublic()`) to You use the private key (`keyPair.getPrivate()`) to create a JWS and the public key (`keyPair.getPublic()`) to
parse/verify a JWS. parse/verify a JWS.
**NOTE: The `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, and `ES512` algorithms are not provided by the JDK by **NOTE: The `PS256`, `PS384`, and `PS512` algorithms require JDK 11 or a compatible JCA Provider
default.** If you want to use them, you must enable a JCA provider in the JDK that supports those algorithms (like BouncyCastle) in the runtime classpath.** If you are using JDK 10 or earlier and you want to use them, see
(such as BouncyCastle). See the [Installation](#Installation) section to see how to enable BouncyCastle if you want the [Installation](#Installation) section to see how to enable BouncyCastle. All other algorithms are natively
to usse these algorithms. supported by the JDK.
<a name="jws-create"></a> <a name="jws-create"></a>
### Creating a JWS ### Creating a JWS
@ -1195,7 +1192,7 @@ scope which is the typical JJWT default). That is:
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <artifactId>jjwt-jackson</artifactId>
<version>0.10.0</version> <version>0.10.1</version>
<scope>compile</scope> <!-- Not runtime --> <scope>compile</scope> <!-- Not runtime -->
</dependency> </dependency>
``` ```
@ -1204,7 +1201,7 @@ scope which is the typical JJWT default). That is:
```groovy ```groovy
dependencies { dependencies {
compile 'io.jsonwebtoken:jjwt-jackson:0.10.0' compile 'io.jsonwebtoken:jjwt-jackson:0.10.1'
} }
``` ```

View File

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

View File

@ -15,7 +15,6 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.lang.RuntimeEnvironment;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
@ -74,25 +73,19 @@ public enum SignatureAlgorithm {
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048), RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048),
/** /**
* JWA algorithm name for {@code ECDSA using P-256 and SHA-256}. <b>This is not a JDK standard algorithm and * JWA algorithm name for {@code ECDSA using P-256 and SHA-256}
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/ */
ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", false, 256, 256), ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256),
/** /**
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and * JWA algorithm name for {@code ECDSA using P-384 and SHA-384}
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/ */
ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", false, 384, 384), ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384),
/** /**
* JWA algorithm name for {@code ECDSA using P-521 and SHA-512}. <b>This is not a JDK standard algorithm and * JWA algorithm name for {@code ECDSA using P-521 and SHA-512}
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/ */
ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", false, 512, 521), ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521),
/** /**
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard * JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
@ -115,10 +108,6 @@ public enum SignatureAlgorithm {
*/ */
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false, 512, 2048); PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false, 512, 2048);
static {
RuntimeEnvironment.enableBouncyCastleIfPossible();
}
//purposefully ordered higher to lower: //purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256));

View File

@ -160,17 +160,8 @@ class JwtsTest {
mockStatic(Classes) mockStatic(Classes)
//JwtBuilder loads SignatureAlgorithm which in turn uses RuntimeEnvironment which in turn checks for BC:
expect(Classes.isAvailable(eq("org.bouncycastle.jce.provider.BouncyCastleProvider"))).andReturn(false)
replay Classes
def instance = createMock(JwtBuilder) def instance = createMock(JwtBuilder)
verify Classes
reset Classes
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultJwtBuilder"))).andReturn(instance) expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultJwtBuilder"))).andReturn(instance)
replay Classes, instance replay Classes, instance

View File

@ -121,7 +121,7 @@ class SignatureAlgorithmTest {
@Test @Test
void testIsJdkStandard() { void testIsJdkStandard() {
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("ES") || alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) { if (alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) {
assertFalse alg.isJdkStandard() assertFalse alg.isJdkStandard()
} else { } else {
assertTrue alg.isJdkStandard() assertTrue alg.isJdkStandard()

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId> <artifactId>jjwt-root</artifactId>
<version>0.11.0-SNAPSHOT</version> <version>0.10.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@ -38,11 +38,12 @@
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <artifactId>jjwt-jackson</artifactId>

View File

@ -18,11 +18,13 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -85,10 +87,8 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* Generates a new secure-random key pair of sufficient strength for the specified Elliptic Curve {@link * 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 * 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 * 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 * #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom)} using {@code "EC"} as the {@code
* jcaAlgorithmName} and {@code "BC"} as the {@code jcaProviderName} since EllipticCurve requires the use of an * jcaAlgorithmName}.
* 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 * @param alg alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or {@code
* ES512} * ES512}
@ -101,7 +101,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* @see #generateKeyPair(String, String, SignatureAlgorithm, SecureRandom) * @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("EC", null, alg, random);
} }
/** /**
@ -111,8 +111,8 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* *
* @param jcaAlgorithmName the JCA name of the algorithm to use for key pair generation, for example, {@code * @param jcaAlgorithmName the JCA name of the algorithm to use for key pair generation, for example, {@code
* ECDSA}. * ECDSA}.
* @param jcaProviderName the JCA provider name of the algorithm implementation, for example {@code BC} for * @param jcaProviderName the JCA provider name of the algorithm implementation (for example {@code "BC"} for
* BouncyCastle. * BouncyCastle) or {@code null} if the default provider should be used.
* @param alg alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or * @param alg alg the algorithm indicating strength, must be one of {@code ES256}, {@code ES384} or
* {@code ES512} * {@code ES512}
* @param random the SecureRandom generator to use during key generation. * @param random the SecureRandom generator to use during key generation.
@ -128,9 +128,17 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
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 {
KeyPairGenerator g = KeyPairGenerator.getInstance(jcaAlgorithmName, jcaProviderName); KeyPairGenerator g;
if (Strings.hasText(jcaProviderName)) {
g = KeyPairGenerator.getInstance(jcaAlgorithmName, jcaProviderName);
} else {
g = KeyPairGenerator.getInstance(jcaAlgorithmName);
}
String paramSpecCurveName = EC_CURVE_NAMES.get(alg); String paramSpecCurveName = EC_CURVE_NAMES.get(alg);
g.initialize(org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(paramSpecCurveName), random); ECGenParameterSpec spec = new ECGenParameterSpec(paramSpecCurveName);
g.initialize(spec, random);
return g.generateKeyPair(); return g.generateKeyPair();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e); throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e);
@ -143,18 +151,19 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* *
* @param alg The ECDSA algorithm. Must be supported and not * @param alg The ECDSA algorithm. Must be supported and not
* {@code null}. * {@code null}.
*
* @return The expected byte array length for the signature. * @return The expected byte array length for the signature.
*
* @throws JwtException If the algorithm is not supported. * @throws JwtException If the algorithm is not supported.
*/ */
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg) public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
throws JwtException { throws JwtException {
switch (alg) { switch (alg) {
case ES256: return 64; case ES256:
case ES384: return 96; return 64;
case ES512: return 132; case ES384:
return 96;
case ES512:
return 132;
default: default:
throw new JwtException("Unsupported Algorithm: " + alg.name()); throw new JwtException("Unsupported Algorithm: " + alg.name());
} }
@ -167,9 +176,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* *
* @param derSignature The ASN1./DER-encoded. Must not be {@code null}. * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
* @param outputLength The expected length of the ECDSA JWS signature. * @param outputLength The expected length of the ECDSA JWS signature.
*
* @return The ECDSA JWS encoded signature. * @return The ECDSA JWS encoded signature.
*
* @throws JwtException If the ASN.1/DER signature format is invalid. * @throws JwtException If the ASN.1/DER signature format is invalid.
*/ */
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) throws JwtException { public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) throws JwtException {
@ -220,7 +227,6 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
} }
/** /**
* Transcodes the ECDSA JWS signature into ASN.1/DER format for use by * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
* the JCA verifier. * the JCA verifier.
@ -228,9 +234,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* @param jwsSignature The JWS signature, consisting of the * @param jwsSignature The JWS signature, consisting of the
* concatenated R and S values. Must not be * concatenated R and S values. Must not be
* {@code null}. * {@code null}.
*
* @return The ASN.1/DER encoded signature. * @return The ASN.1/DER encoded signature.
*
* @throws JwtException If the ECDSA JWS signature format is invalid. * @throws JwtException If the ECDSA JWS signature format is invalid.
*/ */
public static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws JwtException { public static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws JwtException {
@ -239,7 +243,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
int i = rawLen; int i = rawLen;
while((i > 0) && (jwsSignature[rawLen - i] == 0)) { while ((i > 0) && (jwsSignature[rawLen - i] == 0)) {
i--; i--;
} }

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.RuntimeEnvironment;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
@ -54,6 +55,10 @@ public abstract class RsaProvider extends SignatureProvider {
return m; return m;
} }
static {
RuntimeEnvironment.enableBouncyCastleIfPossible(); //PS256, PS384, PS512 on <= JDK 10 require BC
}
protected RsaProvider(SignatureAlgorithm alg, Key key) { protected RsaProvider(SignatureAlgorithm alg, Key key) {
super(alg, key); super(alg, key);
Assert.isTrue(alg.isRsa(), "SignatureAlgorithm must be an RSASSA or RSASSA-PSS algorithm."); Assert.isTrue(alg.isRsa(), "SignatureAlgorithm must be an RSASSA or RSASSA-PSS algorithm.");

View File

@ -38,7 +38,7 @@ class EllipticCurveProviderTest {
@Test @Test
void testGenerateKeyPairWithInvalidProviderName() { void testGenerateKeyPairWithInvalidProviderName() {
try { try {
EllipticCurveProvider.generateKeyPair("ECDSA", "Foo", SignatureAlgorithm.ES256, null) EllipticCurveProvider.generateKeyPair("EC", "Foo", SignatureAlgorithm.ES256, null)
fail() fail()
} catch (IllegalStateException ise) { } catch (IllegalStateException ise) {
assertEquals ise.message, "Unable to generate Elliptic Curve KeyPair: no such provider: Foo" assertEquals ise.message, "Unable to generate Elliptic Curve KeyPair: no such provider: Foo"
@ -49,7 +49,7 @@ class EllipticCurveProviderTest {
@Test @Test
void testGenerateKeyPairWithNullAlgorithm() { void testGenerateKeyPairWithNullAlgorithm() {
try { try {
EllipticCurveProvider.generateKeyPair("ECDSA", "Foo", null, null) EllipticCurveProvider.generateKeyPair("EC", "Foo", null, null)
fail() fail()
} catch (IllegalArgumentException ise) { } catch (IllegalArgumentException ise) {
assertEquals ise.message, "SignatureAlgorithm argument cannot be null." assertEquals ise.message, "SignatureAlgorithm argument cannot be null."
@ -59,7 +59,7 @@ class EllipticCurveProviderTest {
@Test @Test
void testGenerateKeyPairWithNonEllipticCurveAlgorithm() { void testGenerateKeyPairWithNonEllipticCurveAlgorithm() {
try { try {
EllipticCurveProvider.generateKeyPair("ECDSA", "Foo", SignatureAlgorithm.HS256, null) EllipticCurveProvider.generateKeyPair("EC", "Foo", SignatureAlgorithm.HS256, null)
fail() fail()
} catch (IllegalArgumentException ise) { } catch (IllegalArgumentException ise) {
assertEquals ise.message, "SignatureAlgorithm argument must represent an Elliptic Curve algorithm." assertEquals ise.message, "SignatureAlgorithm argument must represent an Elliptic Curve algorithm."

View File

@ -19,7 +19,6 @@ import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.SignatureException
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test import org.junit.Test
import java.security.* import java.security.*
@ -29,10 +28,6 @@ import static org.junit.Assert.*
class EllipticCurveSignatureValidatorTest { class EllipticCurveSignatureValidatorTest {
static {
Security.addProvider(new BouncyCastleProvider())
}
@Test @Test
void testDoVerifyWithInvalidKeyException() { void testDoVerifyWithInvalidKeyException() {
@ -62,7 +57,7 @@ class EllipticCurveSignatureValidatorTest {
@Test @Test
void ecdsaSignatureComplianceTest() { void ecdsaSignatureComplianceTest() {
def fact = KeyFactory.getInstance("ECDSA", "BC"); def fact = KeyFactory.getInstance("EC");
def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg=" def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg="
def pub = fact.generatePublic(new X509EncodedKeySpec(Decoders.BASE64.decode(publicKey))) def pub = fact.generatePublic(new X509EncodedKeySpec(Decoders.BASE64.decode(publicKey)))
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, pub) def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, pub)
@ -82,7 +77,7 @@ class EllipticCurveSignatureValidatorTest {
void legacySignatureCompatTest() { void legacySignatureCompatTest() {
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair() def keypair = EllipticCurveProvider.generateKeyPair()
def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName, "BC") def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName)
def data = withoutSignature.getBytes("US-ASCII") def data = withoutSignature.getBytes("US-ASCII")
signature.initSign(keypair.private) signature.initSign(keypair.private)
signature.update(data) signature.update(data)

View File

@ -47,7 +47,7 @@ class SignatureProviderTest {
@Test @Test
void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithm() { void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithm() {
def p = new SignatureProvider(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().getPublic()) { def p = new SignatureProvider(SignatureAlgorithm.PS256, RsaProvider.generateKeyPair().getPrivate()) {
@Override @Override
protected Signature getSignatureInstance() throws NoSuchAlgorithmException { protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
throw new NoSuchAlgorithmException('foo') throw new NoSuchAlgorithmException('foo')
@ -65,7 +65,7 @@ class SignatureProviderTest {
@Test @Test
void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithmWithoutBouncyCastle() { void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithmWithoutBouncyCastle() {
def p = new SignatureProvider(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().getPublic()) { def p = new SignatureProvider(SignatureAlgorithm.PS256, RsaProvider.generateKeyPair().getPrivate()) {
@Override @Override
protected Signature getSignatureInstance() throws NoSuchAlgorithmException { protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
throw new NoSuchAlgorithmException('foo') throw new NoSuchAlgorithmException('foo')
@ -81,7 +81,7 @@ class SignatureProviderTest {
p.createSignatureInstance() p.createSignatureInstance()
fail() fail()
} catch (SignatureException se) { } catch (SignatureException se) {
assertTrue se.message.contains('Try including BouncyCastle in the runtime classpath') assertTrue se.message.contains('This is not a standard JDK algorithm. Try including BouncyCastle in the runtime classpath.')
} }
} }
} }

View File

@ -71,18 +71,21 @@ class KeysImplTest {
KeyPair pair = Keys.keyPairFor(alg); KeyPair pair = Keys.keyPairFor(alg);
assertNotNull pair assertNotNull pair
String asn1oid = "secp${alg.minKeyLength}r1" int len = alg.minKeyLength
String asn1oid = "secp${len}r1"
String suffix = len == 256 ? ", X9.62 prime${len}v1" : '' //the JDK only adds this extra suffix to the secp256r1 curve name and not secp384r1 or secp521r1 curve names
String jdkParamName = "$asn1oid [NIST P-${len}${suffix}]" as String
PublicKey pub = pair.getPublic() PublicKey pub = pair.getPublic()
assert pub instanceof ECPublicKey assert pub instanceof ECPublicKey
assertEquals "ECDSA", pub.algorithm assertEquals "EC", pub.algorithm
assertEquals asn1oid, pub.params.name assertEquals jdkParamName, pub.params.name
assertEquals alg.minKeyLength, pub.params.order.bitLength() assertEquals alg.minKeyLength, pub.params.order.bitLength()
PrivateKey priv = pair.getPrivate() PrivateKey priv = pair.getPrivate()
assert priv instanceof ECPrivateKey assert priv instanceof ECPrivateKey
assertEquals "ECDSA", priv.algorithm assertEquals "EC", priv.algorithm
assertEquals asn1oid, priv.params.name assertEquals jdkParamName, priv.params.name
assertEquals alg.minKeyLength, priv.params.order.bitLength() assertEquals alg.minKeyLength, priv.params.order.bitLength()
} else { } else {

13
pom.xml
View File

@ -25,7 +25,7 @@
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId> <artifactId>jjwt-root</artifactId>
<version>0.11.0-SNAPSHOT</version> <version>0.10.1-SNAPSHOT</version>
<name>JJWT</name> <name>JJWT</name>
<description>JSON Web Token support for the JVM and Android</description> <description>JSON Web Token support for the JVM and Android</description>
<packaging>pom</packaging> <packaging>pom</packaging>
@ -121,12 +121,6 @@
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <artifactId>jjwt-jackson</artifactId>
@ -147,12 +141,13 @@
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>${orgjson.version}</version> <version>${orgjson.version}</version>
</dependency> </dependency>
<!-- Used only during testing for PS256, PS384 and PS512 since JDK <= 10 doesn't support them: -->
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version> <version>${bouncycastle.version}</version>
<scope>compile</scope> <scope>test</scope>
<optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>