Skip to content

Commit c1a45c6

Browse files
authored
Merge pull request #302 from twilio/asym-auth
Asymmetric auth
2 parents ec48da6 + facdc1b commit c1a45c6

File tree

9 files changed

+628
-24
lines changed

9 files changed

+628
-24
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.twilio.example;
2+
3+
4+
import com.twilio.http.TwilioRestClient;
5+
import com.twilio.http.ValidationClient;
6+
import com.twilio.rest.accounts.v1.credential.PublicKey;
7+
import com.twilio.rest.api.v2010.account.Message;
8+
import com.twilio.rest.api.v2010.account.NewSigningKey;
9+
import com.twilio.twiml.TwiMLException;
10+
import com.twilio.type.PhoneNumber;
11+
import org.apache.commons.codec.binary.Base64;
12+
13+
import java.security.KeyPair;
14+
import java.security.KeyPairGenerator;
15+
16+
public class ValidationExample {
17+
18+
public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
19+
public static final String AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");
20+
21+
/**
22+
* Example Twilio usage.
23+
*
24+
* @param args command line args
25+
* @throws TwiMLException if unable to generate TwiML
26+
*/
27+
public static void main(String[] args) throws Exception {
28+
29+
// Generate public/private key pair
30+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
31+
keyGen.initialize(2048);
32+
KeyPair pair = keyGen.generateKeyPair();
33+
java.security.PublicKey pk = pair.getPublic();
34+
35+
// Use the default rest client
36+
TwilioRestClient client =
37+
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
38+
.build();
39+
40+
// Create a public key and signing key using the default client
41+
PublicKey key = PublicKey.creator(
42+
Base64.encodeBase64String(pk.getEncoded())
43+
).setFriendlyName("Public Key").create(client);
44+
45+
NewSigningKey signingKey = NewSigningKey.creator().create(client);
46+
47+
// Switch to validation client as the default client
48+
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+
53+
// Make REST API requests
54+
Iterable<Message> messages = Message.reader().read(validationClient);
55+
for (Message m : messages) {
56+
System.out.println(m.getBody());
57+
}
58+
59+
Message m = Message.creator(
60+
new PhoneNumber("+1XXXXXXXXXX"),
61+
new PhoneNumber("+1XXXXXXXXXX"),
62+
"Asymmetric Auth Test"
63+
).create(validationClient);
64+
System.out.println(m.getSid());
65+
66+
}
67+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.twilio.http;
2+
3+
4+
import com.google.common.collect.Lists;
5+
import com.twilio.Twilio;
6+
import com.twilio.exception.ApiException;
7+
import org.apache.http.Header;
8+
import org.apache.http.HttpHeaders;
9+
import org.apache.http.HttpResponse;
10+
import org.apache.http.HttpVersion;
11+
import org.apache.http.client.config.RequestConfig;
12+
import org.apache.http.client.methods.RequestBuilder;
13+
import org.apache.http.impl.client.HttpClientBuilder;
14+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
15+
import org.apache.http.message.BasicHeader;
16+
17+
import java.io.IOException;
18+
import java.nio.charset.StandardCharsets;
19+
import java.security.PrivateKey;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
public class ValidationClient extends HttpClient {
25+
26+
private static final int CONNECTION_TIMEOUT = 10000;
27+
private static final int SOCKET_TIMEOUT = 30500;
28+
29+
private final org.apache.http.client.HttpClient client;
30+
31+
public ValidationClient(String accountSid, String credentialSid, String signingKey, PrivateKey privateKey) {
32+
RequestConfig config = RequestConfig.custom()
33+
.setConnectTimeout(CONNECTION_TIMEOUT)
34+
.setSocketTimeout(SOCKET_TIMEOUT)
35+
.build();
36+
37+
Collection<Header> headers = Lists.<Header>newArrayList(
38+
new BasicHeader("X-Twilio-Client", "java-" + Twilio.VERSION),
39+
new BasicHeader(HttpHeaders.USER_AGENT, "twilio-java/" + Twilio.VERSION + " (" + Twilio.JAVA_VERSION + ")"),
40+
new BasicHeader(HttpHeaders.ACCEPT, "application/json"),
41+
new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "utf-8")
42+
);
43+
44+
client = HttpClientBuilder.create()
45+
.setConnectionManager(new PoolingHttpClientConnectionManager())
46+
.setDefaultRequestConfig(config)
47+
.setDefaultHeaders(headers)
48+
.setMaxConnPerRoute(10)
49+
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey))
50+
.build();
51+
}
52+
53+
@Override
54+
public Response makeRequest(Request request) {
55+
RequestBuilder builder = RequestBuilder.create(request.getMethod().toString())
56+
.setUri(request.constructURL().toString())
57+
.setVersion(HttpVersion.HTTP_1_1)
58+
.setCharset(StandardCharsets.UTF_8);
59+
60+
if (request.requiresAuthentication()) {
61+
builder.addHeader(HttpHeaders.AUTHORIZATION, request.getAuthString());
62+
}
63+
64+
HttpMethod method = request.getMethod();
65+
if (method == HttpMethod.POST) {
66+
builder.addHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
67+
68+
for (Map.Entry<String, List<String>> entry : request.getPostParams().entrySet()) {
69+
for (String value : entry.getValue()) {
70+
builder.addParameter(entry.getKey(), value);
71+
}
72+
}
73+
}
74+
75+
try {
76+
HttpResponse response = client.execute(builder.build());
77+
return new Response(
78+
response.getEntity() == null ? null : response.getEntity().getContent(),
79+
response.getStatusLine().getStatusCode()
80+
);
81+
} catch (IOException e) {
82+
throw new ApiException(e.getMessage());
83+
}
84+
}
85+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.twilio.http;
2+
3+
import com.google.common.collect.Lists;
4+
import com.twilio.jwt.Jwt;
5+
import com.twilio.jwt.validation.ValidationToken;
6+
import org.apache.http.HttpException;
7+
import org.apache.http.HttpRequest;
8+
import org.apache.http.HttpRequestInterceptor;
9+
import org.apache.http.protocol.HttpContext;
10+
11+
import java.io.IOException;
12+
import java.security.PrivateKey;
13+
import java.util.List;
14+
15+
public class ValidationInterceptor implements HttpRequestInterceptor {
16+
17+
private static final List<String> HEADERS = Lists.newArrayList("authorization", "host");
18+
19+
private final String accountSid;
20+
private final String credentialSid;
21+
private final String signingKeySid;
22+
private final PrivateKey privateKey;
23+
24+
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey) {
25+
this.accountSid = accountSid;
26+
this.credentialSid = credentialSid;
27+
this.signingKeySid = signingKeySid;
28+
this.privateKey = privateKey;
29+
}
30+
31+
@Override
32+
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
33+
Jwt jwt = ValidationToken.fromHttpRequest(accountSid, credentialSid, signingKeySid, privateKey, request, HEADERS);
34+
request.addHeader("Twilio-Client-Validation", jwt.toJwt());
35+
}
36+
}

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import io.jsonwebtoken.Jwts;
55
import io.jsonwebtoken.SignatureAlgorithm;
66

