org.jclouds.glacier.util.AWSRequestSignerV4.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.glacier.util.AWSRequestSignerV4.java

Source

/*
 * 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.
 */
package org.jclouds.glacier.util;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.util.Closeables2.closeQuietly;

import java.io.IOException;
import java.util.Locale;
import java.util.Map.Entry;

import javax.crypto.Mac;

import org.jclouds.crypto.Crypto;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;

// TODO: Query parameters, not necessary for Glacier
// TODO: Endpoint on buildCredentialScope is being read from the static string. Uncool.
/**
 * Signs requests using the AWSv4 signing algorithm
 *
 * @see <a href="http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html" />
 */
public final class AWSRequestSignerV4 {

    public static final String AUTH_TAG = "AWS4";
    public static final String HEADER_TAG = "x-amz-";
    public static final String ALGORITHM = AUTH_TAG + "-HMAC-SHA256";
    public static final String TERMINATION_STRING = "aws4_request";
    public static final String REGION = "us-east-1";
    public static final String SERVICE = "glacier";

    private final Crypto crypto;
    private final String identity;
    private final String credential;

    public AWSRequestSignerV4(String identity, String credential, Crypto crypto) {
        this.crypto = checkNotNull(crypto, "crypto");
        this.identity = checkNotNull(identity, "identity");
        this.credential = checkNotNull(credential, "credential");
    }

    private static HashCode buildHashedCanonicalRequest(String method, String endpoint, HashCode hashedPayload,
            String canonicalizedHeadersString, String signedHeaders) {
        return Hashing.sha256().newHasher().putString(method, UTF_8).putString("\n", UTF_8)
                .putString(endpoint, UTF_8).putString("\n", UTF_8).putString("\n", UTF_8)
                .putString(canonicalizedHeadersString, UTF_8).putString("\n", UTF_8).putString(signedHeaders, UTF_8)
                .putString("\n", UTF_8).putString(hashedPayload.toString(), UTF_8).hash();
    }

    private static String createStringToSign(String date, String credentialScope, HashCode hashedCanonicalRequest) {
        return ALGORITHM + "\n" + date + "\n" + credentialScope + "\n" + hashedCanonicalRequest.toString();
    }

    private static String formatDateWithoutTimestamp(String date) {
        return date.substring(0, 8);
    }

    private static String buildCredentialScope(String dateWithoutTimeStamp) {
        return dateWithoutTimeStamp + "/" + REGION + "/" + SERVICE + "/" + TERMINATION_STRING;
    }

    private static Multimap<String, String> buildCanonicalizedHeadersMap(HttpRequest request) {
        Multimap<String, String> headers = request.getHeaders();
        SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
        for (Entry<String, String> header : headers.entries()) {
            if (header.getKey() == null)
                continue;
            String key = header.getKey().toString().toLowerCase(Locale.getDefault());
            // Ignore any headers that are not particularly interesting.
            if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase(HttpHeaders.CONTENT_MD5)
                    || key.equalsIgnoreCase(HttpHeaders.HOST) || key.startsWith(HEADER_TAG)) {
                canonicalizedHeaders.put(key, header.getValue());
            }
        }
        return canonicalizedHeaders;
    }

    private static String buildCanonicalizedHeadersString(Multimap<String, String> canonicalizedHeadersMap) {
        StringBuilder canonicalizedHeadersBuffer = new StringBuilder();
        for (Entry<String, String> header : canonicalizedHeadersMap.entries()) {
            String key = header.getKey();
            canonicalizedHeadersBuffer.append(key.toLowerCase()).append(':').append(header.getValue()).append('\n');
        }
        return canonicalizedHeadersBuffer.toString();
    }

    private static String buildSignedHeaders(Multimap<String, String> canonicalizedHeadersMap) {
        return Joiner.on(';')
                .join(Iterables.transform(canonicalizedHeadersMap.keySet(), new Function<String, String>() {

                    @Override
                    public String apply(String input) {
                        return input.toLowerCase();
                    }
                }));
    }

    private static HashCode buildHashedPayload(HttpRequest request) {
        HashingInputStream his = null;
        try {
            his = new HashingInputStream(Hashing.sha256(),
                    request.getPayload() == null ? ByteSource.empty().openStream()
                            : request.getPayload().openStream());
            ByteStreams.copy(his, ByteStreams.nullOutputStream());
            return his.hash();
        } catch (IOException e) {
            throw new HttpException("Error signing request", e);
        } finally {
            closeQuietly(his);
        }
    }

    private static String buildAuthHeader(String accessKey, String credentialScope, String signedHeaders,
            String signature) {
        return ALGORITHM + " " + "Credential=" + accessKey + "/" + credentialScope + "," + "SignedHeaders="
                + signedHeaders + "," + "Signature=" + signature;
    }

    private byte[] hmacSha256(byte[] key, String s) {
        try {
            Mac hmacSHA256 = crypto.hmacSHA256(key);
            return hmacSHA256.doFinal(s.getBytes());
        } catch (Exception e) {
            throw new HttpException("Error signing request", e);
        }
    }

    private String buildSignature(String dateWithoutTimestamp, String stringToSign) {
        byte[] kSecret = (AUTH_TAG + credential).getBytes(UTF_8);
        byte[] kDate = hmacSha256(kSecret, dateWithoutTimestamp);
        byte[] kRegion = hmacSha256(kDate, REGION);
        byte[] kService = hmacSha256(kRegion, SERVICE);
        byte[] kSigning = hmacSha256(kService, TERMINATION_STRING);
        return BaseEncoding.base16().encode(hmacSha256(kSigning, stringToSign)).toLowerCase();
    }

    public HttpRequest sign(HttpRequest request) {
        // Grab the needed data to build the signature
        Multimap<String, String> canonicalizedHeadersMap = buildCanonicalizedHeadersMap(request);
        String canonicalizedHeadersString = buildCanonicalizedHeadersString(canonicalizedHeadersMap);
        String signedHeaders = buildSignedHeaders(canonicalizedHeadersMap);
        String date = request.getFirstHeaderOrNull(GlacierHeaders.ALTERNATE_DATE);
        String dateWithoutTimestamp = formatDateWithoutTimestamp(date);
        String method = request.getMethod();
        String endpoint = request.getEndpoint().getRawPath();
        String credentialScope = buildCredentialScope(dateWithoutTimestamp);
        HashCode hashedPayload = buildHashedPayload(request);

        // Task 1: Create a Canonical Request For Signature Version 4.
        HashCode hashedCanonicalRequest = buildHashedCanonicalRequest(method, endpoint, hashedPayload,
                canonicalizedHeadersString, signedHeaders);

        // Task 2: Create a String to Sign for Signature Version 4.
        String stringToSign = createStringToSign(date, credentialScope, hashedCanonicalRequest);

        // Task 3: Calculate the AWS Signature Version 4.
        String signature = buildSignature(dateWithoutTimestamp, stringToSign);

        // Sign the request
        String authHeader = buildAuthHeader(identity, credentialScope, signedHeaders, signature);
        request = request.toBuilder().replaceHeader(HttpHeaders.AUTHORIZATION, authHeader).build();
        return request;
    }
}