You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2010/04/14 21:55:44 UTC
svn commit: r934160 - in
/httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth:
AWSScheme.java AWSSchemeFactory.java
Author: olegk
Date: Wed Apr 14 19:55:44 2010
New Revision: 934160
URL: http://svn.apache.org/viewvc?rev=934160&view=rev
Log:
HTTPCLIENT-926: Amazon S3 authentication support
Contributed by Jean-Philippe Steinmetz <caskater47 at gmail.com>
Added:
httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSScheme.java
httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSSchemeFactory.java
Added: httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSScheme.java?rev=934160&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSScheme.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSScheme.java Wed Apr 14 19:55:44 2010
@@ -0,0 +1,255 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.contrib.auth;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHeader;
+
+/**
+ * Implementation of Amazon S3 authentication. This scheme must be used
+ * preemptively only.
+ * <p>
+ * Reference Document: {@link http
+ * ://docs.amazonwebservices.com/AmazonS3/latest/index
+ * .html?RESTAuthentication.html}
+ */
+public class AWSScheme implements AuthScheme {
+
+ private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
+ public static final String NAME = "AWS";
+
+ public AWSScheme() {
+ }
+
+ public Header authenticate(
+ final Credentials credentials,
+ final HttpRequest request) throws AuthenticationException {
+ // If the Date header has not been provided add it as it is required
+ if (request.getFirstHeader("Date") == null) {
+ Header dateHeader = new BasicHeader("Date", DateUtils.formatDate(new Date()));
+ request.addHeader(dateHeader);
+ }
+
+ String canonicalizedAmzHeaders = getCanonicalizedAmzHeaders(request.getAllHeaders());
+ String canonicalizedResource = getCanonicalizedResource(request.getRequestLine().getUri(),
+ (request.getFirstHeader("Host") != null ? request.getFirstHeader("Host").getValue()
+ : null));
+ String contentMD5 = request.getFirstHeader("Content-MD5") != null ? request.getFirstHeader(
+ "Content-MD5").getValue() : "";
+ String contentType = request.getFirstHeader("Content-Type") != null ? request
+ .getFirstHeader("Content-Type").getValue() : "";
+ String date = request.getFirstHeader("Date").getValue();
+ String method = request.getRequestLine().getMethod();
+
+ StringBuilder toSign = new StringBuilder();
+ toSign.append(method).append("\n");
+ toSign.append(contentMD5).append("\n");
+ toSign.append(contentType).append("\n");
+ toSign.append(date).append("\n");
+ toSign.append(canonicalizedAmzHeaders);
+ toSign.append(canonicalizedResource);
+
+ String signature = calculateRFC2104HMAC(toSign.toString(), credentials.getPassword());
+
+ String headerValue = NAME + " " + credentials.getUserPrincipal().getName() + ":"
+ + signature;
+
+ return new BasicHeader("Authorization", headerValue);
+ }
+
+ /**
+ * Computes RFC 2104-compliant HMAC signature.
+ *
+ * @param data
+ * The data to be signed.
+ * @param key
+ * The signing key.
+ * @return The Base64-encoded RFC 2104-compliant HMAC signature.
+ * @throws RuntimeException
+ * when signature generation fails
+ */
+ private static String calculateRFC2104HMAC(
+ final String data,
+ final String key) throws AuthenticationException {
+ try {
+ // get an hmac_sha1 key from the raw key bytes
+ SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
+
+ // get an hmac_sha1 Mac instance and initialize with the signing key
+ Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
+ mac.init(signingKey);
+
+ // compute the hmac on input data bytes
+ byte[] rawHmac = mac.doFinal(data.getBytes());
+
+ // base64-encode the hmac
+ return Base64.encodeBase64String(rawHmac);
+
+ } catch (InvalidKeyException ex) {
+ throw new AuthenticationException("Failed to generate HMAC: " + ex.getMessage(), ex);
+ } catch (NoSuchAlgorithmException ex) {
+ throw new AuthenticationException(HMAC_SHA1_ALGORITHM +
+ " algorithm is not supported", ex);
+ }
+ }
+
+ /**
+ * Returns the canonicalized AMZ headers.
+ *
+ * @param headers
+ * The list of request headers.
+ * @return The canonicalized AMZ headers.
+ */
+ private static String getCanonicalizedAmzHeaders(final Header[] headers) {
+ StringBuilder sb = new StringBuilder();
+ Pattern spacePattern = Pattern.compile("\\s+");
+
+ // Create a lexographically sorted list of headers that begin with x-amz
+ SortedMap<String, String> amzHeaders = new TreeMap<String, String>();
+ for (Header header : headers) {
+ String name = header.getName().toLowerCase();
+
+ if (name.startsWith("x-amz-")) {
+ String value = "";
+
+ if (amzHeaders.containsKey(name))
+ value = amzHeaders.get(name) + "," + header.getValue();
+ else
+ value = header.getValue();
+
+ // All newlines and multiple spaces must be replaced with a
+ // single space character.
+ Matcher m = spacePattern.matcher(value);
+ value = m.replaceAll(" ");
+
+ amzHeaders.put(name, value);
+ }
+ }
+
+ // Concatenate all AMZ headers
+ for (Entry<String, String> entry : amzHeaders.entrySet()) {
+ sb.append(entry.getKey()).append(':').append(entry.getValue()).append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the canonicalized resource.
+ *
+ * @param uri
+ * The resource uri
+ * @param hostName
+ * the host name
+ * @return The canonicalized resource.
+ */
+ private static String getCanonicalizedResource(String uri, String hostName) {
+ StringBuilder sb = new StringBuilder();
+
+ // Append the bucket if there is one
+ if (hostName != null) {
+ // If the host name contains a port number remove it
+ if (hostName.contains(":"))
+ hostName = hostName.substring(0, hostName.indexOf(":"));
+
+ // Now extract the bucket if there is one
+ if (hostName.endsWith(".s3.amazonaws.com")) {
+ String bucketName = hostName.substring(0, hostName.length() - 17);
+ sb.append("/" + bucketName);
+ }
+ }
+
+ int queryIdx = uri.indexOf("?");
+
+ // Append the resource path
+ if (queryIdx >= 0)
+ sb.append(uri.substring(0, queryIdx));
+ else
+ sb.append(uri.substring(0, uri.length()));
+
+ // Append the AWS sub-resource
+ if (queryIdx >= 0) {
+ String query = uri.substring(queryIdx - 1, uri.length());
+
+ if (query.contains("?acl"))
+ sb.append("?acl");
+ else if (query.contains("?location"))
+ sb.append("?location");
+ else if (query.contains("?logging"))
+ sb.append("?logging");
+ else if (query.contains("?torrent"))
+ sb.append("?torrent");
+ }
+
+ return sb.toString();
+ }
+
+ public String getParameter(String name) {
+ return null;
+ }
+
+ public String getRealm() {
+ return null;
+ }
+
+ public String getSchemeName() {
+ return NAME;
+ }
+
+ public boolean isComplete() {
+ return true;
+ }
+
+ public boolean isConnectionBased() {
+ return false;
+ }
+
+ public void processChallenge(final Header header) throws MalformedChallengeException {
+ // Nothing to do here
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSSchemeFactory.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSSchemeFactory.java?rev=934160&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSSchemeFactory.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-contrib/src/main/java/org/apache/http/contrib/auth/AWSSchemeFactory.java Wed Apr 14 19:55:44 2010
@@ -0,0 +1,44 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.contrib.auth;
+
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.params.HttpParams;
+
+/**
+ * {@link AuthSchemeFactory} implementation that creates and initializes
+ * {@link AWSScheme} instances.
+ */
+public class AWSSchemeFactory implements AuthSchemeFactory {
+
+ public AuthScheme newInstance(final HttpParams params) {
+ return new AWSScheme();
+ }
+
+}