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