Skip to content

Commit 7cde0bf

Browse files
authored
feat: add support for PS256, RS256 PKCV in validationClient (#759)
* feat: add support for PS256, RS256 PKCV in validationClient * chore: fix build
1 parent d14084f commit 7cde0bf

File tree

5 files changed

+199
-10
lines changed

5 files changed

+199
-10
lines changed

src/main/java/com/twilio/example/ValidationExample.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.security.KeyPairGenerator;
1414
import java.util.Base64;
1515

16+
import io.jsonwebtoken.SignatureAlgorithm;
17+
1618
public class ValidationExample {
1719

1820
public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
@@ -25,7 +27,7 @@ public class ValidationExample {
2527
* @throws TwiMLException if unable to generate TwiML
2628
*/
2729
public static void main(String[] args) throws Exception {
28-
30+
//Twilio.setRegion("dev");
2931
// Generate public/private key pair
3032
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
3133
keyGen.initialize(2048);
@@ -35,7 +37,8 @@ public static void main(String[] args) throws Exception {
3537
// Use the default rest client
3638
TwilioRestClient client =
3739
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
38-
.build();
40+
.region("dev")
41+
.build();
3942

4043
// Create a public key and signing key using the default client
4144
PublicKey key = PublicKey.creator(
@@ -46,9 +49,11 @@ public static void main(String[] args) throws Exception {
4649

4750
// Switch to validation client as the default client
4851
TwilioRestClient validationClient = new TwilioRestClient.Builder(signingKey.getSid(), signingKey.getSecret())
49-
.accountSid(ACCOUNT_SID)
50-
.httpClient(new ValidationClient(ACCOUNT_SID, key.getSid(), signingKey.getSid(), pair.getPrivate()))
51-
.build();
52+
.accountSid(ACCOUNT_SID)
53+
.region("dev")
54+
// Validation client supports RS256 or PS256 algorithm. Default is RS256.
55+
.httpClient(new ValidationClient(ACCOUNT_SID, key.getSid(), signingKey.getSid(), pair.getPrivate(), SignatureAlgorithm.PS256))
56+
.build();
5257

5358
// Make REST API requests
5459
Iterable<Message> messages = Message.reader().read(validationClient);

src/main/java/com/twilio/http/ValidationClient.java

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
import java.util.Collection;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.Set;
23+
24+
import io.jsonwebtoken.SignatureAlgorithm;
25+
26+
import static io.jsonwebtoken.SignatureAlgorithm.PS256;
27+
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
2228

2329
public class ValidationClient extends HttpClient {
2430

@@ -39,6 +45,23 @@ public ValidationClient(final String accountSid,
3945
this(accountSid, credentialSid, signingKey, privateKey, DEFAULT_REQUEST_CONFIG);
4046
}
4147

48+
/**
49+
* Create a new ValidationClient.
50+
*
51+
* @param accountSid Twilio Account SID
52+
* @param credentialSid Twilio Credential SID
53+
* @param signingKey Twilio Signing key
54+
* @param privateKey Private Key
55+
* @param algorithm Client validation algorithm
56+
*/
57+
public ValidationClient(final String accountSid,
58+
final String credentialSid,
59+
final String signingKey,
60+
final PrivateKey privateKey,
61+
final SignatureAlgorithm algorithm) {
62+
this(accountSid, credentialSid, signingKey, privateKey, DEFAULT_REQUEST_CONFIG, algorithm);
63+
}
64+
4265
/**
4366
* Create a new ValidationClient.
4467
*
@@ -53,7 +76,26 @@ public ValidationClient(final String accountSid,
5376
final String signingKey,
5477
final PrivateKey privateKey,
5578
final RequestConfig requestConfig) {
56-
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG);
79+
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG, RS256);
80+
}
81+
82+
/**
83+
* Create a new ValidationClient.
84+
*
85+
* @param accountSid Twilio Account SID
86+
* @param credentialSid Twilio Credential SID
87+
* @param signingKey Twilio Signing key
88+
* @param privateKey Private Key
89+
* @param requestConfig HTTP Request Config
90+
* @param algorithm Client validation algorithm
91+
*/
92+
public ValidationClient(final String accountSid,
93+
final String credentialSid,
94+
final String signingKey,
95+
final PrivateKey privateKey,
96+
final RequestConfig requestConfig,
97+
final SignatureAlgorithm algorithm) {
98+
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG, algorithm);
5799
}
58100

59101
/**
@@ -72,6 +114,29 @@ public ValidationClient(final String accountSid,
72114
final PrivateKey privateKey,
73115
final RequestConfig requestConfig,
74116
final SocketConfig socketConfig) {
117+
118+
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, socketConfig, RS256);
119+
}
120+
121+
/**
122+
* Create a new ValidationClient.
123+
*
124+
* @param accountSid Twilio Account SID
125+
* @param credentialSid Twilio Credential SID
126+
* @param signingKey Twilio Signing key
127+
* @param privateKey Private Key
128+
* @param requestConfig HTTP Request Config
129+
* @param socketConfig HTTP Socket Config
130+
* @param algorithm Client validation algorithm
131+
*/
132+
public ValidationClient(final String accountSid,
133+
final String credentialSid,
134+
final String signingKey,
135+
final PrivateKey privateKey,
136+
final RequestConfig requestConfig,
137+
final SocketConfig socketConfig,
138+
final SignatureAlgorithm algorithm) {
139+
75140
Collection<BasicHeader> headers = Arrays.asList(
76141
new BasicHeader("X-Twilio-Client", "java-" + Twilio.VERSION),
77142
new BasicHeader(HttpHeaders.ACCEPT, "application/json"),
@@ -86,7 +151,7 @@ public ValidationClient(final String accountSid,
86151
.setDefaultRequestConfig(requestConfig)
87152
.setDefaultHeaders(headers)
88153
.setMaxConnPerRoute(10)
89-
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey))
154+
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey, algorithm))
90155
.setRedirectStrategy(this.getRedirectStrategy())
91156
.build();
92157
}

src/main/java/com/twilio/http/ValidationInterceptor.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.Arrays;
1313
import java.util.List;
1414

15+
import io.jsonwebtoken.SignatureAlgorithm;
16+
1517
public class ValidationInterceptor implements HttpRequestInterceptor {
1618

1719
private static final List<String> HEADERS = Arrays.asList("authorization", "host");
@@ -20,6 +22,7 @@ public class ValidationInterceptor implements HttpRequestInterceptor {
2022
private final String credentialSid;
2123
private final String signingKeySid;
2224
private final PrivateKey privateKey;
25+
private final SignatureAlgorithm algorithm;
2326

2427
/**
2528
* Create a new ValidationInterceptor.
@@ -30,10 +33,27 @@ public class ValidationInterceptor implements HttpRequestInterceptor {
3033
* @param privateKey Private Key
3134
*/
3235
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey) {
36+
37+
this(accountSid, credentialSid, signingKeySid, privateKey, SignatureAlgorithm.RS256);
38+
}
39+
40+
41+
/**
42+
* Create a new ValidationInterceptor.
43+
*
44+
* @param accountSid Twilio Acocunt SID
45+
* @param credentialSid Twilio Credential SID
46+
* @param signingKeySid Twilio Signing Key
47+
* @param privateKey Private Key
48+
* @param algorithm Client validaiton algorithm
49+
*/
50+
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey,
51+
SignatureAlgorithm algorithm) {
3352
this.accountSid = accountSid;
3453
this.credentialSid = credentialSid;
3554
this.signingKeySid = signingKeySid;
3655
this.privateKey = privateKey;
56+
this.algorithm = algorithm;
3757
}
3858

3959
@Override
@@ -44,7 +64,8 @@ public void process(HttpRequest request, HttpContext context) throws HttpExcepti
4464
signingKeySid,
4565
privateKey,
4666
request,
47-
HEADERS
67+
HEADERS,
68+
algorithm
4869
);
4970
request.addHeader("Twilio-Client-Validation", jwt.toJwt());
5071
}

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88
import org.apache.http.HttpEntity;
99
import org.apache.http.HttpEntityEnclosingRequest;
1010
import org.apache.http.HttpRequest;
11+
import org.apache.http.impl.auth.UnsupportedDigestAlgorithmException;
1112

1213
import java.io.IOException;
14+
import java.io.UnsupportedEncodingException;
1315
import java.nio.charset.StandardCharsets;
1416
import java.security.PrivateKey;
1517
import java.util.*;
1618
import java.util.function.Function;
1719

20+
import static io.jsonwebtoken.SignatureAlgorithm.PS256;
21+
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
1822

1923
public class ValidationToken extends Jwt {
2024

@@ -30,9 +34,12 @@ public class ValidationToken extends Jwt {
3034
private final List<String> signedHeaders;
3135
private final String requestBody;
3236

37+
private static final Set<SignatureAlgorithm> supportedAlgorithms
38+
= Collections.unmodifiableSet(new HashSet<>(Arrays.asList(PS256, RS256)));
39+
3340
private ValidationToken(Builder b) {
3441
super(
35-
SignatureAlgorithm.RS256,
42+
b.algorithm,
3643
b.privateKey,
3744
b.credentialSid,
3845
new Date(new Date().getTime() + b.ttl * 1000)
@@ -99,10 +106,37 @@ public static ValidationToken fromHttpRequest(
99106
HttpRequest request,
100107
List<String> signedHeaders
101108
) throws IOException {
109+
110+
return fromHttpRequest(accountSid, credentialSid, signingKeySid, privateKey, request, signedHeaders, SignatureAlgorithm.RS256);
111+
}
112+
113+
/**
114+
* Create a ValidationToken from an HTTP Request.
115+
*
116+
* @param accountSid Twilio Account SID
117+
* @param credentialSid Twilio Credential SID
118+
* @param signingKeySid Twilio Signing Key SID
119+
* @param privateKey Private Key
120+
* @param request HTTP Request
121+
* @param signedHeaders Headers to sign
122+
* @param algorithm Client validation algorithm
123+
* @return The ValidationToken generated from the HttpRequest
124+
* @throws IOException when unable to generate
125+
*/
126+
public static ValidationToken fromHttpRequest(
127+
String accountSid,
128+
String credentialSid,
129+
String signingKeySid,
130+
PrivateKey privateKey,
131+
HttpRequest request,
132+
List<String> signedHeaders,
133+
SignatureAlgorithm algorithm
134+
) throws IOException {
102135
Builder builder = new Builder(accountSid, credentialSid, signingKeySid, privateKey);
103136

104137
String method = request.getRequestLine().getMethod();
105138
builder.method(method);
139+
builder.algorithm(algorithm);
106140

107141
String uri = request.getRequestLine().getUri();
108142
if (uri.contains("?")) {
@@ -151,6 +185,8 @@ public static class Builder {
151185
private String requestBody = "";
152186
private int ttl = 300;
153187

188+
private SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;
189+
154190
/**
155191
* Create a new ValidationToken Builder.
156192
*
@@ -206,6 +242,13 @@ public Builder ttl(int ttl) {
206242
return this;
207243
}
208244

245+
public Builder algorithm(SignatureAlgorithm algorithm) {
246+
if (!supportedAlgorithms.contains(algorithm)) {
247+
throw new IllegalArgumentException("Not supported!");
248+
}
249+
this.algorithm = algorithm;
250+
return this;
251+
}
209252
public ValidationToken build() {
210253
return new ValidationToken(this);
211254
}

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.twilio.jwt.Jwt;
55
import io.jsonwebtoken.Claims;
66
import io.jsonwebtoken.Jwts;
7+
import io.jsonwebtoken.SignatureAlgorithm;
8+
import io.jsonwebtoken.security.InvalidKeyException;
79
import org.apache.http.*;
810
import org.apache.http.client.protocol.HttpClientContext;
911
import org.apache.http.message.BasicHeader;
@@ -20,6 +22,7 @@
2022
import java.security.KeyPair;
2123
import java.security.KeyPairGenerator;
2224
import java.security.PrivateKey;
25+
import java.security.PublicKey;
2326
import java.util.*;
2427
import java.util.concurrent.ExecutionException;
2528
import java.util.concurrent.ExecutorService;
@@ -28,6 +31,13 @@
2831

2932
import static org.mockito.Mockito.when;
3033

34+
import static io.jsonwebtoken.SignatureAlgorithm.PS256;
35+
import static io.jsonwebtoken.SignatureAlgorithm.PS384;
36+
import static io.jsonwebtoken.SignatureAlgorithm.PS512;
37+
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
38+
import static io.jsonwebtoken.SignatureAlgorithm.RS384;
39+
import static io.jsonwebtoken.SignatureAlgorithm.RS512;
40+
3141
public class ValidationTokenTest {
3242

3343
private static final List<String> SIGNED_HEADERS = Arrays.asList("host", "authorization");
@@ -38,6 +48,8 @@ public class ValidationTokenTest {
3848
private Header[] headers;
3949
private PrivateKey privateKey;
4050

51+
private PublicKey publicKey;
52+
4153
@Mock
4254
private HttpRequest request;
4355

@@ -61,6 +73,7 @@ public void setup() throws Exception {
6173
keyGen.initialize(2048);
6274
KeyPair pair = keyGen.generateKeyPair();
6375
privateKey = pair.getPrivate();
76+
publicKey = pair.getPublic();
6477
}
6578

6679
@Test
@@ -80,12 +93,54 @@ public void testTokenBuilder() {
8093
.parseClaimsJws(jwt.toJwt())
8194
.getBody();
8295

83-
8496
this.validateToken(claims);
8597
Assert.assertEquals("authorization;host", claims.get("hrh"));
8698
Assert.assertEquals("4dc9b67bed579647914587b0e22a1c65c1641d8674797cd82de65e766cce5f80", claims.get("rqh"));
8799
}
88100

101+
@Test
102+
public void testTokenValidAlgorithms() {
103+
List<SignatureAlgorithm> validAlgorithms = Arrays.asList(RS256, PS256);
104+
for (SignatureAlgorithm alg : validAlgorithms) {
105+
Jwt jwt = new ValidationToken.Builder(ACCOUNT_SID, CREDENTIAL_SID, SIGNING_KEY_SID, privateKey)
106+
.algorithm(alg)
107+
.method("GET")
108+
.uri("/Messages")
109+
.queryString("PageSize=5&Limit=10")
110+
.headers(headers)
111+
.signedHeaders(SIGNED_HEADERS)
112+
.requestBody("foobar")
113+
.build();
114+
115+
Claims claims =
116+
Jwts.parserBuilder().setSigningKey(publicKey).build()
117+
.parseClaimsJws(jwt.toJwt())
118+
.getBody();
119+
validateToken(claims);
120+
}
121+
}
122+
123+
@Test(expected = IllegalArgumentException.class)
124+
public void testTokenInvalidAlgorithms() {
125+
List<SignatureAlgorithm> validAlgorithms = Arrays.asList(SignatureAlgorithm.HS256, SignatureAlgorithm.ES256, RS384, RS512, PS384, PS512);
126+
for (SignatureAlgorithm alg : validAlgorithms) {
127+
Jwt jwt = new ValidationToken.Builder(ACCOUNT_SID, CREDENTIAL_SID, SIGNING_KEY_SID, privateKey)
128+
.algorithm(alg)
129+
.method("GET")
130+
.uri("/Messages")
131+
.queryString("PageSize=5&Limit=10")
132+
.headers(headers)
133+
.signedHeaders(SIGNED_HEADERS)
134+
.requestBody("foobar")
135+
.build();
136+
137+
138+
Jwts.parserBuilder().setSigningKey(publicKey).build()
139+
.parseClaimsJws(jwt.toJwt())
140+
.getBody();
141+
}
142+
}
143+
89144
@Test
90145
public void testTokenFromHttpRequest() throws IOException {
91146
when(request.getRequestLine()).thenReturn(requestLine);

0 commit comments

Comments
 (0)