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();
+    }
+    
+}