Skip to content
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
68 changes: 56 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ URI uri = URI.create("https://sandbox.api.mastercard.com/service");
String method = "POST";
String payload = "Hello world!";
Charset charset = StandardCharsets.UTF_8;
String authHeader = OAuth.getAuthorizationHeader(uri, method, payload, charset, consumerKey, signingKey);
String authHeader = OAuth.getAuthorizationHeader(uri, method, payload, charset, consumerKey, signingKey); // uses RSA_SHA256 as the default signature method
```
Alternatively, you can specify the signature method as well:
```java
String authHeader = OAuth.getAuthorizationHeader(uri, method, payload, charset, consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256);
```

### Signing HTTP Client Request Objects <a name="signing-http-client-request-objects"></a>
Expand All @@ -113,7 +117,13 @@ HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json; charset=" + charset.name());

HttpsUrlConnectionSigner signer = new HttpsUrlConnectionSigner(charset, consumerKey, signingKey);
HttpsUrlConnectionSigner signer = new HttpsUrlConnectionSigner(charset, consumerKey, signingKey); // uses RSA_SHA256 as the default signature method
signer.sign(con, payload);
```

You can also specify the signature method when creating the signer object:
```java
HttpsUrlConnectionSigner signer = new HttpsUrlConnectionSigner(charset, consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256);
signer.sign(con, payload);
```

Expand All @@ -125,7 +135,9 @@ HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("https://sandbox.api.mastercard.com/service");
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON));

ApacheHttpClient4Signer signer = new ApacheHttpClient4Signer(consumerKey, signingKey);
ApacheHttpClient4Signer signer = new ApacheHttpClient4Signer(consumerKey, signingKey); // uses RSA_SHA256 as the default signature method
// You can also specify the signature method:
// ApacheHttpClient4Signer signer = new ApacheHttpClient4Signer(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256);
signer.sign(httpPost);
```

Expand All @@ -140,7 +152,9 @@ Request.Builder request = new Request.Builder()
.url("https://sandbox.api.mastercard.com/service")
.post(body);

OkHttpSigner signer = new OkHttpSigner(consumerKey, signingKey);
OkHttpSigner signer = new OkHttpSigner(consumerKey, signingKey); // uses RSA_SHA256 as the default signature method
// You can also specify the signature method:
// OkHttpSigner signer = new OkHttpSigner(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256);
signer.sign(request);
```

