1
15
16 package software.amazon.awssdk.auth.signer.internal;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.InputStream;
20 import java.nio.charset.StandardCharsets;
21 import java.security.DigestInputStream;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.SortedMap;
29 import java.util.TreeMap;
30 import javax.crypto.Mac;
31 import javax.crypto.spec.SecretKeySpec;
32 import software.amazon.awssdk.annotations.SdkInternalApi;
33 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
34 import software.amazon.awssdk.auth.credentials.AwsCredentials;
35 import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
36 import software.amazon.awssdk.core.exception.SdkClientException;
37 import software.amazon.awssdk.core.io.SdkDigestInputStream;
38 import software.amazon.awssdk.core.signer.Signer;
39 import software.amazon.awssdk.http.ContentStreamProvider;
40 import software.amazon.awssdk.http.SdkHttpFullRequest;
41 import software.amazon.awssdk.utils.BinaryUtils;
42 import software.amazon.awssdk.utils.StringUtils;
43 import software.amazon.awssdk.utils.http.SdkHttpUtils;
44
45
52 @SdkInternalApi
53 public abstract class AbstractAwsSigner implements Signer {
54
55 private static final ThreadLocal<MessageDigest> SHA256_MESSAGE_DIGEST;
56
57 static {
58 SHA256_MESSAGE_DIGEST = ThreadLocal.withInitial(() -> {
59 try {
60 return MessageDigest.getInstance("SHA-256");
61 } catch (NoSuchAlgorithmException e) {
62 throw SdkClientException.builder()
63 .message("Unable to get SHA256 Function" + e.getMessage())
64 .cause(e)
65 .build();
66 }
67 });
68 }
69
70 private static byte[] doHash(String text) throws SdkClientException {
71 try {
72 MessageDigest md = getMessageDigestInstance();
73 md.update(text.getBytes(StandardCharsets.UTF_8));
74 return md.digest();
75 } catch (Exception e) {
76 throw SdkClientException.builder()
77 .message("Unable to compute hash while signing request: " + e.getMessage())
78 .cause(e)
79 .build();
80 }
81 }
82
83
86 private static MessageDigest getMessageDigestInstance() {
87 MessageDigest messageDigest = SHA256_MESSAGE_DIGEST.get();
88 messageDigest.reset();
89 return messageDigest;
90 }
91
92
96 protected String signAndBase64Encode(String data, String key,
97 SigningAlgorithm algorithm) throws SdkClientException {
98 return signAndBase64Encode(data.getBytes(StandardCharsets.UTF_8), key, algorithm);
99 }
100
101
105 private String signAndBase64Encode(byte[] data, String key,
106 SigningAlgorithm algorithm) throws SdkClientException {
107 try {
108 byte[] signature = sign(data, key.getBytes(StandardCharsets.UTF_8), algorithm);
109 return BinaryUtils.toBase64(signature);
110 } catch (Exception e) {
111 throw SdkClientException.builder()
112 .message("Unable to calculate a request signature: " + e.getMessage())
113 .cause(e)
114 .build();
115 }
116 }
117
118 protected byte[] signWithMac(String stringData, Mac mac) {
119 try {
120 return mac.doFinal(stringData.getBytes(StandardCharsets.UTF_8));
121 } catch (Exception e) {
122 throw SdkClientException.builder()
123 .message("Unable to calculate a request signature: " + e.getMessage())
124 .cause(e)
125 .build();
126 }
127 }
128
129 protected byte[] sign(String stringData, byte[] key,
130 SigningAlgorithm algorithm) throws SdkClientException {
131 try {
132 byte[] data = stringData.getBytes(StandardCharsets.UTF_8);
133 return sign(data, key, algorithm);
134 } catch (Exception e) {
135 throw SdkClientException.builder()
136 .message("Unable to calculate a request signature: " + e.getMessage())
137 .cause(e)
138 .build();
139 }
140 }
141
142 protected byte[] sign(byte[] data, byte[] key, SigningAlgorithm algorithm) throws SdkClientException {
143 try {
144 Mac mac = algorithm.getMac();
145 mac.init(new SecretKeySpec(key, algorithm.toString()));
146 return mac.doFinal(data);
147 } catch (Exception e) {
148 throw SdkClientException.builder()
149 .message("Unable to calculate a request signature: " + e.getMessage())
150 .cause(e)
151 .build();
152 }
153 }
154
155
163 static byte[] hash(String text) throws SdkClientException {
164 return AbstractAwsSigner.doHash(text);
165 }
166
167 byte[] hash(InputStream input) throws SdkClientException {
168 try {
169 MessageDigest md = getMessageDigestInstance();
170 @SuppressWarnings("resource")
171 DigestInputStream digestInputStream = new SdkDigestInputStream(
172 input, md);
173 byte[] buffer = new byte[1024];
174 while (digestInputStream.read(buffer) > -1) {
175 ;
176 }
177 return digestInputStream.getMessageDigest().digest();
178 } catch (Exception e) {
179 throw SdkClientException.builder()
180 .message("Unable to compute hash while signing request: " + e.getMessage())
181 .cause(e)
182 .build();
183 }
184 }
185
186
193 byte[] hash(byte[] data) throws SdkClientException {
194 try {
195 MessageDigest md = getMessageDigestInstance();
196 md.update(data);
197 return md.digest();
198 } catch (Exception e) {
199 throw SdkClientException.builder()
200 .message("Unable to compute hash while signing request: " + e.getMessage())
201 .cause(e)
202 .build();
203 }
204 }
205
206
217 protected String getCanonicalizedQueryString(Map<String, List<String>> parameters) {
218
219 SortedMap<String, List<String>> sorted = new TreeMap<>();
220
221
225 for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
226 String encodedParamName = SdkHttpUtils.urlEncode(entry.getKey());
227 List<String> paramValues = entry.getValue();
228 List<String> encodedValues = new ArrayList<>(paramValues.size());
229 for (String value : paramValues) {
230 String encodedValue = SdkHttpUtils.urlEncode(value);
231
232
233
234 String signatureFormattedEncodedValue = encodedValue == null ? "" : encodedValue;
235
236 encodedValues.add(signatureFormattedEncodedValue);
237 }
238 Collections.sort(encodedValues);
239 sorted.put(encodedParamName, encodedValues);
240
241 }
242
243 return SdkHttpUtils.flattenQueryParameters(sorted).orElse("");
244 }
245
246 protected InputStream getBinaryRequestPayloadStream(ContentStreamProvider streamProvider) {
247 try {
248 if (streamProvider == null) {
249 return new ByteArrayInputStream(new byte[0]);
250 }
251 return streamProvider.newStream();
252 } catch (SdkClientException e) {
253 throw e;
254 } catch (Exception e) {
255 throw SdkClientException.builder()
256 .message("Unable to read request payload to sign request: " + e.getMessage())
257 .cause(e)
258 .build();
259 }
260 }
261
262 String getCanonicalizedResourcePath(String resourcePath, boolean urlEncode) {
263 if (StringUtils.isEmpty(resourcePath)) {
264 return "/";
265 } else {
266 String value = urlEncode ? SdkHttpUtils.urlEncodeIgnoreSlashes(resourcePath) : resourcePath;
267 if (value.startsWith("/")) {
268 return value;
269 } else {
270 return "/".concat(value);
271 }
272 }
273 }
274
275 protected String getCanonicalizedEndpoint(SdkHttpFullRequest request) {
276 String endpointForStringToSign = StringUtils.lowerCase(request.host());
277
278
279
280 if (!SdkHttpUtils.isUsingStandardPort(request.protocol(), request.port())) {
281 endpointForStringToSign += ":" + request.port();
282 }
283
284 return endpointForStringToSign;
285 }
286
287
295 protected AwsCredentials sanitizeCredentials(AwsCredentials credentials) {
296 String accessKeyId = StringUtils.trim(credentials.accessKeyId());
297 String secretKey = StringUtils.trim(credentials.secretAccessKey());
298
299 if (credentials instanceof AwsSessionCredentials) {
300 AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials;
301 return AwsSessionCredentials.create(accessKeyId,
302 secretKey,
303 StringUtils.trim(sessionCredentials.sessionToken()));
304 }
305
306 return AwsBasicCredentials.create(accessKeyId, secretKey);
307 }
308
309
315 protected abstract void addSessionCredentials(SdkHttpFullRequest.Builder mutableRequest,
316 AwsSessionCredentials credentials);
317
318 }
319