Skip to content

Commit ccbc880

Browse files
committed
Polish Asymmetric Auth
1 parent 7bdb5ec commit ccbc880

File tree

14 files changed

+1078
-110
lines changed

14 files changed

+1078
-110
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.twilio.example;
2+
3+
4+
import com.twilio.Twilio;
5+
import com.twilio.http.TwilioRestClient;
6+
import com.twilio.http.ValidationClient;
7+
import com.twilio.rest.accounts.v1.credential.PublicKey;
8+
import com.twilio.rest.api.v2010.account.Message;
9+
import com.twilio.rest.api.v2010.account.NewSigningKey;
10+
import com.twilio.twiml.TwiMLException;
11+
import com.twilio.type.PhoneNumber;
12+
import org.apache.commons.codec.binary.Base64;
13+
14+
import java.security.KeyPair;
15+
import java.security.KeyPairGenerator;
16+
17+
public class ValidationExample {
18+
19+
public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
20+
public static final String AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");
21+
22+
/**
23+
* Example Twilio usage.
24+
*
25+
* @param args command line args
26+
* @throws TwiMLException if unable to generate TwiML
27+
*/
28+
public static void main(String[] args) throws Exception {
29+
30+
// Generate public/private key pair
31+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
32+
keyGen.initialize(2048);
33+
KeyPair pair = keyGen.generateKeyPair();
34+
java.security.PublicKey pk = pair.getPublic();
35+
36+
// Use the default rest client
37+
TwilioRestClient client =
38+
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
39+
.build();
40+
Twilio.setRestClient(client);
41+
42+
// Create a public key and signing key using the default client
43+
PublicKey key = PublicKey.creator(
44+
Base64.encodeBase64String(pk.getEncoded())
45+
).setFriendlyName("Public Key").create();
46+
NewSigningKey signingKey = NewSigningKey.creator().create();
47+
48+
// Switch to validation client as the default client
49+
client = new TwilioRestClient.Builder(signingKey.getSid(), signingKey.getSecret())
50+
.accountSid(ACCOUNT_SID)
51+
.httpClient(new ValidationClient(ACCOUNT_SID, key.getSid(), signingKey.getSid(), pair.getPrivate()))
52+
.build();
53+
Twilio.setRestClient(client);
54+
55+
// Make REST API requests
56+
Iterable<Message> messages = Message.reader().read();
57+
for (Message m : messages) {
58+
System.out.println(m.getBody());
59+
}
60+
61+
Message m = Message.creator(
62+
new PhoneNumber("+11234567890"),
63+
new PhoneNumber("+10987654321"),
64+
"Asymmetric Auth Test"
65+
).create();
66+
System.out.println(m.getSid());
67+
68+
}
69+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.io.IOException;
1818
import java.nio.charset.StandardCharsets;
19+
import java.security.PrivateKey;
1920
import java.util.Collection;
2021
import java.util.List;
2122
import java.util.Map;
@@ -27,7 +28,7 @@ public class ValidationClient extends HttpClient {
2728

2829
private final org.apache.http.client.HttpClient client;
2930

30-
public ValidationClient(String credentialSid, String publicKey) {
31+
public ValidationClient(String accountSid, String credentialSid, String signingKey, PrivateKey privateKey) {
3132
RequestConfig config = RequestConfig.custom()
3233
.setConnectTimeout(CONNECTION_TIMEOUT)
3334
.setSocketTimeout(SOCKET_TIMEOUT)
@@ -45,7 +46,7 @@ public ValidationClient(String credentialSid, String publicKey) {
4546
.setDefaultRequestConfig(config)
4647
.setDefaultHeaders(headers)
4748
.setMaxConnPerRoute(10)
48-
.addInterceptorLast(new ValidationInterceptor(credentialSid, publicKey))
49+
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey))
4950
.build();
5051
}
5152

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@
99
import org.apache.http.protocol.HttpContext;
1010

1111
import java.io.IOException;
12+
import java.security.PrivateKey;
1213
import java.util.List;
1314

1415
public class ValidationInterceptor implements HttpRequestInterceptor {
1516

1617
private static final List<String> HEADERS = Lists.newArrayList("authorization", "host");
1718

19+
private final String accountSid;
1820
private final String credentialSid;
19-
private final String privateKey;
21+
private final String signingKeySid;
22+
private final PrivateKey privateKey;
2023

21-
public ValidationInterceptor(String credentialSid, String privateKey) {
24+
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey) {
25+
this.accountSid = accountSid;
2226
this.credentialSid = credentialSid;
27+
this.signingKeySid = signingKeySid;
2328
this.privateKey = privateKey;
2429
}
2530

2631
@Override
2732
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
28-
Jwt jwt = ValidationToken.fromHttpRequest(credentialSid, privateKey, request, HEADERS);
33+
Jwt jwt = ValidationToken.fromHttpRequest(accountSid, credentialSid, signingKeySid, privateKey, request, HEADERS);
2934
request.addHeader("Twilio-Client-Validation", jwt.toJwt());
3035
}
3136
}

src/main/java/com/twilio/jwt/Jwt.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.jsonwebtoken.SignatureAlgorithm;
66

77
import java.nio.charset.StandardCharsets;
8+
import java.security.PrivateKey;
89
import java.util.Date;
910
import java.util.HashMap;
1011
import java.util.Map;
@@ -15,6 +16,7 @@
1516
public abstract class Jwt {
1617

1718
private final SignatureAlgorithm algorithm;
19+
private final PrivateKey secretKey;
1820
private final String secret;
1921
private final String issuer;
2022
private final Date expiration;
@@ -35,6 +37,20 @@ public Jwt(
3537
) {
3638
this.algorithm = algorithm;
3739
this.secret = secret;
40+
this.secretKey = null;
41+
this.issuer = issuer;
42+
this.expiration = expiration;
43+
}
44+
45+
public Jwt(
46+
SignatureAlgorithm algorithm,
47+
PrivateKey secretKey,
48+
String issuer,
49+
Date expiration
50+
) {
51+
this.algorithm = algorithm;
52+
this.secret = null;
53+
this.secretKey = secretKey;
3854
this.issuer = issuer;
3955
this.expiration = expiration;
4056
}
@@ -51,11 +67,16 @@ public String toJwt() {
5167

5268
JwtBuilder builder =
5369
Jwts.builder()
54-
.signWith(this.algorithm, this.secret.getBytes(StandardCharsets.UTF_8))
5570
.setHeaderParams(headers)
5671
.setIssuer(this.issuer)
5772
.setExpiration(expiration);
5873

74+
if (this.secret != null) {
75+
builder.signWith(this.algorithm, this.secret.getBytes(StandardCharsets.UTF_8));
76+
} else if (this.secretKey != null) {
77+
builder.signWith(this.algorithm, this.secretKey);
78+
}
79+
5980
if (this.getClaims() != null) {
6081
for (Map.Entry<String, Object> entry : this.getClaims().entrySet()) {
6182
builder.claim(entry.getKey(), entry.getValue());

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.io.InputStreamReader;
22+
import java.security.PrivateKey;
2223
import java.util.Arrays;
2324
import java.util.Collections;
2425
import java.util.Date;
@@ -30,9 +31,12 @@
3031
public class ValidationToken extends Jwt {
3132

3233
private static final HashFunction HASH_FUNCTION = Hashing.sha256();
34+
private static final String CTY = "twilio-pkrv;v=1";
3335
private static final String NEW_LINE = "\n";
3436

37+
private final String accountSid;
3538
private final String credentialSid;
39+
private final String signingKeySid;
3640
private final String method;
3741
private final String uri;
3842
private final String queryString;
@@ -42,12 +46,14 @@ public class ValidationToken extends Jwt {
4246

4347
private ValidationToken(Builder b) {
4448
super(
45-
SignatureAlgorithm.HS256,
49+
SignatureAlgorithm.RS256,
4650
b.privateKey,
4751
b.credentialSid,
4852
new Date(new Date().getTime() + b.ttl * 1000)
4953
);
54+
this.accountSid = b.accountSid;
5055
this.credentialSid = b.credentialSid;
56+
this.signingKeySid = b.signingKeySid;
5157
this.method = b.method;
5258
this.uri = b.uri;
5359
this.queryString = b.queryString;
@@ -59,7 +65,7 @@ private ValidationToken(Builder b) {
5965
@Override
6066
public Map<String, Object> getHeaders() {
6167
Map<String, Object> headers = new HashMap<>();
62-
headers.put("cty", "twilio-pkrv;v=1");
68+
headers.put("cty", CTY);
6369
headers.put("kid", this.credentialSid);
6470
return headers;
6571
}
@@ -68,6 +74,9 @@ public Map<String, Object> getHeaders() {
6874
public Map<String, Object> getClaims() {
6975
Map<String, Object> payload = new HashMap<>();
7076

77+
payload.put("iss", this.signingKeySid);
78+
payload.put("sub", this.accountSid);
79+
7180
// Sort the signed headers
7281
Collections.sort(signedHeaders);
7382
List<String> lowercaseSignedHeaders = Lists.transform(signedHeaders, LOWERCASE_STRING);
@@ -122,12 +131,14 @@ public Map<String, Object> getClaims() {
122131
}
123132

124133
public static ValidationToken fromHttpRequest(
134+
String accountSid,
125135
String credentialSid,
126-
String privateKey,
136+
String signingKeySid,
137+
PrivateKey privateKey,
127138
HttpRequest request,
128139
List<String> signedHeaders
129140
) throws IOException {
130-
Builder builder = new Builder(credentialSid, privateKey);
141+
Builder builder = new Builder(accountSid, credentialSid, signingKeySid, privateKey);
131142

132143
String method = request.getRequestLine().getMethod();
133144
builder.method(method);
@@ -190,18 +201,22 @@ public String apply(String s) {
190201

191202
public static class Builder {
192203

204+
private String accountSid;
193205
private String credentialSid;
194-
private String privateKey;
206+
private String signingKeySid;
207+
private PrivateKey privateKey;
195208
private String method;
196209
private String uri;
197210
private String queryString = "";
198211
private Header[] headers;
199212
private List<String> signedHeaders = Collections.emptyList();
200213
private String requestBody = "";
201-
private int ttl = 3600;
214+
private int ttl = 300;
202215

203-
public Builder(String credentialSid, String privateKey) {
216+
public Builder(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey) {
217+
this.accountSid = accountSid;
204218
this.credentialSid = credentialSid;
219+
this.signingKeySid = signingKeySid;
205220
this.privateKey = privateKey;
206221
}
207222

src/main/java/com/twilio/rest/Domains.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.twilio.rest;
99

1010
public enum Domains {
11+
ACCOUNTS("accounts"),
1112
API("api"),
1213
CHAT("ip-messaging"),
1314
IPMESSAGING("ip-messaging"),

0 commit comments

Comments
 (0)