Skip to content

Commit c77a678

Browse files
authored
Merge branch 'master' into master
2 parents e30e584 + 7dcea42 commit c77a678

14 files changed

Lines changed: 1675 additions & 107 deletions

File tree

.circleci/config.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
version: 2
2+
jobs:
3+
java_7_build:
4+
docker:
5+
- image: openjdk:7u121-jdk
6+
steps:
7+
- checkout
8+
- run: chmod +x gradlew
9+
# Download and cache dependencies
10+
- restore_cache:
11+
keys:
12+
- v1-dependencies-{{ checksum "build.gradle" }}
13+
# fallback to using the latest cache if no exact match is found
14+
- v1-dependencies-
15+
# run tests!
16+
- run: ./gradlew clean check jacocoTestReport --continue --console=plain
17+
- run:
18+
name: Upload Coverage
19+
when: on_success
20+
command: bash <(curl -s https://codecov.io/bash)
21+
- save_cache:
22+
paths:
23+
- ~/.m2
24+
key: v1-dependencies-{{ checksum "build.gradle" }}
25+
environment:
26+
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
27+
_JAVA_OPTIONS: "-Xms512m -Xmx1024m"
28+
TERM: dumb
29+
30+
java_8_build:
31+
docker:
32+
- image: openjdk:8-jdk
33+
steps:
34+
- checkout
35+
- run: chmod +x gradlew
36+
# Download and cache dependencies
37+
- restore_cache:
38+
keys:
39+
- v1-dependencies-{{ checksum "build.gradle" }}
40+
# fallback to using the latest cache if no exact match is found
41+
- v1-dependencies-
42+
# run tests!
43+
- run: ./gradlew clean check jacocoTestReport --continue --console=plain
44+
- run:
45+
name: Upload Coverage
46+
when: on_success
47+
command: bash <(curl -s https://codecov.io/bash)
48+
- save_cache:
49+
paths:
50+
- ~/.m2
51+
key: v1-dependencies-{{ checksum "build.gradle" }}
52+
environment:
53+
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
54+
_JAVA_OPTIONS: "-Xms512m -Xmx1024m"
55+
TERM: dumb
56+
57+
workflows:
58+
version: 2
59+
build:
60+
jobs:
61+
- java_7_build
62+
- java_8_build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Temporary Items
4242

4343
# IntelliJ
4444
/out/
45+
/lib/out/
4546

4647
# mpeltonen/sbt-idea plugin
4748
.idea_modules/

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Change Log
22

3+
## [3.3.0](https://github.com/auth0/java-jwt/tree/3.3.0) (2017-11-06)
4+
[Full Changelog](https://github.com/auth0/java-jwt/compare/3.2.0...3.3.0)
5+
**Closed issues**
6+
- Wrong ES256 signature length [\#187](https://github.com/auth0/java-jwt/issues/187)
7+
8+
**Fixed**
9+
- Rework ECDSA [\#212](https://github.com/auth0/java-jwt/pull/212) ([lbalmaceda](https://github.com/lbalmaceda))
10+
- Instantiate exception only when required [\#198](https://github.com/auth0/java-jwt/pull/198) ([rumdidumdum](https://github.com/rumdidumdum))
11+
312
## [3.2.0](https://github.com/auth0/java-jwt/tree/3.2.0) (2017-05-04)
413
[Full Changelog](https://github.com/auth0/java-jwt/compare/3.1.0...3.2.0)
514
**Closed issues**

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ If you're looking for an **Android** version of the JWT Decoder take a look at o
1818
<dependency>
1919
<groupId>com.auth0</groupId>
2020
<artifactId>java-jwt</artifactId>
21-
<version>3.2.0</version>
21+
<version>3.3.0</version>
2222
</dependency>
2323
```
2424

2525
### Gradle
2626

2727
```gradle
28-
compile 'com.auth0:java-jwt:3.2.0'
28+
compile 'com.auth0:java-jwt:3.3.0'
2929
```
3030

3131
## Available Algorithms

circle.yml

Lines changed: 0 additions & 20 deletions
This file was deleted.

lib/build.gradle

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ compileJava {
3434
}
3535

3636
dependencies {
37-
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.4'
38-
compile 'commons-codec:commons-codec:1.10'
39-
compile 'org.bouncycastle:bcprov-jdk15on:1.55'
37+
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.2'
38+
compile 'commons-codec:commons-codec:1.11'
39+
testCompile 'org.bouncycastle:bcprov-jdk15on:1.58'
4040
testCompile 'junit:junit:4.12'
41-
testCompile 'net.jodah:concurrentunit:0.4.2'
42-
testCompile 'org.hamcrest:hamcrest-library:1.3'
43-
testCompile 'org.mockito:mockito-core:2.2.8'
41+
testCompile 'net.jodah:concurrentunit:0.4.3'
42+
testCompile 'org.hamcrest:java-hamcrest:2.0.0.0'
43+
testCompile 'org.mockito:mockito-core:2.11.0'
4444
}
4545

4646
jacocoTestReport {

lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
4444
if (publicKey == null) {
4545
throw new IllegalStateException("The given Public Key is null.");
4646
}
47-
if (!isDERSignature(signatureBytes)) {
48-
signatureBytes = JOSEToDER(signatureBytes);
49-
}
50-
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes);
47+
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, JOSEToDER(signatureBytes));
5148

5249
if (!valid) {
5350
throw new SignatureVerificationException(this);
@@ -64,7 +61,8 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
6461
if (privateKey == null) {
6562
throw new IllegalStateException("The given Private Key is null.");
6663
}
67-
return crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
64+
byte[] signature = crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
65+
return DERToJOSE(signature);
6866
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
6967
throw new SignatureGenerationException(this, e);
7068
}
@@ -75,15 +73,60 @@ public String getSigningKeyId() {
7573
return keyProvider.getPrivateKeyId();
7674
}
7775

78-
private boolean isDERSignature(byte[] signature) {
76+
//Visible for testing
77+
byte[] DERToJOSE(byte[] derSignature) throws SignatureException {
7978
// DER Structure: http://crypto.stackexchange.com/a/1797
80-
// Should begin with 0x30 and have exactly the expected length
81-
return signature[0] == 0x30 && signature.length != ecNumberSize * 2;
79+
boolean derEncoded = derSignature[0] == 0x30 && derSignature.length != ecNumberSize * 2;
80+
if (!derEncoded) {
81+
throw new SignatureException("Invalid DER signature format.");
82+
}
83+
84+
final byte[] joseSignature = new byte[ecNumberSize * 2];
85+
86+
//Skip 0x30
87+
int offset = 1;
88+
if (derSignature[1] == (byte) 0x81) {
89+
//Skip sign
90+
offset++;
91+
}
92+
93+
//Convert to unsigned. Should match DER length - offset
94+
int encodedLength = derSignature[offset++] & 0xff;
95+
if (encodedLength != derSignature.length - offset) {
96+
throw new SignatureException("Invalid DER signature format.");
97+
}
98+
99+
//Skip 0x02
100+
offset++;
101+
102+
//Obtain R number length (Includes padding) and skip it
103+
int rLength = derSignature[offset++];
104+
if (rLength > ecNumberSize + 1) {
105+
throw new SignatureException("Invalid DER signature format.");
106+
}
107+
int rPadding = ecNumberSize - rLength;
108+
//Retrieve R number
109+
System.arraycopy(derSignature, offset + Math.max(-rPadding, 0), joseSignature, Math.max(rPadding, 0), rLength + Math.min(rPadding, 0));
110+
111+
//Skip R number and 0x02
112+
offset += rLength + 1;
113+
114+
//Obtain S number length. (Includes padding)
115+
int sLength = derSignature[offset++];
116+
if (sLength > ecNumberSize + 1) {
117+
throw new SignatureException("Invalid DER signature format.");
118+
}
119+
int sPadding = ecNumberSize - sLength;
120+
//Retrieve R number
121+
System.arraycopy(derSignature, offset + Math.max(-sPadding, 0), joseSignature, ecNumberSize + Math.max(sPadding, 0), sLength + Math.min(sPadding, 0));
122+
123+
return joseSignature;
82124
}
83125

84-
private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
126+
//Visible for testing
127+
byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
85128
if (joseSignature.length != ecNumberSize * 2) {
86-
throw new SignatureException(String.format("The signature length was invalid. Expected %d bytes but received %d", ecNumberSize * 2, joseSignature.length));
129+
throw new SignatureException("Invalid JOSE signature format.");
87130
}
88131

89132
// Retrieve R and S number's length and padding.
@@ -94,10 +137,10 @@ private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
94137

95138
int length = 2 + rLength + 2 + sLength;
96139
if (length > 255) {
97-
throw new SignatureException("Invalid ECDSA signature format");
140+
throw new SignatureException("Invalid JOSE signature format.");
98141
}
99142

100-
byte[] derSignature;
143+
final byte[] derSignature;
101144
int offset;
102145
if (length > 0x7f) {
103146
derSignature = new byte[3 + length];
@@ -109,22 +152,38 @@ private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
109152
}
110153

111154
// DER Structure: http://crypto.stackexchange.com/a/1797
112-
// Header with length info
155+
// Header with signature length info
113156
derSignature[0] = (byte) 0x30;
114-
derSignature[offset++] = (byte) length;
157+
derSignature[offset++] = (byte) (length & 0xff);
158+
159+
// Header with "min R" number length
115160
derSignature[offset++] = (byte) 0x02;
116161
derSignature[offset++] = (byte) rLength;
117162

118163
// R number
119-
System.arraycopy(joseSignature, 0, derSignature, offset + (rLength - ecNumberSize), ecNumberSize);
120-
offset += rLength;
164+
if (rPadding < 0) {
165+
//Sign
166+
derSignature[offset++] = (byte) 0x00;
167+
System.arraycopy(joseSignature, 0, derSignature, offset, ecNumberSize);
168+
offset += ecNumberSize;
169+
} else {
170+
int copyLength = Math.min(ecNumberSize, rLength);
171+
System.arraycopy(joseSignature, rPadding, derSignature, offset, copyLength);
172+
offset += copyLength;
173+
}
121174

122-
// S number length
175+
// Header with "min S" number length
123176
derSignature[offset++] = (byte) 0x02;
124177
derSignature[offset++] = (byte) sLength;
125178

126179
// S number
127-
System.arraycopy(joseSignature, ecNumberSize, derSignature, offset + (sLength - ecNumberSize), ecNumberSize);
180+
if (sPadding < 0) {
181+
//Sign
182+
derSignature[offset++] = (byte) 0x00;
183+
System.arraycopy(joseSignature, ecNumberSize, derSignature, offset, ecNumberSize);
184+
} else {
185+
System.arraycopy(joseSignature, ecNumberSize + sPadding, derSignature, offset, Math.min(ecNumberSize, sLength));
186+
}
128187

129188
return derSignature;
130189
}
@@ -134,7 +193,7 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) {
134193
while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) {
135194
padding++;
136195
}
137-
return bytes[fromIndex + padding] > 0x7f ? padding : padding - 1;
196+
return (bytes[fromIndex + padding] & 0xff) > 0x7f ? padding - 1 : padding;
138197
}
139198

140199
//Visible for testing

lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.auth0.jwt.exceptions.JWTDecodeException;
44
import com.auth0.jwt.interfaces.Claim;
5+
import com.fasterxml.jackson.core.JsonParser;
56
import com.fasterxml.jackson.core.JsonProcessingException;
67
import com.fasterxml.jackson.core.type.TypeReference;
78
import com.fasterxml.jackson.databind.JsonNode;
@@ -66,11 +67,10 @@ public <T> T[] asArray(Class<T> tClazz) throws JWTDecodeException {
6667
return null;
6768
}
6869

69-
ObjectMapper mapper = new ObjectMapper();
7070
T[] arr = (T[]) Array.newInstance(tClazz, data.size());
7171
for (int i = 0; i < data.size(); i++) {
7272
try {
73-
arr[i] = mapper.treeToValue(data.get(i), tClazz);
73+
arr[i] = getObjectMapper().treeToValue(data.get(i), tClazz);
7474
} catch (JsonProcessingException e) {
7575
throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e);
7676
}
@@ -84,11 +84,10 @@ public <T> List<T> asList(Class<T> tClazz) throws JWTDecodeException {
8484
return null;
8585
}
8686

87-
ObjectMapper mapper = new ObjectMapper();
8887
List<T> list = new ArrayList<>();
8988
for (int i = 0; i < data.size(); i++) {
9089
try {
91-
list.add(mapper.treeToValue(data.get(i), tClazz));
90+
list.add(getObjectMapper().treeToValue(data.get(i), tClazz));
9291
} catch (JsonProcessingException e) {
9392
throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e);
9493
}
@@ -102,21 +101,21 @@ public Map<String, Object> asMap() throws JWTDecodeException {
102101
return null;
103102
}
104103

105-
ObjectMapper mapper = new ObjectMapper();
106104
try {
107105
TypeReference<Map<String, Object>> mapType = new TypeReference<Map<String, Object>>() {
108106
};
109-
return mapper.treeAsTokens(data).readValueAs(mapType);
107+
ObjectMapper thisMapper = getObjectMapper();
108+
JsonParser thisParser = thisMapper.treeAsTokens(data);
109+
return thisParser.readValueAs(mapType);
110110
} catch (IOException e) {
111111
throw new JWTDecodeException("Couldn't map the Claim value to Map", e);
112112
}
113113
}
114114

115115
@Override
116116
public <T> T as(Class<T> tClazz) throws JWTDecodeException {
117-
ObjectMapper mapper = new ObjectMapper();
118117
try {
119-
return mapper.treeAsTokens(data).readValueAs(tClazz);
118+
return getObjectMapper().treeAsTokens(data).readValueAs(tClazz);
120119
} catch (IOException e) {
121120
throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e);
122121
}
@@ -151,4 +150,9 @@ static Claim claimFromNode(JsonNode node) {
151150
}
152151
return new JsonNodeClaim(node);
153152
}
153+
154+
//Visible for testing
155+
ObjectMapper getObjectMapper() {
156+
return new ObjectMapper();
157+
}
154158
}

0 commit comments

Comments
 (0)