1
15 package com.amazonaws.auth;
16
17 import com.amazonaws.ReadLimitInfo;
18 import com.amazonaws.SdkClientException;
19 import com.amazonaws.SignableRequest;
20 import com.amazonaws.annotation.SdkTestInternalApi;
21 import com.amazonaws.auth.internal.AWS4SignerRequestParams;
22 import com.amazonaws.auth.internal.AWS4SignerUtils;
23 import com.amazonaws.auth.internal.SignerKey;
24 import com.amazonaws.internal.FIFOCache;
25 import com.amazonaws.log.InternalLogApi;
26 import com.amazonaws.log.InternalLogFactory;
27 import com.amazonaws.util.BinaryUtils;
28 import com.amazonaws.util.DateUtils;
29 import com.amazonaws.util.SdkHttpUtils;
30 import com.amazonaws.util.StringUtils;
31
32 import com.amazonaws.util.endpoint.RegionFromEndpointResolver;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.net.URI;
36 import java.nio.charset.Charset;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Date;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.concurrent.TimeUnit;
44
45 import static com.amazonaws.auth.internal.SignerConstants.AUTHORIZATION;
46 import static com.amazonaws.auth.internal.SignerConstants.AWS4_SIGNING_ALGORITHM;
47 import static com.amazonaws.auth.internal.SignerConstants.AWS4_TERMINATOR;
48 import static com.amazonaws.auth.internal.SignerConstants.HOST;
49 import static com.amazonaws.auth.internal.SignerConstants.LINE_SEPARATOR;
50 import static com.amazonaws.auth.internal.SignerConstants.PRESIGN_URL_MAX_EXPIRATION_SECONDS;
51 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_ALGORITHM;
52 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_CONTENT_SHA256;
53 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_CREDENTIAL;
54 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_DATE;
55 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_EXPIRES;
56 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SECURITY_TOKEN;
57 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SIGNATURE;
58 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SIGNED_HEADER;
59
60
63 public class AWS4Signer extends AbstractAWSSigner implements
64 ServiceAwareSigner, RegionAwareSigner, Presigner, EndpointPrefixAwareSigner, RegionFromEndpointResolverAwareSigner {
65
66 protected static final InternalLogApi log = InternalLogFactory.getLog(AWS4Signer.class);
67 private static final int SIGNER_CACHE_MAX_SIZE = 300;
68 private static final FIFOCache<SignerKey> signerCache = new FIFOCache<SignerKey>(SIGNER_CACHE_MAX_SIZE);
69 private static final List<String> listOfHeadersToIgnoreInLowerCase = Arrays.asList("connection", "x-amzn-trace-id");
70
71 private final SdkClock clock;
72
73
77 protected String serviceName;
78
79
83 private String endpointPrefix;
84
85 private RegionFromEndpointResolver regionFromEndpointResolver;
86
87
91 protected String regionName;
92
93
94 protected Date overriddenDate;
95
96
104 protected boolean doubleUrlEncode;
105
106
110 public AWS4Signer() {
111 this(true);
112 }
113
114
121 public AWS4Signer(boolean doubleUrlEncoding) {
122 this(doubleUrlEncoding, SdkClock.Instance.get());
123 }
124
125 @SdkTestInternalApi
126 public AWS4Signer(SdkClock clock) {
127 this(true, clock);
128 }
129
130 private AWS4Signer(boolean doubleUrlEncode, SdkClock clock) {
131 this.doubleUrlEncode = doubleUrlEncode;
132 this.clock = clock;
133 }
134
135
145 @Override
146 public void setServiceName(String serviceName) {
147 this.serviceName = serviceName;
148 }
149
150
160 @Override
161 public void setRegionName(String regionName) {
162 this.regionName = regionName;
163 }
164
165
174 @Override
175 public void setEndpointPrefix(String endpointPrefix) {
176 this.endpointPrefix = endpointPrefix;
177 }
178
179
183 @SdkTestInternalApi
184 public void setOverrideDate(Date overriddenDate) {
185 if (overriddenDate != null) {
186 this.overriddenDate = new Date(overriddenDate.getTime());
187 } else {
188 this.overriddenDate = null;
189 }
190 }
191
192 @Override
193 public void setRegionFromEndpointResolver(RegionFromEndpointResolver resolver) {
194 this.regionFromEndpointResolver = resolver;
195 }
196
197
200 public String getRegionName() {
201 return regionName;
202 }
203
204
207 public String getServiceName() {
208 return serviceName;
209 }
210
211
215 public Date getOverriddenDate() {
216 return overriddenDate == null ? null : new Date(
217 overriddenDate.getTime());
218 }
219
220 @Override
221 public void sign(SignableRequest<?> request, AWSCredentials credentials) {
222
223 if (isAnonymous(credentials)) {
224 return;
225 }
226
227 AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials);
228 if (sanitizedCredentials instanceof AWSSessionCredentials) {
229 addSessionCredentials(request,
230 (AWSSessionCredentials) sanitizedCredentials);
231 }
232
233 final AWS4SignerRequestParams signerParams = new AWS4SignerRequestParams(
234 request, overriddenDate, regionName, serviceName,
235 AWS4_SIGNING_ALGORITHM, endpointPrefix, regionFromEndpointResolver);
236
237 addHostHeader(request);
238 request.addHeader(X_AMZ_DATE,
239 signerParams.getFormattedSigningDateTime());
240
241 String contentSha256 = calculateContentHash(request);
242
243 if ("required".equals(request.getHeaders().get(X_AMZ_CONTENT_SHA256))) {
244 request.addHeader(X_AMZ_CONTENT_SHA256, contentSha256);
245 }
246
247 final String canonicalRequest = createCanonicalRequest(request,
248 contentSha256);
249
250 final String stringToSign = createStringToSign(canonicalRequest,
251 signerParams);
252
253 final byte[] signingKey = deriveSigningKey(sanitizedCredentials,
254 signerParams);
255
256 final byte[] signature = computeSignature(stringToSign, signingKey,
257 signerParams);
258
259 request.addHeader(
260 AUTHORIZATION,
261 buildAuthorizationHeader(request, signature,
262 sanitizedCredentials, signerParams));
263
264 processRequestPayload(request, signature, signingKey,
265 signerParams);
266 }
267
268 @Override
269 public void presignRequest(SignableRequest<?> request, AWSCredentials credentials,
270 Date userSpecifiedExpirationDate) {
271
272
273 if (isAnonymous(credentials)) {
274 return;
275 }
276
277 long expirationInSeconds = generateExpirationDate(userSpecifiedExpirationDate);
278
279 addHostHeader(request);
280
281 AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials);
282 if (sanitizedCredentials instanceof AWSSessionCredentials) {
283
284
285
286 request.addParameter(X_AMZ_SECURITY_TOKEN,
287 ((AWSSessionCredentials) sanitizedCredentials)
288 .getSessionToken());
289 }
290
291 final AWS4SignerRequestParams signerRequestParams = new AWS4SignerRequestParams(
292 request, overriddenDate, regionName, serviceName,
293 AWS4_SIGNING_ALGORITHM, endpointPrefix, regionFromEndpointResolver);
294
295
296 final String timeStamp = signerRequestParams.getFormattedSigningDateTime();
297
298 addPreSignInformationToRequest(request, sanitizedCredentials,
299 signerRequestParams, timeStamp, expirationInSeconds);
300
301 final String contentSha256 = calculateContentHashPresign(request);
302
303 final String canonicalRequest = createCanonicalRequest(request,
304 contentSha256);
305
306 final String stringToSign = createStringToSign(canonicalRequest,
307 signerRequestParams);
308
309 final byte[] signingKey = deriveSigningKey(sanitizedCredentials,
310 signerRequestParams);
311
312 final byte[] signature = computeSignature(stringToSign, signingKey,
313 signerRequestParams);
314
315 request.addParameter(X_AMZ_SIGNATURE, BinaryUtils.toHex(signature));
316 }
317
318
324 protected String createCanonicalRequest(SignableRequest<?> request,
325 String contentSha256) {
326
327 final String path = SdkHttpUtils.appendUri(
328 request.getEndpoint().getPath(), request.getResourcePath());
329
330 final StringBuilder canonicalRequestBuilder = new StringBuilder(request
331 .getHttpMethod().toString());
332
333 canonicalRequestBuilder.append(LINE_SEPARATOR)
334
335 .append(getCanonicalizedResourcePath(path, doubleUrlEncode))
336 .append(LINE_SEPARATOR)
337 .append(getCanonicalizedQueryString(request))
338 .append(LINE_SEPARATOR)
339 .append(getCanonicalizedHeaderString(request))
340 .append(LINE_SEPARATOR)
341 .append(getSignedHeadersString(request)).append(LINE_SEPARATOR)
342 .append(contentSha256);
343
344 final String canonicalRequest = canonicalRequestBuilder.toString();
345
346 if (log.isDebugEnabled())
347 log.debug("AWS4 Canonical Request: '\"" + canonicalRequest + "\"");
348
349 return canonicalRequest;
350 }
351
352
357 protected String createStringToSign(String canonicalRequest,
358 AWS4SignerRequestParams signerParams) {
359
360 final StringBuilder stringToSignBuilder = new StringBuilder(
361 signerParams.getSigningAlgorithm());
362 stringToSignBuilder.append(LINE_SEPARATOR)
363 .append(signerParams.getFormattedSigningDateTime())
364 .append(LINE_SEPARATOR)
365 .append(signerParams.getScope())
366 .append(LINE_SEPARATOR)
367 .append(BinaryUtils.toHex(hash(canonicalRequest)));
368
369 final String stringToSign = stringToSignBuilder.toString();
370
371 if (log.isDebugEnabled())
372 log.debug("AWS4 String to Sign: '\"" + stringToSign + "\"");
373
374 return stringToSign;
375 }
376
377
383 private final byte[] deriveSigningKey(AWSCredentials credentials,
384 AWS4SignerRequestParams signerRequestParams) {
385
386 final String cacheKey = computeSigningCacheKeyName(credentials,
387 signerRequestParams);
388 final long daysSinceEpochSigningDate = DateUtils
389 .numberOfDaysSinceEpoch(signerRequestParams
390 .getSigningDateTimeMilli());
391
392 SignerKey signerKey = signerCache.get(cacheKey);
393
394 if (signerKey != null) {
395 if (daysSinceEpochSigningDate == signerKey
396 .getNumberOfDaysSinceEpoch()) {
397 return signerKey.getSigningKey();
398 }
399 }
400 if (log.isDebugEnabled()) {
401 log.debug("Generating a new signing key as the signing key not available in the cache for the date "
402 + TimeUnit.DAYS.toMillis(daysSinceEpochSigningDate));
403 }
404 byte[] signingKey = newSigningKey(credentials,
405 signerRequestParams.getFormattedSigningDate(),
406 signerRequestParams.getRegionName(),
407 signerRequestParams.getServiceName());
408 signerCache.add(cacheKey, new SignerKey(
409 daysSinceEpochSigningDate, signingKey));
410 return signingKey;
411 }
412
413
416 private final String computeSigningCacheKeyName(AWSCredentials credentials,
417 AWS4SignerRequestParams signerRequestParams) {
418 final StringBuilder hashKeyBuilder = new StringBuilder(
419 credentials.getAWSSecretKey());
420
421 return hashKeyBuilder.append("-")
422 .append(signerRequestParams.getRegionName())
423 .append("-")
424 .append(signerRequestParams.getServiceName()).toString();
425 }
426
427
433 protected final byte[] computeSignature(String stringToSign,
434 byte[] signingKey, AWS4SignerRequestParams signerRequestParams) {
435 return sign(stringToSign.getBytes(Charset.forName("UTF-8")), signingKey,
436 SigningAlgorithm.HmacSHA256);
437 }
438
439
442 private String buildAuthorizationHeader(SignableRequest<?> request,
443 byte[] signature, AWSCredentials credentials,
444 AWS4SignerRequestParams signerParams) {
445 final String signingCredentials = credentials.getAWSAccessKeyId() + "/"
446 + signerParams.getScope();
447
448 final String credential = "Credential="
449 + signingCredentials;
450 final String signerHeaders = "SignedHeaders="
451 + getSignedHeadersString(request);
452 final String signatureHeader = "Signature="
453 + BinaryUtils.toHex(signature);
454
455 final StringBuilder authHeaderBuilder = new StringBuilder();
456
457 authHeaderBuilder.append(AWS4_SIGNING_ALGORITHM)
458 .append(" ")
459 .append(credential)
460 .append(", ")
461 .append(signerHeaders)
462 .append(", ")
463 .append(signatureHeader);
464
465 return authHeaderBuilder.toString();
466 }
467
468
471 private void addPreSignInformationToRequest(SignableRequest<?> request,
472 AWSCredentials credentials, AWS4SignerRequestParams signerParams,
473 String timeStamp, long expirationInSeconds) {
474
475 String signingCredentials = credentials.getAWSAccessKeyId() + "/"
476 + signerParams.getScope();
477
478 request.addParameter(X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM);
479 request.addParameter(X_AMZ_DATE, timeStamp);
480 request.addParameter(X_AMZ_SIGNED_HEADER,
481 getSignedHeadersString(request));
482 request.addParameter(X_AMZ_EXPIRES,
483 Long.toString(expirationInSeconds));
484 request.addParameter(X_AMZ_CREDENTIAL, signingCredentials);
485 }
486
487 @Override
488 protected void addSessionCredentials(SignableRequest<?> request,
489 AWSSessionCredentials credentials) {
490 request.addHeader(X_AMZ_SECURITY_TOKEN, credentials.getSessionToken());
491 }
492
493 protected String getCanonicalizedHeaderString(SignableRequest<?> request) {
494 final List<String> sortedHeaders = new ArrayList<String>(request.getHeaders()
495 .keySet());
496 Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
497
498 final Map<String, String> requestHeaders = request.getHeaders();
499 StringBuilder buffer = new StringBuilder();
500 for (String header : sortedHeaders) {
501 if (shouldExcludeHeaderFromSigning(header)) {
502 continue;
503 }
504 String key = StringUtils.lowerCase(header);
505 String value = requestHeaders.get(header);
506
507 StringUtils.appendCompactedString(buffer, key);
508 buffer.append(":");
509 if (value != null) {
510 StringUtils.appendCompactedString(buffer, value);
511 }
512
513 buffer.append("\n");
514 }
515
516 return buffer.toString();
517 }
518
519 protected String getSignedHeadersString(SignableRequest<?> request) {
520 final List<String> sortedHeaders = new ArrayList<String>(request
521 .getHeaders().keySet());
522 Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
523
524 StringBuilder buffer = new StringBuilder();
525 for (String header : sortedHeaders) {
526 if (shouldExcludeHeaderFromSigning(header)) {
527 continue;
528 }
529 if (buffer.length() > 0)
530 buffer.append(";");
531 buffer.append(StringUtils.lowerCase(header));
532 }
533
534 return buffer.toString();
535 }
536
537 protected boolean shouldExcludeHeaderFromSigning(String header) {
538 return listOfHeadersToIgnoreInLowerCase.contains(header.toLowerCase());
539 }
540
541 protected void addHostHeader(SignableRequest<?> request) {
542
543
544
545 final URI endpoint = request.getEndpoint();
546
547 if (endpoint.getHost() == null) {
548 throw new IllegalArgumentException("Request endpoint must have a valid hostname, but it did not: " + endpoint);
549 }
550
551 final StringBuilder hostHeaderBuilder = new StringBuilder(endpoint.getHost());
552 if (SdkHttpUtils.isUsingNonDefaultPort(endpoint)) {
553 hostHeaderBuilder.append(":").append(endpoint.getPort());
554 }
555
556 request.addHeader(HOST, hostHeaderBuilder.toString());
557 }
558
559
566 protected String calculateContentHash(SignableRequest<?> request) {
567 InputStream payloadStream = getBinaryRequestPayloadStream(request);
568 ReadLimitInfo info = request.getReadLimitInfo();
569 payloadStream.mark(info == null ? -1 : info.getReadLimit());
570 String contentSha256 = BinaryUtils.toHex(hash(payloadStream));
571 try {
572 payloadStream.reset();
573 } catch (IOException e) {
574 throw new SdkClientException(
575 "Unable to reset stream after calculating AWS4 signature",
576 e);
577 }
578 return contentSha256;
579 }
580
581
587 protected void processRequestPayload(SignableRequest<?> request, byte[] signature,
588 byte[] signingKey, AWS4SignerRequestParams signerRequestParams) {
589 return;
590 }
591
592
600 protected String calculateContentHashPresign(SignableRequest<?> request) {
601 return calculateContentHash(request);
602 }
603
604
608 private boolean isAnonymous(AWSCredentials credentials) {
609 return credentials instanceof AnonymousAWSCredentials;
610 }
611
612
616 private long generateExpirationDate(Date expirationDate) {
617
618 long expirationInSeconds = expirationDate != null ? ((expirationDate
619 .getTime() - clock.currentTimeMillis()) / 1000L)
620 : PRESIGN_URL_MAX_EXPIRATION_SECONDS;
621
622 if (expirationInSeconds > PRESIGN_URL_MAX_EXPIRATION_SECONDS) {
623 throw new SdkClientException(
624 "Requests that are pre-signed by SigV4 algorithm are valid for at most 7 days. "
625 + "The expiration date set on the current request ["
626 + AWS4SignerUtils.formatTimestamp(expirationDate
627 .getTime()) + "] has exceeded this limit.");
628 }
629 return expirationInSeconds;
630 }
631
632
635 protected byte[] newSigningKey(AWSCredentials credentials,
636 String dateStamp, String regionName, String serviceName) {
637 byte[] kSecret = ("AWS4" + credentials.getAWSSecretKey())
638 .getBytes(Charset.forName("UTF-8"));
639 byte[] kDate = sign(dateStamp, kSecret, SigningAlgorithm.HmacSHA256);
640 byte[] kRegion = sign(regionName, kDate, SigningAlgorithm.HmacSHA256);
641 byte[] kService = sign(serviceName, kRegion,
642 SigningAlgorithm.HmacSHA256);
643 return sign(AWS4_TERMINATOR, kService, SigningAlgorithm.HmacSHA256);
644 }
645 }
646