Java tutorial
/* * Copyright 2010-2013 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.sina.cloudstorage.auth; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.security.DigestInputStream; import java.security.MessageDigest; import java.util.Date; 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.sina.cloudstorage.Request; import com.sina.cloudstorage.SCSClientException; import com.sina.cloudstorage.SDKGlobalConfiguration; import com.sina.cloudstorage.util.HttpUtils; import com.sina.cloudstorage.util.StringInputStream; /** * 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 implements Signer { /** The default encoding to use when URL encoding */ protected static final String DEFAULT_ENCODING = "UTF-8"; /** * Computes an RFC 2104-compliant HMAC signature and returns the result as a * Base64 encoded string. */ protected String signAndBase64Encode(String data, String key, SigningAlgorithm algorithm) throws SCSClientException { try { return signAndBase64Encode(data.getBytes(DEFAULT_ENCODING), key, algorithm); } catch (UnsupportedEncodingException e) { throw new SCSClientException("Unable to calculate a request signature: " + e.getMessage(), e); } } /** * Computes an RFC 2104-compliant HMAC signature for an array of bytes and * returns the result as a Base64 encoded string. */ protected String signAndBase64Encode(byte[] data, String key, SigningAlgorithm algorithm) throws SCSClientException { try { byte[] signature = sign(data, key.getBytes(DEFAULT_ENCODING), algorithm); return new String(Base64.encodeBase64(signature)); } catch (Exception e) { throw new SCSClientException("Unable to calculate a request signature: " + e.getMessage(), e); } } public byte[] sign(String stringData, byte[] key, SigningAlgorithm algorithm) throws SCSClientException { try { byte[] data = stringData.getBytes(DEFAULT_ENCODING); return sign(data, key, algorithm); } catch (Exception e) { throw new SCSClientException("Unable to calculate a request signature: " + e.getMessage(), e); } } protected byte[] sign(byte[] data, byte[] key, SigningAlgorithm algorithm) throws SCSClientException { try { Mac mac = Mac.getInstance(algorithm.toString()); mac.init(new SecretKeySpec(key, algorithm.toString())); return mac.doFinal(data); } catch (Exception e) { throw new SCSClientException("Unable to calculate a request signature: " + e.getMessage(), e); } } /** * Hashes the string contents (assumed to be UTF-8) using the SHA-256 * algorithm. * * @param text * The string to hash. * * @return The hashed bytes from the specified string. * * @throws SCSClientException * If the hash cannot be computed. */ public byte[] hash(String text) throws SCSClientException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(text.getBytes(DEFAULT_ENCODING)); return md.digest(); } catch (Exception e) { throw new SCSClientException("Unable to compute hash while signing request: " + e.getMessage(), e); } } protected byte[] hash(InputStream input) throws SCSClientException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); DigestInputStream digestInputStream = new DigestInputStream(input, md); byte[] buffer = new byte[1024]; while (digestInputStream.read(buffer) > -1) ; return digestInputStream.getMessageDigest().digest(); } catch (Exception e) { throw new SCSClientException("Unable to compute hash while signing request: " + e.getMessage(), e); } } /** * Hashes the binary data using the SHA-256 algorithm. * * @param data * The binary data to hash. * * @return The hashed bytes from the specified data. * * @throws SCSClientException * If the hash cannot be computed. */ public byte[] hash(byte[] data) throws SCSClientException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(data); return md.digest(); } catch (Exception e) { throw new SCSClientException("Unable to compute hash while signing request: " + e.getMessage(), e); } } /** * Examines the specified query string parameters and returns a * canonicalized form. * <p> * The canonicalized query string is formed by first sorting all the query * string parameters, then URI encoding both the key and value and then * joining them, in order, separating key value pairs with an '&'. * * @param parameters * The query string parameters to be canonicalized. * * @return A canonicalized form for the specified query string parameters. */ protected String getCanonicalizedQueryString(Map<String, String> parameters) { SortedMap<String, String> sorted = new TreeMap<String, String>(); Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry<String, String> pair = pairs.next(); String key = pair.getKey(); String value = pair.getValue(); sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false)); } StringBuilder builder = new StringBuilder(); pairs = sorted.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry<String, String> pair = pairs.next(); builder.append(pair.getKey()); builder.append("="); builder.append(pair.getValue()); if (pairs.hasNext()) { builder.append("&"); } } return builder.toString(); } protected String getCanonicalizedQueryString(Request<?> request) { /* * If we're using POST and we don't have any request payload content, * then any request query parameters will be sent as the payload, and * not in the actual query string. */ if (HttpUtils.usePayloadForQueryParameters(request)) return ""; else return this.getCanonicalizedQueryString(request.getParameters()); } // /** // * Returns the request's payload as binary data. // * // * @param request // * The request // * @return The data from the request's payload, as binary data. // */ // protected byte[] getBinaryRequestPayload(Request<?> request) { // if (HttpUtils.usePayloadForQueryParameters(request)) { // String encodedParameters = HttpUtils.encodeParameters(request); // if (encodedParameters == null) return new byte[0]; // try { // return encodedParameters.getBytes(DEFAULT_ENCODING); // } catch (UnsupportedEncodingException e) { // throw new SCSClientException("Unable to encode string into bytes"); // } // } // // return getBinaryRequestPayloadWithoutQueryParams(request); // } // /** // * Returns the request's payload as a String. // * // * @param request // * The request // * @return The data from the request's payload, as a string. // */ // protected String getRequestPayload(Request<?> request) { // return newString(getBinaryRequestPayload(request)); // } /** * Returns the request's payload contents as a String, without processing * any query string params (i.e. no form encoding for query params). * * @param request * The request * @return the request's payload contents as a String, not including any * form encoding of query string params. */ protected String getRequestPayloadWithoutQueryParams(Request<?> request) { return newString(getBinaryRequestPayloadWithoutQueryParams(request)); } /** * Returns the request's payload contents as binary data, without processing * any query string params (i.e. no form encoding for query params). * * @param request * The request * @return The request's payload contents as binary data, not including any * form encoding of query string params. */ protected byte[] getBinaryRequestPayloadWithoutQueryParams(Request<?> request) { InputStream content = getBinaryRequestPayloadStreamWithoutQueryParams(request); try { content.mark(-1); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 5]; while (true) { int bytesRead = content.read(buffer); if (bytesRead == -1) break; byteArrayOutputStream.write(buffer, 0, bytesRead); } byteArrayOutputStream.close(); content.reset(); return byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new SCSClientException("Unable to read request payload to sign request: " + e.getMessage(), e); } } // protected InputStream getBinaryRequestPayloadStream(Request<?> request) { // if (HttpUtils.usePayloadForQueryParameters(request)) { // String encodedParameters = HttpUtils.encodeParameters(request); // if (encodedParameters == null) return new ByteArrayInputStream(new byte[0]); // try { // return new ByteArrayInputStream(encodedParameters.getBytes(DEFAULT_ENCODING)); // } catch (UnsupportedEncodingException e) { // throw new SCSClientException("Unable to encode string into bytes"); // } // } // // return getBinaryRequestPayloadStreamWithoutQueryParams(request); // } protected InputStream getBinaryRequestPayloadStreamWithoutQueryParams(Request<?> request) { try { InputStream content = request.getContent(); if (content == null) return new ByteArrayInputStream(new byte[0]); if (content instanceof StringInputStream) { return content; } if (!content.markSupported()) { throw new SCSClientException("Unable to read request payload to sign request."); } return request.getContent(); } catch (Exception e) { throw new SCSClientException("Unable to read request payload to sign request: " + e.getMessage(), e); } } protected String getCanonicalizedResourcePath(String resourcePath) { return getCanonicalizedResourcePath(resourcePath, true); } protected String getCanonicalizedResourcePath(String resourcePath, boolean urlEncode) { if (resourcePath == null || resourcePath.length() == 0) { return "/"; } else { String value = urlEncode ? HttpUtils.urlEncode(resourcePath, true) : resourcePath; if (value.startsWith("/")) { return value; } else { return "/".concat(value); } } } 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; } /** * Loads the individual access key ID and secret key from the specified * credentials, ensuring that access to the credentials is synchronized on * the credentials object itself, and trimming any extra whitespace from the * credentials. * <p> * Returns either a {@link BasicSessionCredentials} or a * {@link BasicAWSCredentials} object, depending on the input type. * * @param credentials * @return A new credentials object with the sanitized credentials. */ protected AWSCredentials sanitizeCredentials(AWSCredentials credentials) { String accessKeyId = null; String secretKey = null; String token = null; synchronized (credentials) { accessKeyId = credentials.getAWSAccessKeyId(); secretKey = credentials.getAWSSecretKey(); } if (secretKey != null) secretKey = secretKey.trim(); if (accessKeyId != null) accessKeyId = accessKeyId.trim(); if (token != null) token = token.trim(); return new BasicAWSCredentials(accessKeyId, secretKey); } /** * Safely converts a UTF-8 encoded byte array into a String. * * @param bytes UTF-8 encoded binary character data. * * @return The converted String object. */ protected String newString(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new SCSClientException("Unable to encode bytes to String", e); } } protected Date getSignatureDate(int timeOffset) { Date dateValue = new Date(); if (timeOffset != 0) { long epochMillis = dateValue.getTime(); epochMillis -= timeOffset * 1000; dateValue = new Date(epochMillis); } return dateValue; } protected int getTimeOffset(Request<?> request) { int timeOffset = request.getTimeOffset(); if (SDKGlobalConfiguration.getGlobalTimeOffset() != 0) { // if global time offset is set then use that (For clock skew issues) timeOffset = SDKGlobalConfiguration.getGlobalTimeOffset(); } return timeOffset; } }