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