//
// typica - A client library for Amazon Web Services
// Copyright (C) 2007 Xerox Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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.xerox.amazonws.common;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* This class provides common code to the query and rest connection classes
*
* @author D. Kavanagh
* @author developer@dotech.com
*/
public abstract class AWSConnection {
private String awsAccessId;
private String awsSecretKey;
private boolean isSecure;
private String server;
private int port;
private String resourcePrefix = "/";
private int sigVersion = 2;
protected Map <String, List<String>> headers;
// used for caching last used Mac obj.. to save time 99.99% of the time
// no longer static. was causing bottleneck for multi-core systems.
private Map<String, Mac> macMap = new HashMap<String, Mac>();
private String lastSecretKey;
private Object macSync = new Object();
/**
* Initializes the queue service with your AWS login information.
*
* @param awsAccessId The your user key into AWS
* @param awsSecretKey The secret string used to generate signatures for authentication.
* @param isSecure True if the data should be encrypted on the wire on the way to or from SQS.
* @param server Which host to connect to.
* @param port Which port to use.
*/
public AWSConnection(String awsAccessId, String awsSecretKey, boolean isSecure,
String server, int port)
{
this.awsAccessId = awsAccessId;
this.awsSecretKey = awsSecretKey;
this.isSecure = isSecure;
this.server = server;
this.port = port;
this.headers = new TreeMap<String, List<String>>();
}
/**
* This method provides the URL for the queue service based on initialization.
*
* @return generated queue service url
*/
public URL getUrl() {
try {
return makeURL("");
} catch (MalformedURLException ex) {
return null;
}
}
public String getAwsAccessKeyId() {
return this.awsAccessId;
}
public String getSecretAccessKey() {
return this.awsSecretKey;
}
public boolean isSecure() {
return this.isSecure;
}
public String getServer() {
return this.server;
}
public void setServer(String server) {
this.server = server;
}
public int getPort() {
return this.port;
}
public String getResourcePrefix() {
return this.resourcePrefix;
}
public void setResourcePrefix(String prefix) {
this.resourcePrefix = prefix;
}
/**
* This method returns the signature version
*
* @return the version
*/
public int getSignatureVersion() {
return sigVersion;
}
/**
* This method sets the signature version used to sign requests (0, 1 or 2).
* NOTE: This value defaults to 2, so passing 1 is the most likely use case.
*
* @param version signature version
*/
public void setSignatureVersion(int version) {
if (version != 0 && version != 1 && version != 2) {
throw new IllegalArgumentException("Only signature versions 0, 1 and 2 supported");
}
sigVersion = version;
}
/**
* Create a new URL object for a given resource.
* @param resource The resource name (bucketName + "/" + key).
*/
protected URL makeURL(String resource) throws MalformedURLException {
String protocol = this.isSecure ? "https" : "http";
return new URL(protocol, this.server, this.port, resourcePrefix+resource);
}
/**
* Calculate the HMAC/SHA1 on a string.
* @param awsSecretKey passcode to sign it with
* @param canonicalString data to sign
* @return signature
* @throws NoSuchAlgorithmException If the algorithm does not exist. Unlikely
* @throws InvalidKeyException If the key is invalid.
*/
protected String encode(String awsSecretKey, String canonicalString,
boolean urlencode) {
return encode(awsSecretKey, canonicalString, urlencode, getAlgorithm());
}
protected String encode(String awsSecretKey, String canonicalString,
boolean urlencode, String algorithm) {
// The following HMAC/SHA1 code for the signature is taken from the
// AWS Platform's implementation of RFC2104 (amazon.webservices.common.Signature)
//
// Acquire an HMAC/SHA1 from the raw key bytes.
SecretKeySpec signingKey =
new SecretKeySpec(awsSecretKey.getBytes(), algorithm);
// Acquire the MAC instance and initialize with the signing key.
Mac mac = null;
synchronized (macSync) {
mac = macMap.get(algorithm);
if (mac == null || !lastSecretKey.equals(awsSecretKey)) {
try {
mac = Mac.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
// should not happen
throw new RuntimeException("Could not find sha1 algorithm", e);
}
try {
mac.init(signingKey);
macMap.put(algorithm, mac);
} catch (InvalidKeyException e) {
// also should not happen
mac = null;
throw new RuntimeException("Could not initialize the MAC algorithm", e);
}
lastSecretKey = awsSecretKey;
}
}
// Compute the HMAC on the digest, and set it.
byte [] signedBytes = null;
synchronized (mac) {
try {
signedBytes = mac.doFinal(canonicalString.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
signedBytes = mac.doFinal(canonicalString.getBytes());
}
}
String b64 = new String(Base64.encodeBase64(signedBytes));
if (urlencode) {
return urlencode(b64);
} else {
return b64;
}
}
protected String getAlgorithm() {
return (sigVersion==2)?"HmacSHA256":"HmacSHA1";
}
protected String urlencode(String unencoded) {
String encoded = unencoded;
try {
if (sigVersion == 2) {
encoded = URLEncoder.encode(unencoded, "UTF-8").replace("+", "%20").replace("*", "%2A").replaceAll("%7E", "~");
}
else {
encoded = URLEncoder.encode(unencoded, "UTF-8");
}
} catch (UnsupportedEncodingException e) {
// should never happen
throw new RuntimeException("Could not url encode to UTF-8", e);
}
return encoded;
}
}