• 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.core.internal.util;
17 
18 import static software.amazon.awssdk.core.HttpChecksumConstant.SIGNING_METHOD;
19 import static software.amazon.awssdk.core.HttpChecksumConstant.X_AMZ_TRAILER;
20 import static software.amazon.awssdk.core.internal.util.HttpChecksumResolver.getResolvedChecksumSpecs;
21 
22 import java.io.BufferedInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.nio.ByteBuffer;
26 import java.util.Objects;
27 import java.util.Optional;
28 import software.amazon.awssdk.annotations.SdkInternalApi;
29 import software.amazon.awssdk.core.ClientType;
30 import software.amazon.awssdk.core.HttpChecksumConstant;
31 import software.amazon.awssdk.core.checksums.Algorithm;
32 import software.amazon.awssdk.core.checksums.ChecksumSpecs;
33 import software.amazon.awssdk.core.checksums.SdkChecksum;
34 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
35 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
36 import software.amazon.awssdk.core.internal.signer.SigningMethod;
37 import software.amazon.awssdk.http.SdkHttpRequest;
38 import software.amazon.awssdk.http.SdkHttpResponse;
39 import software.amazon.awssdk.utils.Pair;
40 import software.amazon.awssdk.utils.StringUtils;
41 
42 @SdkInternalApi
43 public final class HttpChecksumUtils {
44 
45     private static final int CHECKSUM_BUFFER_SIZE = 16 * 1024;
46 
HttpChecksumUtils()47     private HttpChecksumUtils() {
48     }
49 
50     /**
51      * @param algorithmName Checksum Algorithm Name
52      * @return Http Checksum header for a given Algorithm.
53      */
httpChecksumHeader(String algorithmName)54     public static String httpChecksumHeader(String algorithmName) {
55         return String.format("%s-%s", HttpChecksumConstant.HTTP_CHECKSUM_HEADER_PREFIX,
56                              StringUtils.lowerCase(algorithmName));
57     }
58 
59     /**
60      * The header based Checksum is computed only if following criteria is met
61      * - Flexible checksum is not already computed.
62      * -
63      * - HeaderChecksumSpecs are defined.
64      * - Unsigned Payload request.
65      */
isStreamingUnsignedPayload(SdkHttpRequest sdkHttpRequest, ExecutionAttributes executionAttributes, ChecksumSpecs headerChecksumSpecs, boolean isContentStreaming)66     public static boolean isStreamingUnsignedPayload(SdkHttpRequest sdkHttpRequest,
67                                                      ExecutionAttributes executionAttributes,
68                                                      ChecksumSpecs headerChecksumSpecs,
69                                                      boolean isContentStreaming) {
70         SigningMethod signingMethodUsed = executionAttributes.getAttribute(SIGNING_METHOD);
71         String protocol = sdkHttpRequest.protocol();
72         if (isHeaderBasedSigningAuth(signingMethodUsed, protocol)) {
73             return false;
74         }
75         return isUnsignedPayload(signingMethodUsed, protocol, isContentStreaming) && headerChecksumSpecs.isRequestStreaming();
76     }
77 
isHeaderBasedSigningAuth(SigningMethod signingMethodUsed, String protocol)78     public static boolean isHeaderBasedSigningAuth(SigningMethod signingMethodUsed, String protocol) {
79         switch (signingMethodUsed) {
80             case HEADER_BASED_AUTH: {
81                 return true;
82             }
83             case PROTOCOL_BASED_UNSIGNED: {
84                 return "http".equals(protocol);
85             }
86             default: {
87                 return false;
88             }
89         }
90     }
91 
92     /**
93      * @param signingMethod Signing Method.
94      * @param protocol The http/https protocol.
95      * @return true if Payload signing is resolved to Unsigned payload.
96      */
isUnsignedPayload(SigningMethod signingMethod, String protocol, boolean isContentStreaming)97     public static boolean isUnsignedPayload(SigningMethod signingMethod, String protocol, boolean isContentStreaming) {
98         switch (signingMethod) {
99             case UNSIGNED_PAYLOAD:
100                 return true;
101             case PROTOCOL_STREAMING_SIGNING_AUTH:
102                 return "https".equals(protocol) || !isContentStreaming;
103             case PROTOCOL_BASED_UNSIGNED:
104                 return "https".equals(protocol);
105             default:
106                 return false;
107         }
108     }
109 
110     /**
111      * Computes the Checksum  of the data in the given input stream and returns it as an array of bytes.
112      *
113      * @param is          InputStream for which checksum needs to be calculated.
114      * @param algorithm algorithm that will be used to compute the checksum of input stream.
115      * @return Calculated checksum in bytes.
116      * @throws IOException I/O errors while reading.
117      */
computeChecksum(InputStream is, Algorithm algorithm)118     public static byte[] computeChecksum(InputStream is, Algorithm algorithm) throws IOException {
119         SdkChecksum sdkChecksum = SdkChecksum.forAlgorithm(algorithm);
120         try (BufferedInputStream bis = new BufferedInputStream(is)) {
121             byte[] buffer = new byte[CHECKSUM_BUFFER_SIZE];
122             int bytesRead;
123             while ((bytesRead = bis.read(buffer, 0, buffer.length)) != -1) {
124                 sdkChecksum.update(buffer, 0, bytesRead);
125             }
126             return sdkChecksum.getChecksumBytes();
127         }
128     }
129 
130 
131     /**
132      *
133      * @param executionAttributes Execution attributes defined for the request.
134      * @return Optional ChecksumSpec if checksum Algorithm exist for the checksumSpec
135      */
checksumSpecWithRequestAlgorithm(ExecutionAttributes executionAttributes)136     public static Optional<ChecksumSpecs> checksumSpecWithRequestAlgorithm(ExecutionAttributes executionAttributes) {
137         ChecksumSpecs resolvedChecksumSpecs = getResolvedChecksumSpecs(executionAttributes);
138         if (resolvedChecksumSpecs != null && resolvedChecksumSpecs.algorithm() != null) {
139             return Optional.of(resolvedChecksumSpecs);
140         }
141         return Optional.empty();
142     }
143 
144     /**
145      * Checks if the request header is already updated with Calculated checksum.
146      *
147      * @param sdkHttpRequest      SdkHttpRequest
148      * @return True if the flexible checksum header was already updated.
149      */
isHttpChecksumPresent(SdkHttpRequest sdkHttpRequest, ChecksumSpecs checksumSpec)150     public static boolean isHttpChecksumPresent(SdkHttpRequest sdkHttpRequest, ChecksumSpecs checksumSpec) {
151 
152         //check for the Direct header Or check if Trailer Header is present.
153         return sdkHttpRequest.firstMatchingHeader(checksumSpec.headerName()).isPresent() ||
154                isTrailerChecksumPresent(sdkHttpRequest, checksumSpec);
155     }
156 
isMd5ChecksumRequired(ExecutionAttributes executionAttributes)157     public static boolean isMd5ChecksumRequired(ExecutionAttributes executionAttributes) {
158         ChecksumSpecs resolvedChecksumSpecs = getResolvedChecksumSpecs(executionAttributes);
159         if (resolvedChecksumSpecs == null) {
160             return false;
161         }
162         return resolvedChecksumSpecs.algorithm() == null && resolvedChecksumSpecs.isRequestChecksumRequired();
163 
164     }
165 
isTrailerChecksumPresent(SdkHttpRequest sdkHttpRequest, ChecksumSpecs checksumSpec)166     private static boolean isTrailerChecksumPresent(SdkHttpRequest sdkHttpRequest, ChecksumSpecs checksumSpec) {
167         Optional<String> trailerBasedChecksum = sdkHttpRequest.firstMatchingHeader(X_AMZ_TRAILER);
168         if (trailerBasedChecksum.isPresent()) {
169             return trailerBasedChecksum.filter(checksum -> checksum.equalsIgnoreCase(checksumSpec.headerName())).isPresent();
170         }
171         return false;
172     }
173 
174     /**
175      * The trailer based Checksum is computed only if following criteria is met
176      * - Flexible checksum is not already computed.
177      * - Streaming  Unsigned Payload defined.
178      * - Unsigned Payload request.
179      */
isTrailerBasedFlexibleChecksumComputed(SdkHttpRequest sdkHttpRequest, ExecutionAttributes executionAttributes, ChecksumSpecs checksumSpecs, boolean hasRequestBody, boolean isContentStreaming)180     public static boolean isTrailerBasedFlexibleChecksumComputed(SdkHttpRequest sdkHttpRequest,
181                                                                  ExecutionAttributes executionAttributes,
182                                                                  ChecksumSpecs checksumSpecs,
183                                                                  boolean hasRequestBody,
184                                                                  boolean isContentStreaming) {
185         return hasRequestBody &&
186                !HttpChecksumUtils.isHttpChecksumPresent(sdkHttpRequest, checksumSpecs) &&
187                HttpChecksumUtils.isStreamingUnsignedPayload(sdkHttpRequest, executionAttributes,
188                                                             checksumSpecs, isContentStreaming);
189     }
190 
191     /**
192      *
193      * @param executionAttributes Execution attributes for the request.
194      * @param httpRequest Http Request.
195      * @param clientType Client Type for which the Trailer checksum is appended.
196      * @param checksumSpecs Checksum specs for the request.
197      * @param hasRequestBody Request body.
198      * @return True if Trailer checksum needs to be calculated and appended.
199      */
isTrailerBasedChecksumForClientType( ExecutionAttributes executionAttributes, SdkHttpRequest httpRequest, ClientType clientType, ChecksumSpecs checksumSpecs, boolean hasRequestBody, boolean isContentSteaming)200     public static boolean isTrailerBasedChecksumForClientType(
201         ExecutionAttributes executionAttributes, SdkHttpRequest httpRequest,
202         ClientType clientType, ChecksumSpecs checksumSpecs, boolean hasRequestBody, boolean isContentSteaming) {
203 
204         ClientType actualClientType = executionAttributes.getAttribute(SdkExecutionAttribute.CLIENT_TYPE);
205         return actualClientType.equals(clientType) &&
206                checksumSpecs != null &&
207                HttpChecksumUtils.isTrailerBasedFlexibleChecksumComputed(
208                    httpRequest, executionAttributes, checksumSpecs, hasRequestBody, isContentSteaming);
209     }
210 
211     /**
212      * Loops through the Supported list of checksum for the operation, and gets the Header value for the checksum header.
213      * @param sdkHttpResponse response from service.
214      * @param resolvedChecksumSpecs Resolved checksum specification for the operation.
215      * @return Algorithm and its corresponding checksum value as sent by the service.
216      */
getAlgorithmChecksumValuePair(SdkHttpResponse sdkHttpResponse, ChecksumSpecs resolvedChecksumSpecs)217     public static Pair<Algorithm, String> getAlgorithmChecksumValuePair(SdkHttpResponse sdkHttpResponse,
218                                                                         ChecksumSpecs resolvedChecksumSpecs) {
219         return resolvedChecksumSpecs.responseValidationAlgorithms().stream().map(
220             algorithm -> {
221                 Optional<String> firstMatchingHeader = sdkHttpResponse.firstMatchingHeader(httpChecksumHeader(algorithm.name()));
222                 return firstMatchingHeader.map(s -> Pair.of(algorithm, s)).orElse(null);
223             }).filter(Objects::nonNull).findFirst().orElse(null);
224     }
225 
226     /**
227      *
228      * @param resolvedChecksumSpecs Resolved checksum specification for the operation.
229      * @return True is Response is to be validated for checksum checks.
230      */
isHttpChecksumValidationEnabled(ChecksumSpecs resolvedChecksumSpecs)231     public static boolean isHttpChecksumValidationEnabled(ChecksumSpecs resolvedChecksumSpecs) {
232         return resolvedChecksumSpecs != null &&
233                resolvedChecksumSpecs.isValidationEnabled() &&
234                resolvedChecksumSpecs.responseValidationAlgorithms() != null;
235     }
236 
237 
longToByte(Long input)238     public static byte[] longToByte(Long input) {
239         ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
240         buffer.putLong(input);
241         return buffer.array();
242     }
243 
244 }
245