Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PS256 algorithm for PKCV #755

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ try {
}
```

### Use a Client With PKCV Authentication
### Use a Client With PKCV Authentication

Additional documentation here: https://twilio.com/docs/iam/pkcv/quickstart

Expand Down
22 changes: 15 additions & 7 deletions src/main/java/com/twilio/example/ValidationExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
import java.security.KeyPairGenerator;
import java.util.Base64;

public class ValidationExample {
import io.jsonwebtoken.SignatureAlgorithm;

import static com.twilio.http.HttpClient.DEFAULT_REQUEST_CONFIG;

public class ValidationExample {
public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
public static final String AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");

Expand All @@ -26,6 +29,7 @@ public class ValidationExample {
*/
public static void main(String[] args) throws Exception {

//Twilio.setRegion("dev");
// Generate public/private key pair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
Expand All @@ -34,21 +38,25 @@ public static void main(String[] args) throws Exception {

// Use the default rest client
TwilioRestClient client =
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
.build();
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
.region("dev")
.build();

// Create a public key and signing key using the default client
PublicKey key = PublicKey.creator(
Base64.getEncoder().encodeToString(pk.getEncoded())
Base64.getEncoder().encodeToString(pk.getEncoded())
).setFriendlyName("Public Key").create(client);

NewSigningKey signingKey = NewSigningKey.creator().create(client);

// Switch to validation client as the default client
TwilioRestClient validationClient = new TwilioRestClient.Builder(signingKey.getSid(), signingKey.getSecret())
.accountSid(ACCOUNT_SID)
.httpClient(new ValidationClient(ACCOUNT_SID, key.getSid(), signingKey.getSid(), pair.getPrivate()))
.build();
.accountSid(ACCOUNT_SID)
.region("dev")
// Validation client supports RS256 or PS256 algorithm. Default is RS256.
.httpClient(new ValidationClient(ACCOUNT_SID, key.getSid(), signingKey.getSid(), pair.getPrivate(), DEFAULT_REQUEST_CONFIG,
SignatureAlgorithm.PS256))
.build();

// Make REST API requests
Iterable<Message> messages = Message.reader().read(validationClient);
Expand Down
72 changes: 70 additions & 2 deletions src/main/java/com/twilio/http/ValidationClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.jsonwebtoken.SignatureAlgorithm;

import static io.jsonwebtoken.SignatureAlgorithm.PS256;
import static io.jsonwebtoken.SignatureAlgorithm.RS256;

public class ValidationClient extends HttpClient {

Expand All @@ -39,6 +47,23 @@ public ValidationClient(final String accountSid,
this(accountSid, credentialSid, signingKey, privateKey, DEFAULT_REQUEST_CONFIG);
}

/**
* Create a new ValidationClient.
*
* @param accountSid Twilio Account SID
* @param credentialSid Twilio Credential SID
* @param signingKey Twilio Signing key
* @param privateKey Private Key
* @param algorithm Client validation algorithm
*/
public ValidationClient(final String accountSid,
final String credentialSid,
final String signingKey,
final PrivateKey privateKey,
final SignatureAlgorithm algorithm) {
this(accountSid, credentialSid, signingKey, privateKey, DEFAULT_REQUEST_CONFIG, algorithm);
}

/**
* Create a new ValidationClient.
*
Expand All @@ -53,7 +78,26 @@ public ValidationClient(final String accountSid,
final String signingKey,
final PrivateKey privateKey,
final RequestConfig requestConfig) {
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG);
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG, RS256);
}

/**
* Create a new ValidationClient.
*
* @param accountSid Twilio Account SID
* @param credentialSid Twilio Credential SID
* @param signingKey Twilio Signing key
* @param privateKey Private Key
* @param requestConfig HTTP Request Config
* @param algorithm Client validation algorithm
*/
public ValidationClient(final String accountSid,
final String credentialSid,
final String signingKey,
final PrivateKey privateKey,
final RequestConfig requestConfig,
final SignatureAlgorithm algorithm) {
this(accountSid, credentialSid, signingKey, privateKey, requestConfig, DEFAULT_SOCKET_CONFIG, algorithm);
}

/**
Expand All @@ -72,6 +116,28 @@ public ValidationClient(final String accountSid,
final PrivateKey privateKey,
final RequestConfig requestConfig,
final SocketConfig socketConfig) {

this(accountSid, credentialSid, signingKey, privateKey, requestConfig, socketConfig, RS256);
}

/**
* Create a new ValidationClient.
*
* @param accountSid Twilio Account SID
* @param credentialSid Twilio Credential SID
* @param signingKey Twilio Signing key
* @param privateKey Private Key
* @param requestConfig HTTP Request Config
* @param socketConfig HTTP Socket Config
* @param algorithm Client validation algorithm
*/
public ValidationClient(final String accountSid,
final String credentialSid,
final String signingKey,
final PrivateKey privateKey,
final RequestConfig requestConfig,
final SocketConfig socketConfig,
final SignatureAlgorithm algorithm) {
Collection<BasicHeader> headers = Arrays.asList(
new BasicHeader("X-Twilio-Client", "java-" + Twilio.VERSION),
new BasicHeader(HttpHeaders.ACCEPT, "application/json"),
Expand All @@ -81,12 +147,14 @@ public ValidationClient(final String accountSid,
final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setDefaultSocketConfig(socketConfig);

// should I validate algorithms here? Nah lets do it in validation token

client = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.setDefaultHeaders(headers)
.setMaxConnPerRoute(10)
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey))
.addInterceptorLast(new ValidationInterceptor(accountSid, credentialSid, signingKey, privateKey, algorithm))
.setRedirectStrategy(this.getRedirectStrategy())
.build();
}
Expand Down
21 changes: 20 additions & 1 deletion src/main/java/com/twilio/http/ValidationInterceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.util.Arrays;
import java.util.List;

import io.jsonwebtoken.SignatureAlgorithm;

public class ValidationInterceptor implements HttpRequestInterceptor {

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

/**
* Create a new ValidationInterceptor.
Expand All @@ -30,10 +33,25 @@ public class ValidationInterceptor implements HttpRequestInterceptor {
* @param privateKey Private Key
*/
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey) {
this(accountSid, credentialSid, signingKeySid, privateKey, SignatureAlgorithm.RS256);
}

/**
* Create a new ValidationInterceptor.
*
* @param accountSid Twilio Acocunt SID
* @param credentialSid Twilio Credential SID
* @param signingKeySid Twilio Signing Key
* @param privateKey Private Key
* @param algorithm Client validaiton algorithm
*/
public ValidationInterceptor(String accountSid, String credentialSid, String signingKeySid, PrivateKey privateKey,
SignatureAlgorithm algorithm) {
this.accountSid = accountSid;
this.credentialSid = credentialSid;
this.signingKeySid = signingKeySid;
this.privateKey = privateKey;
this.algorithm = algorithm;
}

@Override
Expand All @@ -44,7 +62,8 @@ public void process(HttpRequest request, HttpContext context) throws HttpExcepti
signingKeySid,
privateKey,
request,
HEADERS
HEADERS,
algorithm
);
request.addHeader("Twilio-Client-Validation", jwt.toJwt());
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/twilio/jwt/Jwt.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
Expand Down
48 changes: 46 additions & 2 deletions src/main/java/com/twilio/jwt/validation/ValidationToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.impl.auth.UnsupportedDigestAlgorithmException;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.*;
import java.util.function.Function;

import static io.jsonwebtoken.SignatureAlgorithm.PS256;
import static io.jsonwebtoken.SignatureAlgorithm.RS256;

public class ValidationToken extends Jwt {

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

private static final Set<SignatureAlgorithm> supportedAlgorithms
= Collections.unmodifiableSet(new HashSet<>(Arrays.asList(PS256, RS256)));

private ValidationToken(Builder b) {
super(
SignatureAlgorithm.RS256,
b.algorithm,
b.privateKey,
b.credentialSid,
new Date(new Date().getTime() + b.ttl * 1000)
Expand Down Expand Up @@ -91,19 +98,47 @@ public Map<String, Object> getClaims() {
* @return The ValidationToken generated from the HttpRequest
* @throws IOException when unable to generate
*/
public static ValidationToken fromHttpRequest(
String accountSid,
String credentialSid,
String signingKeySid,
PrivateKey privateKey,
HttpRequest request,
List<String> signedHeaders
) throws IOException {
return fromHttpRequest(accountSid, credentialSid, signingKeySid, privateKey, request, signedHeaders, SignatureAlgorithm.RS256);
}

/**
* Create a ValidationToken from an HTTP Request.
*
* @param accountSid Twilio Account SID
* @param credentialSid Twilio Credential SID
* @param signingKeySid Twilio Signing Key SID
* @param privateKey Private Key
* @param request HTTP Request
* @param signedHeaders Headers to sign
* @param algorithm Client validation algorithm
* @return The ValidationToken generated from the HttpRequest
* @throws IOException when unable to generate
*/
public static ValidationToken fromHttpRequest(
String accountSid,
String credentialSid,
String signingKeySid,
PrivateKey privateKey,
HttpRequest request,
List<String> signedHeaders
List<String> signedHeaders,
SignatureAlgorithm algorithm
) throws IOException {
Builder builder = new Builder(accountSid, credentialSid, signingKeySid, privateKey);

String method = request.getRequestLine().getMethod();
builder.method(method);

// I think it can go here...
builder.algorithm(algorithm);

String uri = request.getRequestLine().getUri();
if (uri.contains("?")) {
String[] uriParts = uri.split("\\?");
Expand Down Expand Up @@ -150,6 +185,7 @@ public static class Builder {
private List<String> signedHeaders = Collections.emptyList();
private String requestBody = "";
private int ttl = 300;
private SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;

/**
* Create a new ValidationToken Builder.
Expand Down Expand Up @@ -206,6 +242,14 @@ public Builder ttl(int ttl) {
return this;
}

public Builder algorithm(SignatureAlgorithm algorithm) {
if (!supportedAlgorithms.contains(algorithm)) {
throw new IllegalArgumentException("Not supported!");
}
this.algorithm = algorithm;
return this;
}

public ValidationToken build() {
return new ValidationToken(this);
}
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/twilio/http/ValidationClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.security.KeyPair;
import java.security.KeyPairGenerator;

import io.jsonwebtoken.SignatureAlgorithm;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
Expand Down
Loading
Loading