7-
import java.nio.charset.StandardCharsets;
7+
import javax.crypto.spec.SecretKeySpec;
8+
import java.security.Key;
89
import java.util.Date;
910
import java.util.HashMap;
1011
import java.util.Map;
@@ -15,7 +16,7 @@
1516
public abstract class Jwt {
1617

1718
private final SignatureAlgorithm algorithm;
18-
private final String secret;
19+
private final Key secretKey;
1920
private final String issuer;
2021
private final Date expiration;
2122

@@ -32,9 +33,31 @@ public Jwt(
3233
String secret,
3334
String issuer,
3435
Date expiration
36+
) {
37+
this(
38+
algorithm,
39+
new SecretKeySpec(secret.getBytes(), algorithm.getJcaName()),
40+
issuer,
41+
expiration
42+
);
43+
}
44+
45+
/**
46+
* Create a new JWT.
47+
*
48+
* @param algorithm algorithm to use
49+
* @param secretKey secret key
50+
* @param issuer JWT issuer
51+
* @param expiration expiration Date
52+
*/
53+
public Jwt(
54+
SignatureAlgorithm algorithm,
55+
Key secretKey,
56+
String issuer,
57+
Date expiration
3558
) {
3659
this.algorithm = algorithm;
37-
this.secret = secret;
60+
this.secretKey = secretKey;
3861
this.issuer = issuer;
3962
this.expiration = expiration;
4063
}
@@ -51,7 +74,7 @@ public String toJwt() {
5174

5275
JwtBuilder builder =
5376
Jwts.builder()
54-
.signWith(this.algorithm, this.secret.getBytes(StandardCharsets.UTF_8))
77+
.signWith(this.algorithm, this.secretKey)
5578
.setHeaderParams(headers)
5679
.setIssuer(this.issuer)
5780
.setExpiration(expiration);

0 commit comments

Comments
 (0)