All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.s3.internal.AWSS3V4Signer Maven / Gradle / Ivy

/*
 * Copyright 2013-2017 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 AWS 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy