• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.authcrt.signer.internal;
17 
18 import java.nio.charset.StandardCharsets;
19 import java.time.Clock;
20 import java.time.Duration;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.TreeSet;
24 import software.amazon.awssdk.annotations.SdkInternalApi;
25 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
26 import software.amazon.awssdk.auth.credentials.AwsCredentials;
27 import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
28 import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
29 import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
30 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
31 import software.amazon.awssdk.crt.auth.credentials.Credentials;
32 import software.amazon.awssdk.http.SdkHttpFullRequest;
33 import software.amazon.awssdk.utils.StringUtils;
34 import software.amazon.awssdk.utils.http.SdkHttpUtils;
35 
36 @SdkInternalApi
37 public class SigningUtils {
38 
39     /**
40      * Attribute allowing the user to inject a clock that will be used for the signing timestamp
41      */
42     public static final ExecutionAttribute<Clock> SIGNING_CLOCK = new ExecutionAttribute<>("SigningClock");
43 
44     private static final String BODY_HASH_NAME = "x-amz-content-sha256";
45     private static final String DATE_NAME = "X-Amz-Date";
46     private static final String AUTHORIZATION_NAME = "Authorization";
47     private static final String REGION_SET_NAME = "X-amz-region-set";
48 
49     private static final String SIGNATURE_NAME = "X-Amz-Signature";
50     private static final String CREDENTIAL_NAME = "X-Amz-Credential";
51     private static final String ALGORITHM_NAME = "X-Amz-Algorithm";
52     private static final String SIGNED_HEADERS_NAME = "X-Amz-SignedHeaders";
53     private static final String EXPIRES_NAME = "X-Amz-Expires";
54 
55     private static final Set<String> FORBIDDEN_HEADERS = buildForbiddenHeaderSet();
56     private static final Set<String> FORBIDDEN_PARAMS = buildForbiddenQueryParamSet();
57 
58     private static final String HOST_HEADER = "Host";
59 
SigningUtils()60     private SigningUtils() {
61     }
62 
buildCredentials(ExecutionAttributes executionAttributes)63     public static Credentials buildCredentials(ExecutionAttributes executionAttributes) {
64         AwsCredentials sdkCredentials = SigningUtils.sanitizeCredentials(
65             executionAttributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS));
66         byte[] sessionToken = null;
67         if (sdkCredentials instanceof AwsSessionCredentials) {
68             AwsSessionCredentials sessionCreds = (AwsSessionCredentials) sdkCredentials;
69             sessionToken = sessionCreds.sessionToken().getBytes(StandardCharsets.UTF_8);
70         }
71 
72         return new Credentials(sdkCredentials.accessKeyId().getBytes(StandardCharsets.UTF_8),
73                                sdkCredentials.secretAccessKey().getBytes(StandardCharsets.UTF_8), sessionToken);
74     }
75 
getSigningClock(ExecutionAttributes executionAttributes)76     public static Clock getSigningClock(ExecutionAttributes executionAttributes) {
77         Clock clock = executionAttributes.getAttribute(SIGNING_CLOCK);
78         if (clock != null) {
79             return clock;
80         }
81 
82         Clock baseClock = Clock.systemUTC();
83         Optional<Integer> timeOffset = Optional.ofNullable(executionAttributes.getAttribute(
84             AwsSignerExecutionAttribute.TIME_OFFSET));
85         return timeOffset
86             .map(offset -> Clock.offset(baseClock, Duration.ofSeconds(-offset)))
87             .orElse(baseClock);
88     }
89 
sanitizeCredentials(AwsCredentials credentials)90     public static AwsCredentials sanitizeCredentials(AwsCredentials credentials) {
91         String accessKeyId = StringUtils.trim(credentials.accessKeyId());
92         String secretKey = StringUtils.trim(credentials.secretAccessKey());
93 
94         if (credentials instanceof AwsSessionCredentials) {
95             AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials;
96             return AwsSessionCredentials.create(accessKeyId,
97                     secretKey,
98                     StringUtils.trim(sessionCredentials.sessionToken()));
99         }
100 
101         return AwsBasicCredentials.create(accessKeyId, secretKey);
102     }
103 
sanitizeSdkRequestForCrtSigning(SdkHttpFullRequest request)104     public static SdkHttpFullRequest sanitizeSdkRequestForCrtSigning(SdkHttpFullRequest request) {
105 
106         SdkHttpFullRequest.Builder builder = request.toBuilder();
107 
108         // Ensure path is non-empty
109         String path = builder.encodedPath();
110         if (path == null || path.length() == 0) {
111             builder.encodedPath("/");
112         }
113 
114         builder.clearHeaders();
115 
116         // Filter headers that will cause signing to fail
117         request.forEachHeader((name, value) -> {
118             if (!FORBIDDEN_HEADERS.contains(name)) {
119                 builder.putHeader(name, value);
120             }
121         });
122 
123         // Add host, which must be signed. We ignore any pre-existing Host header to match the behavior of the SigV4 signer.
124         String hostHeader = SdkHttpUtils.isUsingStandardPort(request.protocol(), request.port())
125                             ? request.host()
126                             : request.host() + ":" + request.port();
127         builder.putHeader(HOST_HEADER, hostHeader);
128 
129         builder.clearQueryParameters();
130 
131         // Filter query parameters that will cause signing to fail
132         request.forEachRawQueryParameter((key, value) -> {
133             if (!FORBIDDEN_PARAMS.contains(key)) {
134                 builder.putRawQueryParameter(key, value);
135             }
136         });
137 
138         return builder.build();
139     }
140 
buildForbiddenHeaderSet()141     private static Set<String> buildForbiddenHeaderSet() {
142         Set<String> forbiddenHeaders = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
143 
144         forbiddenHeaders.add(BODY_HASH_NAME);
145         forbiddenHeaders.add(DATE_NAME);
146         forbiddenHeaders.add(AUTHORIZATION_NAME);
147         forbiddenHeaders.add(REGION_SET_NAME);
148 
149         return forbiddenHeaders;
150     }
151 
buildForbiddenQueryParamSet()152     private static Set<String> buildForbiddenQueryParamSet() {
153         Set<String> forbiddenParams = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
154 
155         forbiddenParams.add(SIGNATURE_NAME);
156         forbiddenParams.add(DATE_NAME);
157         forbiddenParams.add(CREDENTIAL_NAME);
158         forbiddenParams.add(ALGORITHM_NAME);
159         forbiddenParams.add(SIGNED_HEADERS_NAME);
160         forbiddenParams.add(REGION_SET_NAME);
161         forbiddenParams.add(EXPIRES_NAME);
162 
163         return forbiddenParams;
164     }
165 }
166