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