• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stripe / stripe-java / #16493

03 Oct 2024 07:15PM UTC coverage: 12.942% (+0.08%) from 12.864%
#16493

push

github

web-flow
Merge Stripe-java v27.0.0 to beta branch (#1888)

409 of 1651 new or added lines in 88 files covered. (24.77%)

31 existing lines in 7 files now uncovered.

18773 of 145050 relevant lines covered (12.94%)

0.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.89
/src/main/java/com/stripe/net/RequestSigningAuthenticator.java
1
package com.stripe.net;
2

3
import com.stripe.exception.AuthenticationException;
4
import java.nio.charset.StandardCharsets;
5
import java.security.GeneralSecurityException;
6
import java.security.MessageDigest;
7
import java.security.NoSuchAlgorithmException;
8
import java.util.Base64;
9
import java.util.List;
10

11
public abstract class RequestSigningAuthenticator implements Authenticator {
12
  @FunctionalInterface
13
  interface CurrentTimeInSecondsGetter {
14
    Long getCurrentTimeInSeconds();
15
  }
16

17
  private CurrentTimeInSecondsGetter currentTimeInSecondsGetter =
1✔
18
      new CurrentTimeInSecondsGetter() {
1✔
19
        @Override
20
        public Long getCurrentTimeInSeconds() {
21
          return java.time.Clock.systemUTC().millis() / 1000;
1✔
22
        }
23
      };
24
  private static final String authorizationHeaderName = "Authorization";
25
  private static final String stripeContextHeaderName = "Stripe-Context";
26
  private static final String stripeAccountHeaderName = "Stripe-Account";
27
  private static final String contentDigestHeaderName = "Content-Digest";
28
  private static final String signatureInputHeaderName = "Signature-Input";
29
  private static final String signatureHeaderName = "Signature";
30
  private static final String[] coveredHeaders =
1✔
31
      new String[] {
32
        "Content-Type",
33
        contentDigestHeaderName,
34
        stripeContextHeaderName,
35
        stripeAccountHeaderName,
36
        authorizationHeaderName
37
      };
38

39
  private static final String[] coveredHeadersGet =
1✔
40
      new String[] {stripeContextHeaderName, stripeAccountHeaderName, authorizationHeaderName};
41

42
  private static final String coveredHeaderFormatted;
43
  private static final String coveredHeaderGetFormatted;
44

45
  static {
46
    coveredHeaderFormatted = formatCoveredHeaders(coveredHeaders);
1✔
47
    coveredHeaderGetFormatted = formatCoveredHeaders(coveredHeadersGet);
1✔
48
  }
1✔
49

50
  private final String keyId;
51

52
  public RequestSigningAuthenticator(String keyId) {
1✔
53
    this.keyId = keyId;
1✔
54
  }
1✔
55

56
  @Override
57
  public final StripeRequest authenticate(StripeRequest request) throws AuthenticationException {
58
    if (request.content() != null) {
1✔
59
      request =
1✔
60
          request.withAdditionalHeader(
1✔
61
              contentDigestHeaderName, calculateDigestHeader(request.content()));
1✔
62
    }
63

64
    final Long created = this.currentTimeInSecondsGetter.getCurrentTimeInSeconds();
1✔
65
    request =
1✔
66
        request
67
            .withAdditionalHeader(authorizationHeaderName, String.format("STRIPE-V2-SIG %s", keyId))
1✔
68
            .withAdditionalHeader(
1✔
69
                signatureInputHeaderName,
70
                String.format("sig1=%s", calculateSignatureInput(request.method(), created)));
1✔
71

72
    final byte[] signatureBase = calculateSignatureBase(request, created);
1✔
73
    String signature;
74

75
    try {
76
      signature = Base64.getEncoder().encodeToString(sign(signatureBase));
1✔
77
    } catch (GeneralSecurityException e) {
1✔
78
      throw new AuthenticationException("Error calculating request signature.", null, null, 0, e);
1✔
79
    }
1✔
80
    request =
1✔
81
        request.withAdditionalHeader(signatureHeaderName, String.format("sig1=:%s:", signature));
1✔
82

83
    return request;
1✔
84
  }
85

86
  public abstract byte[] sign(byte[] signatureBase) throws GeneralSecurityException;
87

88
  RequestSigningAuthenticator withCurrentTimeInSecondsGetter(CurrentTimeInSecondsGetter getter) {
89
    this.currentTimeInSecondsGetter = getter;
1✔
90
    return this;
1✔
91
  }
92

93
  private String calculateDigestHeader(HttpContent content) {
94
    MessageDigest messageDigest;
95
    try {
96
      messageDigest = MessageDigest.getInstance("SHA-256");
1✔
NEW
97
    } catch (NoSuchAlgorithmException e) {
×
NEW
98
      throw new IllegalStateException(
×
99
          "Error calculating request digest: your Java installation does not provide the SHA-256 digest algorithm, which is necessary for sending secure requests to Stripe.",
100
          e);
101
    }
1✔
102

103
    String digest =
104
        Base64.getEncoder().encodeToString(messageDigest.digest(content.byteArrayContent()));
1✔
105
    return String.format("sha-256=:%s:", digest);
1✔
106
  }
107

108
  private byte[] calculateSignatureBase(StripeRequest request, Long created) {
109
    StringBuilder stringBuilder = new StringBuilder();
1✔
110
    String[] headers =
111
        request.method() == ApiResource.RequestMethod.GET ? coveredHeadersGet : coveredHeaders;
1✔
112
    for (String header : headers) {
1✔
113
      List<String> values = request.headers().allValues(header);
1✔
114

115
      stringBuilder.append('"').append(header.toLowerCase()).append("\": ");
1✔
116
      boolean firstValue = true;
1✔
117
      for (String value : values) {
1✔
118
        if (firstValue) {
1✔
119
          firstValue = false;
1✔
120
        } else {
NEW
121
          stringBuilder.append(",");
×
122
        }
123
        stringBuilder.append(value);
1✔
124
      }
1✔
125

126
      stringBuilder.append('\n');
1✔
127
    }
128

129
    stringBuilder.append("\"@signature-params\": ");
1✔
130
    appendSignatureInput(stringBuilder, request.method(), created);
1✔
131

132
    return stringBuilder.toString().getBytes(StandardCharsets.UTF_8);
1✔
133
  }
134

135
  private String calculateSignatureInput(ApiResource.RequestMethod method, Long created) {
136
    StringBuilder stringBuilder = new StringBuilder();
1✔
137
    appendSignatureInput(stringBuilder, method, created);
1✔
138
    return stringBuilder.toString();
1✔
139
  }
140

141
  private void appendSignatureInput(
142
      StringBuilder stringBuilder, ApiResource.RequestMethod method, Long created) {
143
    stringBuilder
1✔
144
        .append(
1✔
145
            method == ApiResource.RequestMethod.GET
1✔
146
                ? coveredHeaderGetFormatted
1✔
147
                : coveredHeaderFormatted)
1✔
148
        .append(";created=")
1✔
149
        .append(created);
1✔
150
  }
1✔
151

152
  private static String formatCoveredHeaders(String[] headers) {
153
    StringBuilder stringBuilder = new StringBuilder();
1✔
154
    stringBuilder.append('(');
1✔
155
    boolean first = true;
1✔
156
    for (String header : headers) {
1✔
157
      if (first) {
1✔
158
        first = false;
1✔
159
      } else {
160
        stringBuilder.append(' ');
1✔
161
      }
162
      stringBuilder.append('"').append(header.toLowerCase()).append('"');
1✔
163
    }
164
    stringBuilder.append(')');
1✔
165
    return stringBuilder.toString();
1✔
166
  }
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc