diff --git a/src/main/java/com/stripe/net/ApiRequestParams.java b/src/main/java/com/stripe/net/ApiRequestParams.java index 679c5f847de..b6ac6cb749f 100644 --- a/src/main/java/com/stripe/net/ApiRequestParams.java +++ b/src/main/java/com/stripe/net/ApiRequestParams.java @@ -11,7 +11,7 @@ public abstract class ApiRequestParams { * Param key for an `extraParams` map. Any param/sub-param specifying a field intended to support * extra params from users should have the annotation * {@code @SerializedName(ApiRequestParams.EXTRA_PARAMS_KEY)}. Logic to handle this is in {@link - * ApiRequestParamsConverter}.t + * ApiRequestParamsConverter}. */ public static final String EXTRA_PARAMS_KEY = "_stripe_java_extra_param_key"; diff --git a/src/main/java/com/stripe/net/BaseAddress.java b/src/main/java/com/stripe/net/BaseAddress.java index b241f5f0ab3..70ac6f6c083 100644 --- a/src/main/java/com/stripe/net/BaseAddress.java +++ b/src/main/java/com/stripe/net/BaseAddress.java @@ -8,6 +8,6 @@ public enum BaseAddress { CONNECT, /** https://files.stripe.com */ FILES, - /** https://events.stripe.com */ + /** https://meter-events.stripe.com */ METER_EVENTS } diff --git a/src/main/java/com/stripe/net/RequestSigningAuthenticator.java b/src/main/java/com/stripe/net/RequestSigningAuthenticator.java deleted file mode 100644 index 5b038176347..00000000000 --- a/src/main/java/com/stripe/net/RequestSigningAuthenticator.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.stripe.net; - -import com.stripe.exception.AuthenticationException; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.List; - -public abstract class RequestSigningAuthenticator implements Authenticator { - @FunctionalInterface - interface CurrentTimeInSecondsGetter { - Long getCurrentTimeInSeconds(); - } - - private CurrentTimeInSecondsGetter currentTimeInSecondsGetter = - new CurrentTimeInSecondsGetter() { - @Override - public Long getCurrentTimeInSeconds() { - return java.time.Clock.systemUTC().millis() / 1000; - } - }; - private static final String authorizationHeaderName = "Authorization"; - private static final String stripeContextHeaderName = "Stripe-Context"; - private static final String stripeAccountHeaderName = "Stripe-Account"; - private static final String contentDigestHeaderName = "Content-Digest"; - private static final String signatureInputHeaderName = "Signature-Input"; - private static final String signatureHeaderName = "Signature"; - private static final String[] coveredHeaders = - new String[] { - "Content-Type", - contentDigestHeaderName, - stripeContextHeaderName, - stripeAccountHeaderName, - authorizationHeaderName - }; - - private static final String[] coveredHeadersGet = - new String[] {stripeContextHeaderName, stripeAccountHeaderName, authorizationHeaderName}; - - private static final String coveredHeaderFormatted; - private static final String coveredHeaderGetFormatted; - - static { - coveredHeaderFormatted = formatCoveredHeaders(coveredHeaders); - coveredHeaderGetFormatted = formatCoveredHeaders(coveredHeadersGet); - } - - private final String keyId; - - public RequestSigningAuthenticator(String keyId) { - this.keyId = keyId; - } - - @Override - public final StripeRequest authenticate(StripeRequest request) throws AuthenticationException { - if (request.content() != null) { - request = - request.withAdditionalHeader( - contentDigestHeaderName, calculateDigestHeader(request.content())); - } - - final Long created = this.currentTimeInSecondsGetter.getCurrentTimeInSeconds(); - request = - request - .withAdditionalHeader(authorizationHeaderName, String.format("STRIPE-V2-SIG %s", keyId)) - .withAdditionalHeader( - signatureInputHeaderName, - String.format("sig1=%s", calculateSignatureInput(request.method(), created))); - - final byte[] signatureBase = calculateSignatureBase(request, created); - String signature; - - try { - signature = Base64.getEncoder().encodeToString(sign(signatureBase)); - } catch (GeneralSecurityException e) { - throw new AuthenticationException("Error calculating request signature.", null, null, 0, e); - } - request = - request.withAdditionalHeader(signatureHeaderName, String.format("sig1=:%s:", signature)); - - return request; - } - - public abstract byte[] sign(byte[] signatureBase) throws GeneralSecurityException; - - RequestSigningAuthenticator withCurrentTimeInSecondsGetter(CurrentTimeInSecondsGetter getter) { - this.currentTimeInSecondsGetter = getter; - return this; - } - - private String calculateDigestHeader(HttpContent content) { - MessageDigest messageDigest; - try { - messageDigest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException( - "Error calculating request digest: your Java installation does not provide the SHA-256 digest algorithm, which is necessary for sending secure requests to Stripe.", - e); - } - - String digest = - Base64.getEncoder().encodeToString(messageDigest.digest(content.byteArrayContent())); - return String.format("sha-256=:%s:", digest); - } - - private byte[] calculateSignatureBase(StripeRequest request, Long created) { - StringBuilder stringBuilder = new StringBuilder(); - String[] headers = - request.method() == ApiResource.RequestMethod.GET ? coveredHeadersGet : coveredHeaders; - for (String header : headers) { - List values = request.headers().allValues(header); - - stringBuilder.append('"').append(header.toLowerCase()).append("\": "); - boolean firstValue = true; - for (String value : values) { - if (firstValue) { - firstValue = false; - } else { - stringBuilder.append(","); - } - stringBuilder.append(value); - } - - stringBuilder.append('\n'); - } - - stringBuilder.append("\"@signature-params\": "); - appendSignatureInput(stringBuilder, request.method(), created); - - return stringBuilder.toString().getBytes(StandardCharsets.UTF_8); - } - - private String calculateSignatureInput(ApiResource.RequestMethod method, Long created) { - StringBuilder stringBuilder = new StringBuilder(); - appendSignatureInput(stringBuilder, method, created); - return stringBuilder.toString(); - } - - private void appendSignatureInput( - StringBuilder stringBuilder, ApiResource.RequestMethod method, Long created) { - stringBuilder - .append( - method == ApiResource.RequestMethod.GET - ? coveredHeaderGetFormatted - : coveredHeaderFormatted) - .append(";created=") - .append(created); - } - - private static String formatCoveredHeaders(String[] headers) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append('('); - boolean first = true; - for (String header : headers) { - if (first) { - first = false; - } else { - stringBuilder.append(' '); - } - stringBuilder.append('"').append(header.toLowerCase()).append('"'); - } - stringBuilder.append(')'); - return stringBuilder.toString(); - } -} diff --git a/src/test/java/com/stripe/net/RequestSigningAuthenticatorTest.java b/src/test/java/com/stripe/net/RequestSigningAuthenticatorTest.java deleted file mode 100644 index 992cdb8b354..00000000000 --- a/src/test/java/com/stripe/net/RequestSigningAuthenticatorTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.stripe.net; - -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.google.common.collect.ImmutableMap; -import com.stripe.BaseStripeTest; -import com.stripe.exception.StripeException; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import org.junit.jupiter.api.Test; - -public class RequestSigningAuthenticatorTest extends BaseStripeTest { - Function testCurrentTimeGetter = - (l) -> { - return new RequestSigningAuthenticator.CurrentTimeInSecondsGetter() { - @Override - public Long getCurrentTimeInSeconds() { - return l; - } - }; - }; - - @Test - public void appliesSignatureToRequest() throws StripeException { - List signatureBases = new ArrayList<>(); - - StripeRequest request = - StripeRequest.create( - ApiResource.RequestMethod.POST, - "http://example.com/get", - ImmutableMap.of("string", "String!"), - RequestOptions.builder() - .setAuthenticator( - new RequestSigningAuthenticator("keyid") { - @Override - public byte[] sign(byte[] signatureBase) throws GeneralSecurityException { - signatureBases.add(signatureBase); - return new byte[] {1, 2, 3, 4, 5}; - } - }.withCurrentTimeInSecondsGetter(testCurrentTimeGetter.apply(123456789L))) - .build(), - ApiMode.V2); - - assertEquals( - "\"content-type\": application/json\n" - + "\"content-digest\": sha-256=:HA3i38j+04ac71IzPtG1JK8o4q9sPK0fYPmJHmci5bg=:\n" - + "\"stripe-context\": \n" - + "\"stripe-account\": \n" - + "\"authorization\": STRIPE-V2-SIG keyid\n" - + "\"@signature-params\": (\"content-type\" \"content-digest\" \"stripe-context\" \"stripe-account\" \"authorization\");created=123456789", - new String(signatureBases.get(0), StandardCharsets.UTF_8)); - assertEquals( - "sig1=(\"content-type\" \"content-digest\" \"stripe-context\" \"stripe-account\" \"authorization\");" - + "created=123456789", - request.headers().firstValue("Signature-Input").get()); - assertEquals("sig1=:AQIDBAU=:", request.headers().firstValue("Signature").get()); - assertEquals( - "sha-256=:HA3i38j+04ac71IzPtG1JK8o4q9sPK0fYPmJHmci5bg=:", - request.headers().firstValue("Content-Digest").get()); - assertEquals("STRIPE-V2-SIG keyid", request.headers().firstValue("Authorization").get()); - assertEquals("application/json", request.headers().firstValue("Content-Type").get()); - } - - @Test - public void appliesSignatureToGetRequest() throws StripeException { - List signatureBases = new ArrayList<>(); - - StripeRequest request = - StripeRequest.create( - ApiResource.RequestMethod.GET, - "http://example.com/get", - null, - RequestOptions.builder() - .setAuthenticator( - new RequestSigningAuthenticator("keyid") { - @Override - public byte[] sign(byte[] signatureBase) throws GeneralSecurityException { - signatureBases.add(signatureBase); - return new byte[] {1, 2, 3, 4, 5}; - } - }.withCurrentTimeInSecondsGetter(testCurrentTimeGetter.apply(123456789L))) - .build(), - ApiMode.V2); - - assertEquals( - "\"stripe-context\": \n" - + "\"stripe-account\": \n" - + "\"authorization\": STRIPE-V2-SIG keyid\n" - + "\"@signature-params\": (\"stripe-context\" \"stripe-account\" \"authorization\");created=123456789", - new String(signatureBases.get(0), StandardCharsets.UTF_8)); - assertEquals( - "sig1=(\"stripe-context\" \"stripe-account\" \"authorization\");" + "created=123456789", - request.headers().firstValue("Signature-Input").get()); - assertEquals("sig1=:AQIDBAU=:", request.headers().firstValue("Signature").get()); - assertEquals(false, request.headers().firstValue("Content-Digest").isPresent()); - assertEquals("STRIPE-V2-SIG keyid", request.headers().firstValue("Authorization").get()); - } - - @Test - public void wrapsSecurityException() { - StripeException exception = - assertThrows( - StripeException.class, - () -> - StripeRequest.create( - ApiResource.RequestMethod.POST, - "http://example.com/get", - ImmutableMap.of("string", "String!"), - RequestOptions.builder() - .setAuthenticator( - new RequestSigningAuthenticator("keyid") { - @Override - public byte[] sign(byte[] signatureBase) - throws GeneralSecurityException { - throw new GeneralSecurityException("something bad happened"); - } - }) - .build(), - ApiMode.V2)); - - assertEquals("Error calculating request signature.", exception.getMessage()); - assertEquals("something bad happened", exception.getCause().getMessage()); - } -}