1
15
16 package software.amazon.awssdk.auth.signer;
17
18 import static software.amazon.awssdk.auth.signer.internal.SignerConstant.X_AMZ_CONTENT_SHA256;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.Optional;
23 import software.amazon.awssdk.annotations.SdkPublicApi;
24 import software.amazon.awssdk.auth.credentials.CredentialUtils;
25 import software.amazon.awssdk.auth.signer.internal.AbstractAws4Signer;
26 import software.amazon.awssdk.auth.signer.internal.Aws4SignerRequestParams;
27 import software.amazon.awssdk.auth.signer.internal.AwsChunkedEncodingInputStream;
28 import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
29 import software.amazon.awssdk.auth.signer.params.AwsS3V4SignerParams;
30 import software.amazon.awssdk.core.exception.SdkClientException;
31 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
32 import software.amazon.awssdk.http.ContentStreamProvider;
33 import software.amazon.awssdk.http.SdkHttpFullRequest;
34 import software.amazon.awssdk.utils.BinaryUtils;
35
36
39 @SdkPublicApi
40 public final class AwsS3V4Signer extends AbstractAws4Signer<AwsS3V4SignerParams, Aws4PresignerParams> {
41
42 private static final String CONTENT_SHA_256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
43
44
47 private static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
48 private static final String CONTENT_LENGTH = "Content-Length";
49
50 private AwsS3V4Signer() {
51 }
52
53 public static AwsS3V4Signer create() {
54 return new AwsS3V4Signer();
55 }
56
57 @Override
58 public SdkHttpFullRequest sign(SdkHttpFullRequest request, ExecutionAttributes executionAttributes) {
59 AwsS3V4SignerParams signingParams = constructAwsS3SignerParams(executionAttributes);
60
61 return sign(request, signingParams);
62 }
63
64
72 public SdkHttpFullRequest sign(SdkHttpFullRequest request, AwsS3V4SignerParams signingParams) {
73
74 if (CredentialUtils.isAnonymous(signingParams.awsCredentials())) {
75 return request;
76 }
77
78 Aws4SignerRequestParams requestParams = new Aws4SignerRequestParams(signingParams);
79
80 return doSign(request, requestParams, signingParams).build();
81 }
82
83 private AwsS3V4SignerParams constructAwsS3SignerParams(ExecutionAttributes executionAttributes) {
84 AwsS3V4SignerParams.Builder signerParams = extractSignerParams(AwsS3V4SignerParams.builder(),
85 executionAttributes);
86
87 Optional.ofNullable(executionAttributes.getAttribute(S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING))
88 .ifPresent(signerParams::enableChunkedEncoding);
89
90 Optional.ofNullable(executionAttributes.getAttribute(S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING))
91 .ifPresent(signerParams::enablePayloadSigning);
92
93 return signerParams.build();
94 }
95
96 @Override
97 public SdkHttpFullRequest presign(SdkHttpFullRequest request, ExecutionAttributes executionAttributes) {
98 Aws4PresignerParams signingParams =
99 extractPresignerParams(Aws4PresignerParams.builder(), executionAttributes).build();
100
101 return presign(request, signingParams);
102 }
103
104
112 public SdkHttpFullRequest presign(SdkHttpFullRequest request, Aws4PresignerParams signingParams) {
113
114 if (CredentialUtils.isAnonymous(signingParams.awsCredentials())) {
115 return request;
116 }
117
118 Aws4SignerRequestParams requestParams = new Aws4SignerRequestParams(signingParams);
119
120 return doPresign(request, requestParams, signingParams).build();
121 }
122
123
126 @Override
127 protected void processRequestPayload(SdkHttpFullRequest.Builder mutableRequest,
128 byte[] signature,
129 byte[] signingKey,
130 Aws4SignerRequestParams signerRequestParams,
131 AwsS3V4SignerParams signerParams) {
132
133 if (useChunkEncoding(mutableRequest, signerParams)) {
134 if (mutableRequest.contentStreamProvider() != null) {
135 ContentStreamProvider streamProvider = mutableRequest.contentStreamProvider();
136 mutableRequest.contentStreamProvider(() -> AwsS3V4Signer.this.asChunkEncodedStream(
137 streamProvider.newStream(),
138 signature,
139 signingKey,
140 signerRequestParams
141 ));
142 }
143 }
144 }
145
146 @Override
147 protected String calculateContentHashPresign(SdkHttpFullRequest.Builder mutableRequest, Aws4PresignerParams signerParams) {
148 return UNSIGNED_PAYLOAD;
149 }
150
151 private AwsChunkedEncodingInputStream asChunkEncodedStream(InputStream inputStream,
152 byte[] signature,
153 byte[] signingKey,
154 Aws4SignerRequestParams signerRequestParams) {
155 return new AwsChunkedEncodingInputStream(
156 inputStream,
157 signingKey,
158 signerRequestParams.getFormattedRequestSigningDateTime(),
159 signerRequestParams.getScope(),
160 BinaryUtils.toHex(signature), this);
161 }
162
163
168 @Override
169 protected String calculateContentHash(SdkHttpFullRequest.Builder mutableRequest, AwsS3V4SignerParams signerParams) {
170
171
172
173 mutableRequest.putHeader(X_AMZ_CONTENT_SHA256, "required");
174
175 if (isPayloadSigningEnabled(mutableRequest, signerParams)) {
176 if (useChunkEncoding(mutableRequest, signerParams)) {
177 String contentLength = mutableRequest.firstMatchingHeader(CONTENT_LENGTH)
178 .orElse(null);
179 long originalContentLength;
180 if (contentLength != null) {
181 originalContentLength = Long.parseLong(contentLength);
182 } else {
183
192 try {
193 originalContentLength = getContentLength(mutableRequest);
194 } catch (IOException e) {
195 throw SdkClientException.builder()
196 .message("Cannot get the content-length of the request content.")
197 .cause(e)
198 .build();
199 }
200 }
201 mutableRequest.putHeader("x-amz-decoded-content-length", Long.toString(originalContentLength));
202
203
204 mutableRequest.putHeader(CONTENT_LENGTH, Long.toString(
205 AwsChunkedEncodingInputStream.calculateStreamContentLength(originalContentLength)));
206 return CONTENT_SHA_256;
207 } else {
208 return super.calculateContentHash(mutableRequest, signerParams);
209 }
210 }
211
212 return UNSIGNED_PAYLOAD;
213 }
214
215
218 private boolean useChunkEncoding(SdkHttpFullRequest.Builder mutableRequest, AwsS3V4SignerParams signerParams) {
219
220 return isPayloadSigningEnabled(mutableRequest, signerParams) && isChunkedEncodingEnabled(signerParams);
221 }
222
223
226 private boolean isChunkedEncodingEnabled(AwsS3V4SignerParams signerParams) {
227 Boolean isChunkedEncodingEnabled = signerParams.enableChunkedEncoding();
228 return isChunkedEncodingEnabled != null && isChunkedEncodingEnabled;
229 }
230
231
234 private boolean isPayloadSigningEnabled(SdkHttpFullRequest.Builder request, AwsS3V4SignerParams signerParams) {
235
238 if (!request.protocol().equals("https") && request.contentStreamProvider() != null) {
239 return true;
240 }
241
242 Boolean isPayloadSigningEnabled = signerParams.enablePayloadSigning();
243 return isPayloadSigningEnabled != null && isPayloadSigningEnabled;
244 }
245
246
249 private static long getContentLength(SdkHttpFullRequest.Builder requestBuilder) throws IOException {
250 InputStream content = requestBuilder.contentStreamProvider().newStream();
251
252 long contentLength = 0;
253 byte[] tmp = new byte[4096];
254 int read;
255 while ((read = content.read(tmp)) != -1) {
256 contentLength += read;
257 }
258 return contentLength;
259 }
260 }
261