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

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

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2013-2014 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 java.io.IOException;
import java.io.InputStream;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.AmazonClientException;
import com.amazonaws.Request;
import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSSessionCredentials;
import com.amazonaws.auth.AnonymousAWSCredentials;
import com.amazonaws.auth.AwsChunkedEncodingInputStream;
import com.amazonaws.auth.Presigner;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.UploadPartRequest;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.util.BinaryUtils;

/**
 * AWS4 signer implementation for AWS S3
 */
public class AWSS3V4Signer extends AWS4Signer implements Presigner {

    /** We use the logger for AmazonS3Client to */
    private static Log clientLog = LogFactory.getLog(AmazonS3Client.class);

    /** Seconds in a week, which is the max expiration time Sig-v4 accepts */
    private final static long MAX_EXPIRATION_TIME_IN_SECONDS = 60 * 60 * 24 * 7;

    /**
     * Don't double-url-encode path elements; S3 expects path elements to be
     * encoded only once in the canonical URI.
     */
    public AWSS3V4Signer() {
        super(false);
    }

    /**
     * Sign the URL using V4, it will compute the signature and add it to the
     * query string as value of parameter 'X-Amz-Signature'.
     */
    public void presignRequest(Request request, AWSCredentials credentials, Date expiration) {

        addHostHeader(request);

        // annonymous credentials, don't sign
        if (credentials instanceof AnonymousAWSCredentials) {
            return;
        }

        AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials);
        if (sanitizedCredentials instanceof AWSSessionCredentials) {
            addSessionCredentials(request, (AWSSessionCredentials) sanitizedCredentials);
            // For SigV4 presigning URL, we need to add "x-amz-security-token"
            // as a query string parameter, before constructing the canonical
            // request.
            request.addParameter(Headers.SECURITY_TOKEN, ((AWSSessionCredentials) sanitizedCredentials).getSessionToken());
        }

        long dateMilli = getDateFromRequest(request);
        final String dateStamp = getDateStamp(dateMilli);

        String scope = getScope(request, dateStamp);

        String signingCredentials = sanitizedCredentials.getAWSAccessKeyId() + "/" + scope;
        long expirationInSeconds = (expiration.getTime() - System.currentTimeMillis()) / 1000L;
        if (expirationInSeconds > MAX_EXPIRATION_TIME_IN_SECONDS) {
            throw new AmazonClientException("Requests that are pre-signed by SigV4 algorithm are valid for at most 7 days. " +
                    "The expiration date set on the current request ["
                    + getTimeStamp(expiration.getTime()) + "] has exceeded this limit.");
        }

        // Add the important parameters for v4 signing
        long now = System.currentTimeMillis();
        final String timeStamp = getTimeStamp(now);
        request.addParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
        request.addParameter("X-Amz-Date", timeStamp);
        request.addParameter("X-Amz-SignedHeaders", getSignedHeadersString(request));
        request.addParameter("X-Amz-Expires", Long.toString(expirationInSeconds));
        request.addParameter("X-Amz-Credential", signingCredentials);

        String contentSha256 = "UNSIGNED-PAYLOAD";

        HeaderSigningResult headerSigningResult = computeSignature(
                request,
                dateStamp,
                timeStamp,
                ALGORITHM,
                contentSha256,
                sanitizedCredentials);
        request.addParameter("X-Amz-Signature", BinaryUtils.toHex(headerSigningResult.getSignature()));
    }

	/**
	 * If necessary, creates a chunk-encoding wrapper on the request payload.
	 */
	@Override
	protected void processRequestPayload(Request request, HeaderSigningResult headerSigningResult) {
		if (useChunkEncoding(request)) {
        	InputStream payloadStream = request.getContent();
            String dateTime = headerSigningResult.getDateTime();
        	String keyPath = headerSigningResult.getScope();
        	byte[] kSigning = headerSigningResult.getKSigning();
        	String signature = BinaryUtils.toHex(headerSigningResult.getSignature());
    		AwsChunkedEncodingInputStream chunkEncodededStream = new AwsChunkedEncodingInputStream(payloadStream, kSigning, dateTime, keyPath, signature, this);
    		request.setContent(chunkEncodededStream);
		}
	}

	/**
	 * 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(Request 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 (useChunkEncoding(request)) {
			String contentSha256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
			long originalContentLength;
	    	if (request.getHeaders().containsKey(Headers.CONTENT_LENGTH)) {
	        	originalContentLength = Long.parseLong(request.getHeaders().get(Headers.CONTENT_LENGTH));
	    	}
	    	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 AmazonClientException("Cannot get the content-lenght 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 contentSha256;
		}
		else
			return super.calculateContentHash(request);
	}

    /**
     * Determine whether to use aws-chunked for signing
     */
	private static boolean useChunkEncoding(Request request) {
    	// Whether to use chunked encoding for signing the request
    	boolean chunkedEncodingEnabled = false;
    	if (request.getOriginalRequest() instanceof PutObjectRequest
    			|| request.getOriginalRequest() instanceof UploadPartRequest ) {
    		chunkedEncodingEnabled = true;
    	}
    	return chunkedEncodingEnabled;
	}
	
	/**
	 * Read the content of the request to get the length of the stream.
	 * This method will wrap the stream by RepeatableInputStream if it is 
	 * not mark-supported.
	 */
	private static long getContentLength(Request request) throws IOException {
		InputStream content = request.getContent();
		if ( !content.markSupported() ) {
			int streamBufferSize = Constants.getStreamBufferSize();
            content = new RepeatableInputStream(content, streamBufferSize);
            request.setContent(content);
		}
		
		long contentLength = 0;
		byte[] tmp = new byte[4096];
		int read;
		try {
			content.mark(-1);
			while ( (read = content.read(tmp)) != -1 ) {
				contentLength += read;
			}
			content.reset();
		} finally {
			content.close();
		}
		
		return contentLength;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy