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

feat: add support for PS256, RS256 PKCV in validationClient #759

Merged
merged 3 commits into from
Aug 23, 2023
Merged
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
15 changes: 10 additions & 5 deletions src/main/java/com/twilio/example/ValidationExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.security.KeyPairGenerator;
import java.util.Base64;

import io.jsonwebtoken.SignatureAlgorithm;

public class ValidationExample {

public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
Expand All @@ -25,7 +27,7 @@ public class ValidationExample {
* @throws TwiMLException if unable to generate TwiML
*/
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 @@ -35,7 +37,8 @@ public static void main(String[] args) throws Exception {
// Use the default rest client
TwilioRestClient client =
new TwilioRestClient.Builder(ACCOUNT_SID, AUTH_TOKEN)
.build();
.region("dev")
.build();

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

// 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(), SignatureAlgorithm.PS256))
.build();

// Make REST API requests
Iterable<Message> messages = Message.reader().read(validationClient);
Expand Down
69 changes: 67 additions & 2 deletions src/main/java/com/twilio/http/ValidationClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
import java.util.Collection;
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 +45,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 +76,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 +114,29 @@ 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 @@ -86,7 +151,7 @@ public ValidationClient(final String accountSid,
.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
23 changes: 22 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,27 @@ 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 +64,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
45 changes: 44 additions & 1 deletion 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 @@ -99,10 +106,37 @@ public static ValidationToken fromHttpRequest(
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,
SignatureAlgorithm algorithm
) throws IOException {
Builder builder = new Builder(accountSid, credentialSid, signingKeySid, privateKey);

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

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

private SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;

/**
* Create a new ValidationToken Builder.
*
Expand Down Expand Up @@ -206,6 +242,13 @@ 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
57 changes: 56 additions & 1 deletion src/test/java/com/twilio/jwt/validation/ValidationTokenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.twilio.jwt.Jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException;
import org.apache.http.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.message.BasicHeader;
Expand All @@ -20,6 +22,7 @@
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
Expand All @@ -28,6 +31,13 @@

import static org.mockito.Mockito.when;

import static io.jsonwebtoken.SignatureAlgorithm.PS256;
import static io.jsonwebtoken.SignatureAlgorithm.PS384;
import static io.jsonwebtoken.SignatureAlgorithm.PS512;
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
import static io.jsonwebtoken.SignatureAlgorithm.RS384;
import static io.jsonwebtoken.SignatureAlgorithm.RS512;

public class ValidationTokenTest {

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

private PublicKey publicKey;

@Mock
private HttpRequest request;

Expand All @@ -61,6 +73,7 @@ public void setup() throws Exception {
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
privateKey = pair.getPrivate();
publicKey = pair.getPublic();
}

@Test
Expand All @@ -80,12 +93,54 @@ public void testTokenBuilder() {
.parseClaimsJws(jwt.toJwt())
.getBody();


this.validateToken(claims);
Assert.assertEquals("authorization;host", claims.get("hrh"));
Assert.assertEquals("4dc9b67bed579647914587b0e22a1c65c1641d8674797cd82de65e766cce5f80", claims.get("rqh"));
}

@Test
public void testTokenValidAlgorithms() {
List<SignatureAlgorithm> validAlgorithms = Arrays.asList(RS256, PS256);
for (SignatureAlgorithm alg : validAlgorithms) {
Jwt jwt = new ValidationToken.Builder(ACCOUNT_SID, CREDENTIAL_SID, SIGNING_KEY_SID, privateKey)
.algorithm(alg)
.method("GET")
.uri("/Messages")
.queryString("PageSize=5&Limit=10")
.headers(headers)
.signedHeaders(SIGNED_HEADERS)
.requestBody("foobar")
.build();

Claims claims =
Jwts.parserBuilder().setSigningKey(publicKey).build()
.parseClaimsJws(jwt.toJwt())
.getBody();
validateToken(claims);
}
}

@Test(expected = IllegalArgumentException.class)
public void testTokenInvalidAlgorithms() {
List<SignatureAlgorithm> validAlgorithms = Arrays.asList(SignatureAlgorithm.HS256, SignatureAlgorithm.ES256, RS384, RS512, PS384, PS512);
for (SignatureAlgorithm alg : validAlgorithms) {
Jwt jwt = new ValidationToken.Builder(ACCOUNT_SID, CREDENTIAL_SID, SIGNING_KEY_SID, privateKey)
.algorithm(alg)
.method("GET")
.uri("/Messages")
.queryString("PageSize=5&Limit=10")
.headers(headers)
.signedHeaders(SIGNED_HEADERS)
.requestBody("foobar")
.build();


Jwts.parserBuilder().setSigningKey(publicKey).build()
.parseClaimsJws(jwt.toJwt())
.getBody();
}
}

@Test
public void testTokenFromHttpRequest() throws IOException {
when(request.getRequestLine()).thenReturn(requestLine);
Expand Down