• 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.services.s3.internal.checksums;
17 
18 import static software.amazon.awssdk.services.s3.model.ServerSideEncryption.AWS_KMS;
19 
20 import java.util.Arrays;
21 import java.util.Optional;
22 import software.amazon.awssdk.annotations.SdkInternalApi;
23 import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
24 import software.amazon.awssdk.core.ClientType;
25 import software.amazon.awssdk.core.SdkRequest;
26 import software.amazon.awssdk.core.checksums.ChecksumSpecs;
27 import software.amazon.awssdk.core.checksums.SdkChecksum;
28 import software.amazon.awssdk.core.exception.RetryableException;
29 import software.amazon.awssdk.core.interceptor.ExecutionAttribute;
30 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
31 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
32 import software.amazon.awssdk.http.SdkHttpHeaders;
33 import software.amazon.awssdk.http.SdkHttpRequest;
34 import software.amazon.awssdk.http.SdkHttpResponse;
35 import software.amazon.awssdk.services.s3.S3Client;
36 import software.amazon.awssdk.services.s3.S3Configuration;
37 import software.amazon.awssdk.services.s3.internal.handlers.AsyncChecksumValidationInterceptor;
38 import software.amazon.awssdk.services.s3.internal.handlers.SyncChecksumValidationInterceptor;
39 import software.amazon.awssdk.services.s3.model.ChecksumMode;
40 import software.amazon.awssdk.services.s3.model.GetObjectRequest;
41 import software.amazon.awssdk.services.s3.model.PutObjectRequest;
42 import software.amazon.awssdk.services.s3.model.PutObjectResponse;
43 import software.amazon.awssdk.utils.BinaryUtils;
44 import software.amazon.awssdk.utils.StringUtils;
45 import software.amazon.awssdk.utils.internal.Base16Lower;
46 
47 /**
48  * Class used by {@link SyncChecksumValidationInterceptor} and
49  * {@link AsyncChecksumValidationInterceptor} to determine if trailing checksums
50  * should be enabled for a given request.
51  */
52 @SdkInternalApi
53 public final class ChecksumsEnabledValidator {
54 
55     public static final ExecutionAttribute<SdkChecksum> CHECKSUM = new ExecutionAttribute<>("checksum");
56 
ChecksumsEnabledValidator()57     private ChecksumsEnabledValidator() {
58     }
59 
60     /**
61      * Checks if trailing checksum is enabled and {@link ChecksumMode} is disabled for
62      * {@link S3Client#getObject(GetObjectRequest)} per request.
63      *
64      * @param request the request
65      * @param executionAttributes the executionAttributes
66      * @return true if trailing checksums is enabled and ChecksumMode is disabled, false otherwise
67      */
getObjectChecksumEnabledPerRequest(SdkRequest request, ExecutionAttributes executionAttributes)68     public static boolean getObjectChecksumEnabledPerRequest(SdkRequest request,
69                                                              ExecutionAttributes executionAttributes) {
70         return request instanceof GetObjectRequest
71                && ((GetObjectRequest) request).checksumMode() != ChecksumMode.ENABLED
72                && checksumEnabledPerConfig(executionAttributes);
73     }
74 
75     /**
76      * Checks if trailing checksum is enabled for {@link S3Client#getObject(GetObjectRequest)} per response.
77      *
78      * @param request the request
79      * @param responseHeaders the response headers
80      * @return true if trailing checksums is enabled, false otherwise
81      */
getObjectChecksumEnabledPerResponse(SdkRequest request, SdkHttpHeaders responseHeaders)82     public static boolean getObjectChecksumEnabledPerResponse(SdkRequest request, SdkHttpHeaders responseHeaders) {
83         return request instanceof GetObjectRequest && checksumEnabledPerResponse(responseHeaders);
84     }
85 
86     /**
87      * Validates that checksums should be enabled based on {@link ClientType} and the presence of S3 specific headers.
88      *
89      * @param expectedClientType - The expected client type for enabling checksums
90      * @param executionAttributes - {@link ExecutionAttributes} to determine the actual client type
91      * @return If trailing checksums should be enabled for this request.
92      */
shouldRecordChecksum(SdkRequest sdkRequest, ClientType expectedClientType, ExecutionAttributes executionAttributes, SdkHttpRequest httpRequest)93     public static boolean shouldRecordChecksum(SdkRequest sdkRequest,
94                                                ClientType expectedClientType,
95                                                ExecutionAttributes executionAttributes,
96                                                SdkHttpRequest httpRequest) {
97         if (!(sdkRequest instanceof PutObjectRequest)) {
98             return false;
99         }
100 
101         ClientType actualClientType = executionAttributes.getAttribute(SdkExecutionAttribute.CLIENT_TYPE);
102 
103         if (expectedClientType != actualClientType) {
104             return false;
105         }
106 
107 
108         if (hasServerSideEncryptionHeader(httpRequest)) {
109             return false;
110         }
111 
112         //Checksum validation is done at Service side when HTTP Checksum algorithm attribute is set.
113         if (isHttpCheckSumValidationEnabled(executionAttributes)) {
114             return false;
115         }
116 
117         return checksumEnabledPerConfig(executionAttributes);
118     }
119 
isHttpCheckSumValidationEnabled(ExecutionAttributes executionAttributes)120     private static boolean isHttpCheckSumValidationEnabled(ExecutionAttributes executionAttributes) {
121         Optional<ChecksumSpecs> resolvedChecksum =
122             executionAttributes.getOptionalAttribute(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS);
123         if (resolvedChecksum.isPresent()) {
124             ChecksumSpecs checksumSpecs = resolvedChecksum.get();
125             return checksumSpecs.algorithm() != null;
126         }
127         return false;
128     }
129 
responseChecksumIsValid(SdkHttpResponse httpResponse)130     public static boolean responseChecksumIsValid(SdkHttpResponse httpResponse) {
131         return !hasServerSideEncryptionHeader(httpResponse);
132     }
133 
hasServerSideEncryptionHeader(SdkHttpHeaders httpRequest)134     private static boolean hasServerSideEncryptionHeader(SdkHttpHeaders httpRequest) {
135         // S3 doesn't support trailing checksums for customer encryption
136         if (httpRequest.firstMatchingHeader(ChecksumConstant.SERVER_SIDE_CUSTOMER_ENCRYPTION_HEADER).isPresent()) {
137             return true;
138         }
139 
140         // S3 doesn't support trailing checksums for KMS encrypted objects
141         if (httpRequest.firstMatchingHeader(ChecksumConstant.SERVER_SIDE_ENCRYPTION_HEADER)
142                        .filter(h -> h.contains(AWS_KMS.toString()))
143                        .isPresent()) {
144             return true;
145         }
146         return false;
147     }
148 
149     /**
150      * Client side validation for {@link PutObjectRequest}
151      *
152      * @param response the response
153      * @param executionAttributes the execution attributes
154      */
validatePutObjectChecksum(PutObjectResponse response, ExecutionAttributes executionAttributes)155     public static void validatePutObjectChecksum(PutObjectResponse response, ExecutionAttributes executionAttributes) {
156         SdkChecksum checksum = executionAttributes.getAttribute(CHECKSUM);
157 
158         if (response.eTag() != null) {
159             String contentMd5 = BinaryUtils.toBase64(checksum.getChecksumBytes());
160             byte[] digest = BinaryUtils.fromBase64(contentMd5);
161             byte[] ssHash = Base16Lower.decode(StringUtils.replace(response.eTag(), "\"", ""));
162 
163             if (!Arrays.equals(digest, ssHash)) {
164                 throw RetryableException.create(
165                     String.format("Data read has a different checksum than expected. Was 0x%s, but expected 0x%s. " +
166                                   "This commonly means that the data was corrupted between the client and " +
167                                   "service. Note: Despite this error, the upload still completed and was persisted in S3.",
168                                   BinaryUtils.toHex(digest), BinaryUtils.toHex(ssHash)));
169             }
170         }
171     }
172 
173     /**
174      * Check the response header to see if the trailing checksum is enabled.
175      *
176      * @param responseHeaders the SdkHttpHeaders
177      * @return true if the trailing checksum is present in the header, false otherwise.
178      */
checksumEnabledPerResponse(SdkHttpHeaders responseHeaders)179     private static boolean checksumEnabledPerResponse(SdkHttpHeaders responseHeaders) {
180         return responseHeaders.firstMatchingHeader(ChecksumConstant.CHECKSUM_ENABLED_RESPONSE_HEADER)
181                               .filter(b -> b.equals(ChecksumConstant.ENABLE_MD5_CHECKSUM_HEADER_VALUE))
182                               .isPresent();
183     }
184 
185     /**
186      * Check the {@link S3Configuration#checksumValidationEnabled()} to see if the checksum is enabled.
187      *
188      * @param executionAttributes the execution attributes
189      * @return true if the trailing checksum is enabled in the config, false otherwise.
190      */
checksumEnabledPerConfig(ExecutionAttributes executionAttributes)191     private static boolean checksumEnabledPerConfig(ExecutionAttributes executionAttributes) {
192         S3Configuration serviceConfiguration =
193             (S3Configuration) executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_CONFIG);
194 
195         return serviceConfiguration == null || serviceConfiguration.checksumValidationEnabled();
196     }
197 }
198