All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.aws.security.EC2RequestSigner Maven / Gradle / Ivy

There is a newer version: 2.2
Show newest version
/*
 * Copyright (c) 2008-2016, Hazelcast, Inc. 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.
 * 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.hazelcast.aws.security;

import com.hazelcast.aws.impl.Constants;
import com.hazelcast.aws.utility.AwsURLEncoder;
import com.hazelcast.config.AwsConfig;
import com.hazelcast.util.QuickMath;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;

public class EC2RequestSigner {

    private static final String NEW_LINE = "\n";
    private static final String API_TERMINATOR = "aws4_request";
    private static final String HMAC_SHA256 = "HmacSHA256";
    private static final String UTF_8 = "UTF-8";
    private static final int DATE_LENGTH = 8;
    private static final int LAST_INDEX = 8;

    private final AwsConfig config;
    private final String timestamp;

    private String service;
    private Map attributes;
    private String endpoint;

    public EC2RequestSigner(AwsConfig config, String timeStamp, String endpoint) {
        if (config == null) {
            throw new IllegalArgumentException("config cannot be null");
        }
        if (timeStamp == null) {
            throw new IllegalArgumentException("timeStamp cannot be null");
        }
        this.config = config;
        this.timestamp = timeStamp;
        this.service = null;
        this.endpoint = endpoint;
    }

    public String getCredentialScope() {
        // datestamp/region/service/API_TERMINATOR
        String dateStamp = timestamp.substring(0, DATE_LENGTH);
        return format("%s/%s/%s/%s", dateStamp, config.getRegion(), this.service, API_TERMINATOR);
    }

    public String getSignedHeaders() {
        return "host";
    }

    public String sign(String service, Map attributes) {
        if (service == null) {
            throw new IllegalArgumentException("service cannot be null");
        }
        if (attributes == null) {
            throw new IllegalArgumentException("attributes cannot be null");
        }

        this.service = service;
        this.attributes = attributes;

        String canonicalRequest = getCanonicalizedRequest();
        String stringToSign = createStringToSign(canonicalRequest);
        byte[] signingKey = deriveSigningKey();

        return createSignature(stringToSign, signingKey);
    }

    /* Task 1 */
    private String getCanonicalizedRequest() {
        return Constants.GET + NEW_LINE
                + '/' + NEW_LINE
                + getCanonicalizedQueryString(this.attributes) + NEW_LINE
                + getCanonicalHeaders() + NEW_LINE
                + getSignedHeaders() + NEW_LINE
                + sha256Hashhex("");
    }

    /* Task 2 */
    private String createStringToSign(String canonicalRequest) {
        return Constants.SIGNATURE_METHOD_V4 + NEW_LINE
                + timestamp + NEW_LINE
                + getCredentialScope() + NEW_LINE
                + sha256Hashhex(canonicalRequest);
    }

    /* Task 3 */
    private byte[] deriveSigningKey() {
        String signKey = config.getSecretKey();
        String dateStamp = timestamp.substring(0, DATE_LENGTH);
        // this is derived from
        // http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python

        try {
            String key = "AWS4" + signKey;
            Mac mDate = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec skDate = new SecretKeySpec(key.getBytes(UTF_8), HMAC_SHA256);
            mDate.init(skDate);
            byte[] kDate = mDate.doFinal(dateStamp.getBytes(UTF_8));

            Mac mRegion = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec skRegion = new SecretKeySpec(kDate, HMAC_SHA256);
            mRegion.init(skRegion);
            byte[] kRegion = mRegion.doFinal(config.getRegion().getBytes(UTF_8));

            Mac mService = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec skService = new SecretKeySpec(kRegion, HMAC_SHA256);
            mService.init(skService);
            byte[] kService = mService.doFinal(this.service.getBytes(UTF_8));

            Mac mSigning = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec skSigning = new SecretKeySpec(kService, HMAC_SHA256);
            mSigning.init(skSigning);

            return mSigning.doFinal("aws4_request".getBytes(UTF_8));
        } catch (NoSuchAlgorithmException e) {
            return null;
        } catch (InvalidKeyException e) {
            return null;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    private String createSignature(String stringToSign, byte[] signingKey) {
        byte[] signature;
        try {
            Mac signMac = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec signKS = new SecretKeySpec(signingKey, HMAC_SHA256);
            signMac.init(signKS);
            signature = signMac.doFinal(stringToSign.getBytes(UTF_8));
        } catch (NoSuchAlgorithmException e) {
            return null;
        } catch (InvalidKeyException e) {
            return null;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
        return QuickMath.bytesToHex(signature);
    }

    protected String getCanonicalHeaders() {
        return format("host:%s%s", endpoint, NEW_LINE);
    }

    public String getCanonicalizedQueryString(Map attributes) {
        List components = getListOfEntries(attributes);
        Collections.sort(components);
        return getCanonicalizedQueryString(components);
    }

    protected String getCanonicalizedQueryString(List list) {
        Iterator it = list.iterator();
        StringBuilder result = new StringBuilder(it.next());
        while (it.hasNext()) {
            result.append('&').append(it.next());
        }
        return result.toString();
    }

    protected void addComponents(List components, Map attributes, String key) {
        components.add(AwsURLEncoder.urlEncode(key) + '=' + AwsURLEncoder.urlEncode(attributes.get(key)));
    }

    protected List getListOfEntries(Map entries) {
        List components = new ArrayList();
        for (String key : entries.keySet()) {
            addComponents(components, entries, key);
        }
        return components;
    }

    private String sha256Hashhex(String in) {
        String payloadHash;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(in.getBytes(UTF_8));
            byte[] digest = md.digest();
            payloadHash = QuickMath.bytesToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            return null;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
        return payloadHash;
    }

    public String createFormattedCredential() {
        return config.getAccessKey() + '/' + timestamp.substring(0, LAST_INDEX) + '/'
                + config.getRegion() + '/' + "ec2/aws4_request";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy