/* * Copyright 2010 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.auth; import java.net.MalformedURLException; import java.security.MessageDigest; import java.security.SignatureException; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazonaws.Request; import com.amazonaws.util.DateUtils; /** * Signer implementation that signs requests with the AWS3 signing protocol. */ public class AWS3Signer extends AbstractAWSSigner { private static final String AUTHORIZATION_HEADER = "X-Amzn-Authorization"; private static final String NONCE_HEADER = "x-amz-nonce"; private static final String HTTP_SCHEME = "AWS3"; private static final String HTTPS_SCHEME = "AWS3-HTTPS"; private final AWSCredentials credentials; protected static final DateUtils dateUtils = new DateUtils(); private static final Log log = LogFactory.getLog(AWS3Signer.class); /** * Constructs a new AWS3Signer using the specified AWS account credentials * to sign requests using the AWS3 signing protocol. * * @param credentials * The AWS account credentials to use when signing requests. */ public AWS3Signer(AWSCredentials credentials) { this.credentials = credentials; } /** * Signs the specified request with the AWS3 signing protocol by using the * AWS account credentials specified when this object was constructed and * adding the required AWS3 headers to the request. * * @param request * The request to sign. * * @throws SignatureException * If any problems are encountered while signing the request. */ public void sign(Request<?> request) throws SignatureException { SigningAlgorithm algorithm = SigningAlgorithm.HmacSHA256; String nonce = UUID.randomUUID().toString(); String date = dateUtils.formatRfc822Date(new Date()); boolean isHttps = isHttpsRequest(request); String stringToSign; if (isHttps) { stringToSign = date + nonce; } else { stringToSign = "POST\n" + getCanonicalizedEndpoint(request.getEndpoint()) + "\n" + getCanonicalizedResourcePath(request.getEndpoint()) + "\n" + getCanonicalizedQueryString(request.getParameters()) + "\n" + getCanonicalizedHeadersForStringToSign(request) + "\n" + ""; // we shouldn't ever have a payload in a request yet stringToSign = hash(stringToSign); } log.debug("Calculated StringToSign: " + stringToSign); String accessKeyId = null; String secretKey = null; synchronized (credentials) { accessKeyId = credentials.getAWSAccessKeyId(); secretKey = credentials.getAWSSecretKey(); } String signature = sign(stringToSign, secretKey, algorithm); StringBuilder builder = new StringBuilder(); builder.append(isHttps ? HTTPS_SCHEME : HTTP_SCHEME).append(" "); builder.append("AWSAccessKeyId=" + accessKeyId + ","); builder.append("Algorithm=" + algorithm.toString() + ","); builder.append("Signature=" + signature); request.addHeader(AUTHORIZATION_HEADER, builder.toString()); request.addHeader(NONCE_HEADER, nonce); request.addHeader("Date", date); } private String hash(String text) throws SignatureException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(text.getBytes()); return toHex(md.digest()); } catch (Exception e) { throw new SignatureException("Unable to compute hash while signing request: " + e.getMessage(), e); } } private String toHex(byte[] data) { StringBuilder sb = new StringBuilder(data.length * 2); for (int i = 0; i < data.length; i++) { String hex = Integer.toHexString(data[i]); if (hex.length() == 1) { // Append leading zero. sb.append("0"); } else if (hex.length() == 8) { // Remove ff prefix from negative numbers. hex = hex.substring(6); } sb.append(hex); } return sb.toString().toLowerCase(Locale.getDefault()); } protected String getCanonicalizedHeadersForStringToSign(Request<?> request) { SortedMap<String, String> sortedHeaderMap = new TreeMap<String, String>(); for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) { String key = entry.getKey().toLowerCase(); if (key.startsWith("x-amz") || key.equals("date") || key.equals("content-length")) { sortedHeaderMap.put(key, entry.getValue()); } } StringBuilder builder = new StringBuilder(); for (Map.Entry<String, String> entry : sortedHeaderMap.entrySet()) { builder.append(entry.getKey()).append(":") .append(entry.getValue()).append("\n"); } return builder.toString(); } private boolean isHttpsRequest(Request<?> request) throws SignatureException { try { String protocol = request.getEndpoint().toURL().getProtocol().toLowerCase(); if (protocol.equals("http")) { return false; } else if (protocol.equals("https")) { return true; } else { throw new SignatureException("Unknown request endpoint protocol " + "encountered while signing request: " + protocol); } } catch (MalformedURLException e) { throw new SignatureException("Unable to parse request endpoint during signing", e); } } }