com.amazonaws.services.s3.internal.AWSS3V4Signer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-java-sdk-s3 Show documentation
Show all versions of aws-java-sdk-s3 Show documentation
The AWS Java SDK for Amazon S3 module holds the client classes that are used for communicating with Amazon Simple Storage Service
/*
* Copyright 2013-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.services.s3.internal;
import com.amazonaws.SdkClientException;
import com.amazonaws.ReadLimitInfo;
import com.amazonaws.Request;
import com.amazonaws.ResetException;
import com.amazonaws.SignableRequest;
import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.AwsChunkedEncodingInputStream;
import com.amazonaws.auth.internal.AWS4SignerRequestParams;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.UploadPartRequest;
import com.amazonaws.services.s3.request.S3HandlerContextKeys;
import com.amazonaws.util.BinaryUtils;
import java.io.IOException;
import java.io.InputStream;
import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_CONTENT_SHA256;
/**
* AWS4 signer implementation for Amazon Web Services S3
*/
public class AWSS3V4Signer extends AWS4Signer {
private static final String CONTENT_SHA_256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
/** Sent to S3 in lieu of a payload hash when unsigned payloads are enabled */
private static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
/**
* Don't double-url-encode path elements; S3 expects path elements to be encoded only once in
* the canonical URI.
*/
public AWSS3V4Signer() {
super(false);
}
/**
* If necessary, creates a chunk-encoding wrapper on the request payload.
*/
@Override
protected void processRequestPayload(SignableRequest> request, byte[] signature,
byte[] signingKey, AWS4SignerRequestParams signerRequestParams) {
if (useChunkEncoding(request)) {
AwsChunkedEncodingInputStream chunkEncodededStream = new AwsChunkedEncodingInputStream(
request.getContent(), signingKey,
signerRequestParams.getFormattedSigningDateTime(),
signerRequestParams.getScope(),
BinaryUtils.toHex(signature), this);
request.setContent(chunkEncodededStream);
}
}
@Override
protected String calculateContentHashPresign(SignableRequest> request){
return "UNSIGNED-PAYLOAD";
}
/**
* Returns the pre-defined header value and set other necessary headers if
* the request needs to be chunk-encoded. Otherwise calls the superclass
* method which calculates the hash of the whole content for signing.
*/
@Override
protected String calculateContentHash(SignableRequest> request) {
// To be consistent with other service clients using sig-v4,
// we just set the header as "required", and AWS4Signer.sign() will be
// notified to pick up the header value returned by this method.
request.addHeader(X_AMZ_CONTENT_SHA256, "required");
if (isPayloadSigningEnabled(request)) {
if (useChunkEncoding(request)) {
final String contentLength = request.getHeaders().get(Headers.CONTENT_LENGTH);
final long originalContentLength;
if (contentLength != null) {
originalContentLength = Long.parseLong(contentLength);
} else {
/**
* "Content-Length" header could be missing if the caller is
* uploading a stream without setting Content-Length in
* ObjectMetadata. Before using sigv4, we rely on HttpClient to
* add this header by using BufferedHttpEntity when creating the
* HttpRequest object. But now, we need this information
* immediately for the signing process, so we have to cache the
* stream here.
*/
try {
originalContentLength = getContentLength(request);
} catch (IOException e) {
throw new SdkClientException(
"Cannot get the content-length of the request content.", e);
}
}
request.addHeader("x-amz-decoded-content-length",
Long.toString(originalContentLength));
// Make sure "Content-Length" header is not empty so that HttpClient
// won't cache the stream again to recover Content-Length
request.addHeader(Headers.CONTENT_LENGTH, Long.toString(
AwsChunkedEncodingInputStream
.calculateStreamContentLength(originalContentLength)));
return CONTENT_SHA_256;
} else {
return super.calculateContentHash(request);
}
}
return UNSIGNED_PAYLOAD;
}
/**
* Determine whether to use aws-chunked for signing
*/
private boolean useChunkEncoding(SignableRequest> request) {
// If chunked encoding is explicitly disabled through client options return right here.
// Chunked encoding only makes sense to do when the payload is signed
if (!isPayloadSigningEnabled(request) || isChunkedEncodingDisabled(request)) {
return false;
}
if (request.getOriginalRequestObject() instanceof PutObjectRequest
|| request.getOriginalRequestObject() instanceof UploadPartRequest) {
return true;
}
return false;
}
/**
* @return True if chunked encoding has been explicitly disabled per the request. False
* otherwise.
*/
private boolean isChunkedEncodingDisabled(SignableRequest> signableRequest) {
if (signableRequest instanceof Request) {
Request> request = (Request>) signableRequest;
Boolean isChunkedEncodingDisabled = request
.getHandlerContext(S3HandlerContextKeys.IS_CHUNKED_ENCODING_DISABLED);
return isChunkedEncodingDisabled != null && isChunkedEncodingDisabled;
}
return false;
}
/**
* @return True if payload signing is explicitly enabled.
*/
private boolean isPayloadSigningEnabled(SignableRequest> signableRequest) {
/**
* If we aren't using https we should always sign the payload.
*/
if (!signableRequest.getEndpoint().getScheme().equals("https")) {
return true;
}
if (signableRequest instanceof Request) {
Request> request = (Request>) signableRequest;
Boolean isPayloadSigningEnabled = request
.getHandlerContext(S3HandlerContextKeys.IS_PAYLOAD_SIGNING_ENABLED);
return isPayloadSigningEnabled != null && isPayloadSigningEnabled;
}
return false;
}
/**
* Read the content of the request to get the length of the stream. This
* method will wrap the stream by SdkBufferedInputStream if it is not
* mark-supported.
*/
static long getContentLength(SignableRequest> request) throws IOException {
final InputStream content = request.getContent();
if (!content.markSupported())
throw new IllegalStateException("Bug: request input stream must have been made mark-and-resettable at this point");
ReadLimitInfo info = request.getReadLimitInfo();
final int readLimit = info.getReadLimit();
long contentLength = 0;
byte[] tmp = new byte[4096];
int read;
content.mark(readLimit);
while ((read = content.read(tmp)) != -1) {
contentLength += read;
}
try {
content.reset();
} catch(IOException ex) {
throw new ResetException("Failed to reset the input stream", ex);
}
return contentLength;
}
}