Skip to content

Commit 6c06683

Browse files
committed
Polish up signature
1 parent 1dc65d6 commit 6c06683

File tree

2 files changed

+71
-18
lines changed

2 files changed

+71
-18
lines changed

src/main/java/com/twilio/jwt/validation/ValidationToken.java

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.google.common.base.Charsets;
44
import com.google.common.base.Function;
55
import com.google.common.base.Joiner;
6+
import com.google.common.base.Strings;
7+
import com.google.common.collect.Lists;
68
import com.google.common.hash.HashFunction;
79
import com.google.common.hash.Hashing;
810
import com.google.common.io.CharStreams;
@@ -19,7 +21,6 @@
1921
import java.io.InputStreamReader;
2022
import java.util.Arrays;
2123
import java.util.Collections;
22-
import java.util.Comparator;
2324
import java.util.Date;
2425
import java.util.HashMap;
2526
import java.util.List;
@@ -80,19 +81,19 @@ public Map<String, Object> getClaims() {
8081

8182
// Normalize all the headers
8283
Header[] lowercaseHeaders = LOWERCASE_KEYS.apply(headers);
83-
Arrays.sort(lowercaseHeaders, new Comparator<Header>() {
84-
@Override
85-
public int compare(Header o1, Header o2) {
86-
return o1.getName().compareTo(o2.getName());
87-
}
88-
});
84+
Map<String, List<String>> combinedHeaders = COMBINE_HEADERS.apply(lowercaseHeaders);
8985

9086
// Add the headers that we care about
91-
for (Header header: lowercaseHeaders) {
92-
if (signedHeaders.contains(header.getName().toLowerCase())) {
93-
signature.append(header.getName().toLowerCase().trim())
87+
for (String header : signedHeaders) {
88+
String lowercase = header.toLowerCase().trim();
89+
90+
if (combinedHeaders.containsKey(lowercase)) {
91+
List<String> values = combinedHeaders.get(lowercase);
92+
Collections.sort(values);
93+
94+
signature.append(lowercase)
9495
.append(":")
95-
.append(header.getValue().trim())
96+
.append(Joiner.on(',').join(values))
9697
.append(NEW_LINE);
9798
}
9899
}
@@ -102,8 +103,10 @@ public int compare(Header o1, Header o2) {
102103
signature.append(includedHeaders).append(NEW_LINE);
103104

104105
// Hash and hex the request payload
105-
String hashedPayload = HASH_FUNCTION.hashString(requestBody, Charsets.UTF_8).toString();
106-
signature.append(hashedPayload).append(NEW_LINE);
106+
if (!Strings.isNullOrEmpty(requestBody)) {
107+
String hashedPayload = HASH_FUNCTION.hashString(requestBody, Charsets.UTF_8).toString();
108+
signature.append(hashedPayload);
109+
}
107110

108111
// Hash and hex the canonical request
109112
String hashedSignature = HASH_FUNCTION.hashString(signature.toString(), Charsets.UTF_8).toString();
@@ -143,6 +146,23 @@ public static ValidationToken fromHttpRequest(
143146
return builder.build();
144147
}
145148

149+
private static Function<Header[], Map<String, List<String>>> COMBINE_HEADERS = new Function<Header[], Map<String, List<String>>>() {
150+
@Override
151+
public Map<String, List<String>> apply(Header[] headers) {
152+
Map<String, List<String>> combinedHeaders = new HashMap<>();
153+
154+
for (Header header : headers) {
155+
if (combinedHeaders.containsKey(header.getName())) {
156+
combinedHeaders.get(header.getName()).add(header.getValue());
157+
} else {
158+
combinedHeaders.put(header.getName(), Lists.newArrayList(header.getValue()));
159+
}
160+
}
161+
162+
return combinedHeaders;
163+
}
164+
};
165+
146166
private static Function<Header[], Header[]> LOWERCASE_KEYS = new Function<Header[], Header[]>() {
147167
@Override
148168
public Header[] apply(Header[] headers) {

src/test/java/com/twilio/jwt/validation/ValidationTokenTest.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77
import mockit.Expectations;
88
import mockit.Mocked;
99
import org.apache.http.Header;
10-
import org.apache.http.HttpRequest;
11-
import org.apache.http.RequestLine;
10+
import org.apache.http.HttpEntity;
11+
import org.apache.http.HttpEntityEnclosingRequest;
1212
import org.apache.http.message.BasicHeader;
1313
import org.junit.Assert;
1414
import org.junit.Before;
1515
import org.junit.Test;
1616

17+
import java.io.ByteArrayInputStream;
1718
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
1820
import java.util.Date;
1921
import java.util.List;
2022

@@ -27,10 +29,10 @@ public class ValidationTokenTest {
2729
private Header[] headers;
2830

2931
@Mocked
30-
private HttpRequest request;
32+
private HttpEntityEnclosingRequest request;
3133

3234
@Mocked
33-
private RequestLine requestLine;
35+
private HttpEntity entity;
3436

3537
@Before
3638
public void setup() {
@@ -83,7 +85,38 @@ public void testTokenFromHttpRequest() throws IOException {
8385

8486

8587
Assert.assertEquals("authorization;host", claims.get("hrh"));
86-
Assert.assertEquals("3e84e1e5af4c084e413f9bd103f9f11abb3d5f58c4b0b72a79a986aebe58cd5b", claims.get("rqh"));
88+
Assert.assertEquals("4b3d2666845a38f00259a5231a08765bb2d12564bc4469fd5b2816204c588967", claims.get("rqh"));
89+
}
90+
91+
@Test
92+
public void testTokenFromPostRequest() throws IOException {
93+
new Expectations() {{
94+
request.getRequestLine().getMethod();
95+
result = "POST";
96+
97+
request.getRequestLine().getUri();
98+
result = "/Messages";
99+
100+
request.getAllHeaders();
101+
result = headers;
102+
103+
request.getEntity();
104+
result = entity;
105+
106+
entity.getContent();
107+
result = new ByteArrayInputStream("testbody".getBytes(StandardCharsets.UTF_8));
108+
}};
109+
110+
Jwt jwt = ValidationToken.fromHttpRequest(CREDENTIAL_SID, SECRET, request, SIGNED_HEADERS);
111+
Claims claims =
112+
Jwts.parser()
113+
.setSigningKey(SECRET.getBytes())
114+
.parseClaimsJws(jwt.toJwt())
115+
.getBody();
116+
117+
118+
Assert.assertEquals("authorization;host", claims.get("hrh"));
119+
Assert.assertEquals("bd792c967c20d546c738b94068f5f72758a10d26c12979677501e1eefe58c65a", claims.get("rqh"));
87120
}
88121

89122
private void validateToken(Claims claims) {

0 commit comments

Comments
 (0)