/*
* 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.URI;
import java.security.SignatureException;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import com.amazonaws.util.HttpUtils;
/**
* Abstract base class for AWS signing protocol implementations. Provides
* utilities commonly needed by signing protocols such as computing
* canonicalized host names, query string parameters, etc.
* <p>
* Not intended to be sub-classed by developers.
*/
public abstract class AbstractAWSSigner {
/** The default encoding to use when URL encoding */
private static final String DEFAULT_ENCODING = "UTF-8";
/**
* Computes RFC 2104-compliant HMAC signature.
*/
protected String sign(String data, String key, SigningAlgorithm algorithm)
throws SignatureException {
try {
Mac mac = Mac.getInstance(algorithm.toString());
mac.init(new SecretKeySpec(key.getBytes(), algorithm.toString()));
byte[] signature = Base64.encodeBase64(mac.doFinal(data
.getBytes(DEFAULT_ENCODING)));
return new String(signature);
} catch (Exception e) {
throw new SignatureException("Failed to generate signature: " + e.getMessage(), e);
}
}
protected String getCanonicalizedQueryString(Map<String, String> parameters) {
SortedMap<String, String> sorted = new TreeMap<String, String>();
sorted.putAll(parameters);
StringBuilder builder = new StringBuilder();
Iterator<Map.Entry<String, String>> pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Map.Entry<String, String> pair = pairs.next();
String key = pair.getKey();
String value = pair.getValue();
builder.append(HttpUtils.urlEncode(key, false));
builder.append("=");
builder.append(HttpUtils.urlEncode(value, false));
if (pairs.hasNext()) {
builder.append("&");
}
}
return builder.toString();
}
protected String getCanonicalizedResourcePath(URI endpoint) {
String uri = endpoint.getPath();
if (uri == null || uri.length() == 0) {
return "/";
} else {
return HttpUtils.urlEncode(uri, true);
}
}
protected String getCanonicalizedEndpoint(URI endpoint) {
String endpointForStringToSign = endpoint.getHost().toLowerCase();
/*
* Apache HttpClient will omit the port in the Host header for default
* port values (i.e. 80 for HTTP and 443 for HTTPS) even if we
* explicitly specify it, so we need to be careful that we use the same
* value here when we calculate the string to sign and in the Host
* header we send in the HTTP request.
*/
if (HttpUtils.isUsingNonDefaultPort(endpoint)) {
endpointForStringToSign += ":" + endpoint.getPort();
}
return endpointForStringToSign;
}
}