1 /*
2 * Copyright (c) 2016. 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 com.amazonaws.services.s3.internal;
17
18 import com.amazonaws.services.s3.Headers;
19 import com.amazonaws.services.s3.model.GetObjectRequest;
20 import com.amazonaws.services.s3.model.ObjectMetadata;
21 import com.amazonaws.services.s3.model.PresignedUrlDownloadRequest;
22 import com.amazonaws.services.s3.model.PresignedUrlUploadRequest;
23 import com.amazonaws.services.s3.model.PutObjectRequest;
24 import com.amazonaws.services.s3.model.S3Object;
25 import com.amazonaws.services.s3.model.UploadPartRequest;
26
27 /**
28 * Logic for determining whether MD5 checksum validation should be performed or not.
29 */
30 public class SkipMd5CheckStrategy {
31
32 /**
33 * System property to disable MD5 validation for GetObject. Any value set for this property will
34 * disable validation.
35 */
36 public static final String DISABLE_GET_OBJECT_MD5_VALIDATION_PROPERTY = "com.amazonaws.services.s3.disableGetObjectMD5Validation";
37
38 /**
39 * System property to disable MD5 validation for both PutObject and UploadPart. Any value set
40 * for this property will disable validation.
41 */
42 public static final String DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY = "com.amazonaws.services.s3.disablePutObjectMD5Validation";
43
44 public static final SkipMd5CheckStrategy INSTANCE = new SkipMd5CheckStrategy();
45
46 // Singleton
47 private SkipMd5CheckStrategy() {
48 }
49
50 /**
51 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
52 * validate the integrity of the message client side based on the server response. We skip the
53 * client side check if any of the following conditions are true:
54 * <ol>
55 * <li>The system property {@value #DISABLE_GET_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
56 * <li>The request involves SSE-C or SSE-KMS</li>
57 * <li>The Etag header is missing</li>
58 * <li>The Etag indicates that the object was created by a MultiPart Upload</li>
59 * </ol>
60 *
61 * @return True if client side validation should be skipped, false otherwise.
62 */
63 public boolean skipClientSideValidationPerGetResponse(ObjectMetadata metadata) {
64 if (isGetObjectMd5ValidationDisabledByProperty()) {
65 return true;
66 }
67 return skipClientSideValidationPerResponse(metadata);
68 }
69
70 /**
71 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
72 * validate the integrity of the message client side based on the server response. We skip the
73 * client side check if any of the following conditions are true:
74 * <ol>
75 * <li>The system property {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
76 * <li>The request involves SSE-C or SSE-KMS</li>
77 * <li>The Etag header is missing</li>
78 * </ol>
79 *
80 * @return True if client side validation should be skipped, false otherwise.
81 */
82 public boolean skipClientSideValidationPerPutResponse(ObjectMetadata metadata) {
83 if (isPutObjectMd5ValidationDisabledByProperty()) {
84 return true;
85 }
86 return skipClientSideValidationPerResponse(metadata);
87 }
88
89 /**
90 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
91 * validate the integrity of the message client side based on the server response. We skip the
92 * client side check if any of the following conditions are true:
93 * <ol>
94 * <li>The system property {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
95 * <li>The request involves SSE-C or SSE-KMS</li>
96 * <li>The Etag header is missing</li>
97 * </ol>
98 *
99 * @return True if client side validation should be skipped, false otherwise.
100 */
101 public boolean skipClientSideValidationPerUploadPartResponse(ObjectMetadata metadata) {
102 return skipClientSideValidationPerPutResponse(metadata);
103 }
104
105 /**
106 * Conveience method to determine whether to do client side validation of a GetObject call based
107 * on both the request and the response. See
108 * {@link #skipClientSideValidationPerRequest(GetObjectRequest)} and
109 * {@link #skipClientSideValidationPerGetResponse(ObjectMetadata)} for more details on the
110 * criterion.
111 *
112 * @param request
113 * Original {@link GetObjectRequest}
114 * @param returnedMetadata
115 * Metadata returned in {@link S3Object}
116 * @return True if client side validation should be skipped, false otherwise.
117 */
118 public boolean skipClientSideValidation(GetObjectRequest request, ObjectMetadata returnedMetadata) {
119 return skipClientSideValidationPerRequest(request) || skipClientSideValidationPerGetResponse(returnedMetadata);
120 }
121
122 /**
123 * Conveience method to determine whether to do client side validation of a {@link PresignedUrlDownloadRequest} call based
124 * on both the request and the response. See
125 * {@link #skipClientSideValidationPerRequest(PresignedUrlDownloadRequest)} and
126 * {@link #skipClientSideValidationPerGetResponse(ObjectMetadata)} for more details on the
127 * criterion.
128 *
129 * @param request
130 * Original {@link PresignedUrlDownloadRequest}
131 * @param returnedMetadata
132 * Metadata returned in {@link S3Object}
133 * @return True if client side validation should be skipped, false otherwise.
134 */
135 public boolean skipClientSideValidation(PresignedUrlDownloadRequest request, ObjectMetadata returnedMetadata) {
136 return skipClientSideValidationPerRequest(request) || skipClientSideValidationPerGetResponse(returnedMetadata);
137 }
138
139 /**
140 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
141 * validate the integrity of the message client side. We skip the client side check if any of
142 * the following conditions are true:
143 * <ol>
144 * <li>The system property {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
145 * <li>The request involves SSE-C or SSE-KMS</li>
146 * </ol>
147 *
148 * @return True if client side validation should be skipped, false otherwise.
149 */
150 public boolean skipClientSideValidationPerRequest(PutObjectRequest request) {
151 if (isPutObjectMd5ValidationDisabledByProperty()) {
152 return true;
153 }
154 return putRequestInvolvesSse(request) || metadataInvolvesSse(request.getMetadata());
155 }
156
157 /**
158 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
159 * validate the integrity of the message client side. We skip the client side check if any of
160 * the following conditions are true:
161 * <ol>
162 * <li>The system property {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
163 * <li>The request involves SSE-C or SSE-KMS</li>
164 * </ol>
165 *
166 * @return True if client side validation should be skipped, false otherwise.
167 */
168 public boolean skipClientSideValidationPerRequest(UploadPartRequest request) {
169 if (isPutObjectMd5ValidationDisabledByProperty()) {
170 return true;
171 }
172 return request.getSSECustomerKey() != null;
173 }
174
175 /**
176 * Determines whether the client should calculate and send the {@link Headers#CONTENT_MD5}
177 * header to be validated by S3 per the request.
178 * <p>
179 * Currently we always try and do server side validation unless it's been explicitly disabled by
180 * the {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} property. Whether or not we actually
181 * calculate the MD5 header is determined in the client based on the source of the data (i.e. if
182 * it's a file we calculate, if not then we don't)
183 * </p>
184 */
185 public boolean skipServerSideValidation(PutObjectRequest request) {
186 if (isPutObjectMd5ValidationDisabledByProperty()) {
187 return true;
188 }
189 return false;
190 }
191
192 /**
193 * Determines whether the client should calculate and send the {@link Headers#CONTENT_MD5}
194 * header to be validated by S3 per the request.
195 * <p>
196 * Currently we always try and do server side validation unless it's been explicitly disabled by
197 * the {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} property. Whether or not we actually
198 * calculate the MD5 header is determined in the client based on the source of the data (i.e. if
199 * it's a file we calculate, if not then we don't)
200 * </p>
201 */
202 public boolean skipServerSideValidation(UploadPartRequest request) {
203 if (isPutObjectMd5ValidationDisabledByProperty()) {
204 return true;
205 }
206 return false;
207 }
208
209 /**
210 * Based on the given {@link GetObjectRequest}, returns whether the specified request should
211 * skip MD5 check on the requested object content. Specifically, MD5 check should be skipped if
212 * one of the following conditions are true:
213 * <ol>
214 * <li>The system property {@value #DISABLE_GET_OBJECT_MD5_VALIDATION_PROPERTY} is set.</li>
215 * <li>The request is a range-get operation</li>
216 * <li>The request is a GET object operation that involves SSE-C</li>
217 * </ol>
218 * Otherwise, MD5 check should not be skipped.
219 */
220 public boolean skipClientSideValidationPerRequest(GetObjectRequest request) {
221 if (isGetObjectMd5ValidationDisabledByProperty()) {
222 return true;
223 }
224 // Skip MD5 check for range get
225 if (request.getRange() != null) {
226 return true;
227 }
228 if (request.getSSECustomerKey() != null) {
229 return true;
230 }
231 return false;
232 }
233
234 /**
235 * Based on the given {@link PresignedUrlDownloadRequest}, returns whether the specified request should
236 * skip MD5 check on the requested object content. Specifically, MD5 check should be skipped if
237 * one of the following conditions are true:
238 * <ol>
239 * <li>The system property {@value #DISABLE_GET_OBJECT_MD5_VALIDATION_PROPERTY} is set.</li>
240 * <li>The request is a range-get operation</li>
241 * </ol>
242 * Otherwise, MD5 check should not be skipped.
243 */
244 public boolean skipClientSideValidationPerRequest(PresignedUrlDownloadRequest request) {
245 if (isGetObjectMd5ValidationDisabledByProperty()) {
246 return true;
247 }
248
249 if (request.getRange() != null) {
250 return true;
251 }
252
253 return false;
254 }
255
256 /**
257 * Determines whether the client should use the {@link Headers#ETAG} header returned by S3 to
258 * validate the integrity of the message client side. We skip the client side check if any of
259 * the following conditions are true:
260 * <ol>
261 * <li>The system property {@value #DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY} is set</li>
262 * <li>The request involves SSE-C or SSE-KMS</li>
263 * </ol>
264 *
265 * @return True if client side validation should be skipped, false otherwise.
266 */
267 public boolean skipClientSideValidationPerRequest(PresignedUrlUploadRequest request) {
268 if (isPutObjectMd5ValidationDisabledByProperty()) {
269 return true;
270 }
271 return metadataInvolvesSse(request.getMetadata());
272 }
273
274 private boolean skipClientSideValidationPerResponse(ObjectMetadata metadata) {
275 if (metadata == null) {
276 return true;
277 }
278 // If Etag is not provided or was computed from a multipart upload then skip the check, the
279 // etag won't be the MD5 of the original content
280 if (metadata.getETag() == null || isMultipartUploadETag(metadata.getETag())) {
281 return true;
282 }
283 return metadataInvolvesSse(metadata);
284 }
285
286 private boolean isGetObjectMd5ValidationDisabledByProperty() {
287 return System.getProperty(DISABLE_GET_OBJECT_MD5_VALIDATION_PROPERTY) != null;
288 }
289
290 private boolean isPutObjectMd5ValidationDisabledByProperty() {
291 return System.getProperty(DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY) != null;
292 }
293
294 /**
295 * If SSE-C or SSE-KMS is involved then the Etag will be the MD5 of the ciphertext not the
296 * plaintext so we can't validate it client side. Plain SSE with S3 managed keys will return an
297 * Etag that does match the MD5 of the plaintext so it's still eligible for client side
298 * validation.
299 *
300 * @param metadata
301 * Metadata of request or response
302 * @return True if the metadata indicates that SSE-C or SSE-KMS is used. False otherwise
303 */
304 private boolean metadataInvolvesSse(ObjectMetadata metadata) {
305 if (metadata == null) {
306 return false;
307 }
308 return containsNonNull(metadata.getSSECustomerAlgorithm(), metadata.getSSECustomerKeyMd5(),
309 metadata.getSSEAwsKmsKeyId());
310 }
311
312 /**
313 * @param request
314 * @return True if {@link PutObjectRequest} has been configured to use SSE-C or SSE-KMS
315 */
316 private boolean putRequestInvolvesSse(PutObjectRequest request) {
317 return containsNonNull(request.getSSECustomerKey(), request.getSSEAwsKeyManagementParams());
318 }
319
320 /**
321 * Returns true if the specified ETag was from a multipart upload.
322 *
323 * @param eTag
324 * The ETag to test.
325 * @return True if the specified ETag was from a multipart upload, otherwise false it if belongs
326 * to an object that was uploaded in a single part.
327 */
328 private static boolean isMultipartUploadETag(String eTag) {
329 return eTag.contains("-");
330 }
331
332 /**
333 * Helper method to avoid long chains of non null checks
334 *
335 * @param items
336 * @return True if any of the provided items is not null. False if all items are null.
337 */
338 private static boolean containsNonNull(Object... items) {
339 for (Object item : items) {
340 if (item != null) {
341 return true;
342 }
343 }
344 return false;
345 }
346 }
347