Expand All @@ -152,7 +166,9 @@ ClientRequest request = ClientRequest.create(HttpMethod.POST, URI.create("https:
.body(BodyInserters.fromValue(new BodyInserterWrapper(yourRequestObject)))
.build();

SpringWebfluxSigner signer = new SpringWebfluxSigner(consumerKey, signingKey);
SpringWebfluxSigner signer = new SpringWebfluxSigner(consumerKey, signingKey); // uses RSA_SHA256 as the default signature method
// You can also specify the signature method:
// SpringWebfluxSigner signer = new SpringWebfluxSigner(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256);
ClientRequest signedRequest = signer.sign(request);
client.exchange(signedRequest);
```
Expand Down Expand Up @@ -193,7 +209,11 @@ See also:
ApiClient client = new ApiClient();
client.setBasePath("https://sandbox.api.mastercard.com");
List<Interceptor> interceptors = client.getHttpClient().interceptors();
interceptors.add(new OkHttp2OAuth1Interceptor(consumerKey, signingKey));
interceptors.add(
new OkHttp2OAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new OkHttp2OAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
);
ServiceApi serviceApi = new ServiceApi(client);
// ...
```
Expand All @@ -207,7 +227,11 @@ client.setHttpClient(
client.getHttpClient()
.newBuilder()
.proxy(proxy) // Optional proxy
.addInterceptor(new OkHttpOAuth1Interceptor(consumerKey, signingKey))
.addInterceptor(
new OkHttpOAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new OkHttpOAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
)
.build()
);
ServiceApi serviceApi = new ServiceApi(client);
Expand All @@ -231,7 +255,11 @@ ApiClient client = new ApiClient();
client.setBasePath("https://sandbox.api.mastercard.com");
Feign.Builder feignBuilder = client.getFeignBuilder();
ArrayList<RequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new OpenFeignOAuth1Interceptor(consumerKey, signingKey, client.getBasePath()));
interceptors.add(
new OpenFeignOAuth1Interceptor(consumerKey, signingKey, client.getBasePath()) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new OpenFeignOAuth1Interceptor(consumerKey, signingKey, client.getBasePath(), SignatureMethod.RSA_PSS_SHA256)
);
feignBuilder.requestInterceptors(interceptors);
ServiceApi serviceApi = client.buildClient(ServiceApi.class);
// ...
Expand All @@ -254,7 +282,11 @@ ApiClient client = new ApiClient();
RestAdapter.Builder adapterBuilder = client.getAdapterBuilder();
adapterBuilder.setEndpoint("https://sandbox.api.mastercard.com");
List<Interceptor> interceptors = client.getOkClient().interceptors();
interceptors.add(new OkHttp2OAuth1Interceptor(consumerKey, signingKey));
interceptors.add(
new OkHttp2OAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new OkHttp2OAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
);
ServiceApi serviceApi = client.createService(ServiceApi.class);
// ...
```
Expand All @@ -276,7 +308,11 @@ ApiClient client = new ApiClient();
Retrofit.Builder adapterBuilder = client.getAdapterBuilder();
adapterBuilder.baseUrl("https://sandbox.api.mastercard.com");
OkHttpClient.Builder okBuilder = client.getOkBuilder();
okBuilder.addInterceptor(new OkHttpOAuth1Interceptor(consumerKey, signingKey));
okBuilder.addInterceptor(
new OkHttpOAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new OkHttpOAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
);
ServiceApi serviceApi = client.createService(ServiceApi.class);
// ...
```
Expand All @@ -297,7 +333,11 @@ ServiceApi serviceApi = client.createService(ServiceApi.class);
HttpRequestInitializer initializer = new HttpRequestInitializer() {
@Override
public void initialize(HttpRequest request) {
request.setInterceptor(new HttpExecuteOAuth1Interceptor(consumerKey, signingKey));
request.setInterceptor(
new HttpExecuteOAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new HttpExecuteOAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
);
}
};
ApiClient client = new ApiClient("https://sandbox.api.mastercard.com", null, initializer, null);
Expand All @@ -320,7 +360,11 @@ ServiceApi serviceApi = client.serviceApi();
```java
WebClient.Builder webClientBuilder = WebClient.builder()
.baseUrl("https://api.mastercard.com/service")
.filter(new SpringWebfluxOAuth1Interceptor(consumerKey, signingKey));
.filter(
new SpringWebfluxOAuth1Interceptor(consumerKey, signingKey) // uses RSA_SHA256 as the default signature method
// if you want to specify the signature method
// new SpringWebfluxOAuth1Interceptor(consumerKey, signingKey, SignatureMethod.RSA_PSS_SHA256)
);

ApiClient apiClient = new ApiClient(webClientBuilder);
ServiceApi serviceApi = client.serviceApi();
Expand Down
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.mastercard.developer</groupId>
<artifactId>oauth1-signer</artifactId>
<version>1.5.6</version>
<version>1.6.0</version>
<packaging>jar</packaging>
<description>Zero dependency library for generating a Mastercard API compliant OAuth signature</description>
<url>https://github.com/Mastercard/oauth1-signer-java</url>
Expand Down Expand Up @@ -113,6 +113,12 @@
<version>3.27.7</version> <!-- We can't bump this one because of the Java 7 support -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.21.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;
import com.mastercard.developer.oauth.OAuth;
import com.mastercard.developer.oauth.SignatureMethod;
import com.mastercard.developer.signers.GoogleApiClientSigner;

import java.io.IOException;
Expand All @@ -16,7 +18,11 @@ public class HttpExecuteOAuth1Interceptor implements HttpExecuteInterceptor {
private final GoogleApiClientSigner signer;

public HttpExecuteOAuth1Interceptor(String consumerKey, PrivateKey signingKey) {
this.signer = new GoogleApiClientSigner(consumerKey, signingKey);
this(consumerKey, signingKey, OAuth.DEFAULT_SIGNATURE_METHOD);
}

public HttpExecuteOAuth1Interceptor(String consumerKey, PrivateKey signingKey, SignatureMethod signatureMethod) {
this.signer = new GoogleApiClientSigner(consumerKey, signingKey, signatureMethod);
}

public void intercept(HttpRequest request) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mastercard.developer.interceptors;

import com.mastercard.developer.oauth.OAuth;
import com.mastercard.developer.oauth.SignatureMethod;
import com.mastercard.developer.signers.OkHttp2Signer;
import com.squareup.okhttp.*;

Expand All @@ -14,7 +16,11 @@ public class OkHttp2OAuth1Interceptor implements Interceptor {
private final OkHttp2Signer signer;

public OkHttp2OAuth1Interceptor(String consumerKey, PrivateKey signingKey) {
this.signer = new OkHttp2Signer(consumerKey, signingKey);
this(consumerKey, signingKey, OAuth.DEFAULT_SIGNATURE_METHOD);
}

public OkHttp2OAuth1Interceptor(String consumerKey, PrivateKey signingKey, SignatureMethod signatureMethod) {
this.signer = new OkHttp2Signer(consumerKey, signingKey, signatureMethod);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mastercard.developer.interceptors;

import com.mastercard.developer.oauth.OAuth;
import com.mastercard.developer.oauth.SignatureMethod;
import com.mastercard.developer.signers.OkHttpSigner;
import okhttp3.*;

Expand All @@ -14,7 +16,11 @@ public class OkHttpOAuth1Interceptor implements Interceptor {
private final OkHttpSigner signer;

public OkHttpOAuth1Interceptor(String consumerKey, PrivateKey signingKey) {
this.signer = new OkHttpSigner(consumerKey, signingKey);
this(consumerKey, signingKey, OAuth.DEFAULT_SIGNATURE_METHOD);
}

public OkHttpOAuth1Interceptor(String consumerKey, PrivateKey signingKey, SignatureMethod signatureMethod) {
this.signer = new OkHttpSigner(consumerKey, signingKey, signatureMethod);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mastercard.developer.interceptors;

import com.mastercard.developer.oauth.OAuth;
import com.mastercard.developer.oauth.SignatureMethod;
import com.mastercard.developer.signers.OpenFeignSigner;
import feign.RequestInterceptor;
import feign.RequestTemplate;
Expand All @@ -15,7 +17,11 @@ public class OpenFeignOAuth1Interceptor implements RequestInterceptor {
private final OpenFeignSigner signer;

public OpenFeignOAuth1Interceptor(String consumerKey, PrivateKey signingKey, String baseUri) {
this.signer = new OpenFeignSigner(consumerKey, signingKey, baseUri);
this(consumerKey, signingKey, baseUri, OAuth.DEFAULT_SIGNATURE_METHOD);
}

public OpenFeignOAuth1Interceptor(String consumerKey, PrivateKey signingKey, String baseUri, SignatureMethod signatureMethod) {
this.signer = new OpenFeignSigner(consumerKey, signingKey, baseUri, signatureMethod);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mastercard.developer.interceptors;

import com.mastercard.developer.oauth.OAuth;
import com.mastercard.developer.oauth.SignatureMethod;
import com.mastercard.developer.signers.SpringWebfluxSigner;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
Expand All @@ -17,7 +19,11 @@ public class SpringWebfluxOAuth1Interceptor implements ExchangeFilterFunction {
private final SpringWebfluxSigner signer;

public SpringWebfluxOAuth1Interceptor(String consumerKey, PrivateKey signingKey) {
this.signer = new SpringWebfluxSigner(consumerKey, signingKey);
this(consumerKey, signingKey, OAuth.DEFAULT_SIGNATURE_METHOD);
}

public SpringWebfluxOAuth1Interceptor(String consumerKey, PrivateKey signingKey, SignatureMethod signatureMethod) {
this.signer = new SpringWebfluxSigner(consumerKey, signingKey, signatureMethod);
}

@Override
Expand Down
51 changes: 43 additions & 8 deletions src/main/java/com/mastercard/developer/oauth/OAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ private OAuth() {

public static final String EMPTY_STRING = "";
public static final String AUTHORIZATION_HEADER_NAME = "Authorization";
public static final SignatureMethod DEFAULT_SIGNATURE_METHOD = SignatureMethod.RSA_SHA256;

private static final Logger LOG = Logger.getLogger(OAuth.class.getName());
private static final String HASH_ALGORITHM = "SHA-256";
private static final String BODY_HASH_ALGORITHM = "SHA-256";
private static final int NONCE_LENGTH = 16;
private static final String ALPHA_NUMERIC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

/**
* Creates a Mastercard API compliant OAuth Authorization header
* Creates a Mastercard API compliant OAuth Authorization header, using RSA-SHA256 as the signature method
*
* @param uri Target URI for this request
* @param method HTTP method of the request
Expand All @@ -40,17 +41,34 @@ private OAuth() {
* @param consumerKey Consumer key set up in a Mastercard Developer Portal project
* @param signingKey The private key that will be used for signing the request that corresponds to the consumerKey
* @return Valid OAuth1.0a signature with a body hash when payload is present
* @see #getAuthorizationHeader(URI, String, String, Charset, String, PrivateKey, SignatureMethod)
*/
public static String getAuthorizationHeader(URI uri, String method, String payload, Charset charset, String consumerKey, PrivateKey signingKey) {
return getAuthorizationHeader(uri, method, payload, charset, consumerKey, signingKey, DEFAULT_SIGNATURE_METHOD);
}

/**
* Creates a Mastercard API compliant OAuth Authorization header
*
* @param uri Target URI for this request
* @param method HTTP method of the request
* @param payload Payload (nullable)
* @param charset Charset encoding of the request
* @param consumerKey Consumer key set up in a Mastercard Developer Portal project
* @param signingKey The private key that will be used for signing the request that corresponds to the consumerKey
* @param signMethod The signature method to use when signing the request
* @return Valid OAuth1.0a signature with a body hash when payload is present
*/
public static String getAuthorizationHeader(URI uri, String method, String payload, Charset charset, String consumerKey, PrivateKey signingKey, SignatureMethod signMethod) {
TreeMap<String, List<String>> queryParams = extractQueryParams(uri, charset);

HashMap<String, String> oauthParams = new HashMap<>();
oauthParams.put("oauth_consumer_key", consumerKey);
oauthParams.put("oauth_nonce", getNonce());
oauthParams.put("oauth_signature_method", "RSA-" + HASH_ALGORITHM.replace("-", ""));
oauthParams.put("oauth_signature_method", signMethod.getOauthName());
oauthParams.put("oauth_timestamp", getTimestamp());
oauthParams.put("oauth_version", "1.0");
oauthParams.put("oauth_body_hash", getBodyHash(payload, charset, HASH_ALGORITHM));
oauthParams.put("oauth_body_hash", getBodyHash(payload, charset, BODY_HASH_ALGORITHM));

// Combine query and oauth_ parameters into lexicographically sorted string
String paramString = toOauthParamString(queryParams, oauthParams);
Expand All @@ -62,7 +80,7 @@ public static String getAuthorizationHeader(URI uri, String method, String paylo
String sbs = getSignatureBaseString(method, baseUri, paramString, charset);

// Signature
String signature = signSignatureBaseString(sbs, signingKey, charset);
String signature = signSignatureBaseString(sbs, signingKey, charset, signMethod);
oauthParams.put("oauth_signature", Util.percentEncode(signature, charset));

return getAuthorizationString(oauthParams);
Expand Down Expand Up @@ -252,6 +270,19 @@ static String getBodyHash(String payload, Charset charset, String hashAlg) {
return Util.b64Encode(hash);
}

/**
* Signs the signature base string using an RSA private key and RSA-SHA256 as the signature method.
*
* @param sbs Signature base string formatted as per https://tools.ietf.org/html/rfc5849#section-3.4.1
* @param signingKey Private key of the RSA key pair that was established with the service provider
* @param charset Charset encoding of the request
* @return RSA signature matching the contents of signature base string
* @see #signSignatureBaseString(String, PrivateKey, Charset, SignatureMethod)
*/
static String signSignatureBaseString(String sbs, PrivateKey signingKey, Charset charset) {
return signSignatureBaseString(sbs, signingKey, charset, DEFAULT_SIGNATURE_METHOD);
}

/**
* Signs the signature base string using an RSA private key. The methodology is described at
* https://tools.ietf.org/html/rfc5849#section-3.4.3 but Mastercard uses the stronger SHA-256 algorithm
Expand All @@ -260,18 +291,22 @@ static String getBodyHash(String payload, Charset charset, String hashAlg) {
* @param sbs Signature base string formatted as per https://tools.ietf.org/html/rfc5849#section-3.4.1
* @param signingKey Private key of the RSA key pair that was established with the service provider
* @param charset Charset encoding of the request
* @param signMethod The signature method to use when signing the request
* @return RSA signature matching the contents of signature base string
*/
static String signSignatureBaseString(String sbs, PrivateKey signingKey, Charset charset) {
static String signSignatureBaseString(String sbs, PrivateKey signingKey, Charset charset, SignatureMethod signMethod) {
try {
Signature signer = Signature.getInstance("SHA256withRSA");
Signature signer = Signature.getInstance(signMethod.getJcaName());
if(signMethod.getAlgorithmParams() != null) {
signer.setParameter(signMethod.getAlgorithmParams());
}
signer.initSign(signingKey);
byte[] sbsBytes = sbs.getBytes(charset);
signer.update(sbsBytes);
byte[] signatureBytes = signer.sign();
return Util.b64Encode(signatureBytes);
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Unable to RSA-SHA256 sign the given string with the provided key", e);
throw new IllegalStateException("Unable to sign with method " + signMethod.getOauthName() + " using the provided key", e);
}
}

Expand Down
Loading