<commit_before>/*
 * Minimal Object Storage Library, (C) 2015 Minio, Inc.
 *
 * 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 io.minio.objectstorage.client;

import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpEncoding;
import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

class RequestSigner implements HttpExecuteInterceptor {
    private final SimpleDateFormat dateFormatyyyyMMddThhmmssZ;
    private final SimpleDateFormat dateFormatyyyyMMdd;
    private final SimpleDateFormat dateFormat;
    private byte[] data = new byte[0];
    private String accessKey = null;
    private String secretKey = null;
    private byte[] userProvidedSigningKey;

    public RequestSigner(byte[] data) {
        if (data == null) {
            data = new byte[0];
        }
        this.data = data;

        dateFormatyyyyMMddThhmmssZ = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        dateFormatyyyyMMddThhmmssZ.setTimeZone(TimeZone.getTimeZone("GMT"));

        dateFormatyyyyMMdd = new SimpleDateFormat("yyyyMMdd");
        dateFormatyyyyMMdd.setTimeZone(TimeZone.getTimeZone("GMT"));

        dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private void signV4(HttpRequest request, byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        if ((this.accessKey == null || this.secretKey == null) && this.userProvidedSigningKey == null) {
            return;
        }

        String region = getRegion(request);

        Date signingDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime();
        addDate(request, signingDate);

        byte[] dataHashBytes = computeSha256(data);
        String dataHash = DatatypeConverter.printHexBinary(dataHashBytes).toLowerCase();

        request.getHeaders().set("x-amz-content-sha256", dataHash);
        request.getHeaders().setDate(dateFormat.format(signingDate));

        request.getHeaders().set("Host", request.getUrl().getHost());

        // get canonical request and headers to sign
        Tuple2<String, String> canonicalRequestAndHeaders = getCanonicalRequest(request, dataHash);
        String canonicalRequest = canonicalRequestAndHeaders.getFirst();
        String signedHeaders = canonicalRequestAndHeaders.getSecond();

        // get sha256 of canonical request
        byte[] canonicalHashBytes = computeSha256(canonicalRequest.getBytes("UTF-8"));
        String canonicalHash = DatatypeConverter.printHexBinary(canonicalHashBytes).toLowerCase();

        // generate key to sign
        String stringToSign = getStringToSign(region, canonicalHash, signingDate);
        byte[] signingKey = getSigningKey(signingDate, region);

        // generate signing key
        String signature = DatatypeConverter.printHexBinary(getSignature(signingKey, stringToSign)).toLowerCase();

        // generate authorization header
        String authorization = getAuthorizationHeader(signedHeaders, signature, signingDate, region);

        // set authorization header
        List<String> authorizationList = new LinkedList<String>();
        authorizationList.add(authorization);
        request.getHeaders().setAuthorization(authorizationList);

        // print debug info
//        System.out.println("--- Canonical Request ---");
//        System.out.println(canonicalRequest);
//        System.out.println("--- Canonical Hash ---");
//        System.out.println(canonicalHash);
//        System.out.println("--- String to Sign ---");
//        System.out.println(stringToSign);
//        System.out.println("--- Signing Key ---");
//        System.out.println(DatatypeConverter.printHexBinary(signingKey));
//        System.out.println("--- Signature ---");
//        System.out.println(signature);
//        System.out.println("--- Authorization ---");
//        System.out.println(authorization);
//        System.out.println("--- End ---");
    }

    private String getRegion(HttpRequest request) {
        String host = request.getUrl().getHost();
        return Regions.INSTANCE.getRegion(host);
    }

    private byte[] computeSha256(byte[] data) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        return digest.digest(data);
    }

    private void addDate(HttpRequest request, Date date) {
        String dateString = dateFormatyyyyMMddThhmmssZ.format(date);
        request.getHeaders().set("x-amz-date", dateString);
    }

    private String getAuthorizationHeader(String signedHeaders, String signature, Date date, String region) {
        return "AWS4-HMAC-SHA256 Credential=" + this.accessKey + "/" + getScope(region, date) + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
    }

    private byte[] getSignature(byte[] signingKey, String stringToSign) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        return signHmac(signingKey, stringToSign.getBytes("UTF-8"));
    }

    private byte[] getSigningKey(Date date, String region) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        if(this.accessKey != null && this.secretKey != null) {
            String formattedDate = dateFormatyyyyMMdd.format(date);
            String dateKeyLine = "AWS4" + this.secretKey;
            byte[] dateKey = signHmac(dateKeyLine.getBytes("UTF-8"), formattedDate.getBytes("UTF-8"));
            byte[] dateRegionKey = signHmac(dateKey, region.getBytes("UTF-8"));
            byte[] dateRegionServiceKey = signHmac(dateRegionKey, "s3".getBytes("UTF-8"));
            return signHmac(dateRegionServiceKey, "aws4_request".getBytes("UTF-8"));
        }
        return this.userProvidedSigningKey;
    }

    private byte[] signHmac(byte[] curKey, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
        SecretKeySpec key = new SecretKeySpec(curKey, "HmacSHA256");
        Mac hmacSha256 = Mac.getInstance("HmacSHA256");
        hmacSha256.init(key);
        hmacSha256.update(data);
        return hmacSha256.doFinal();
    }

    private String getScope(String region, Date date) {
        String formattedDate = dateFormatyyyyMMdd.format(date);
        return formattedDate + "/" + region + "/" + "s3" + "/" + "aws4_request";
    }

    private String getStringToSign(String region, String canonicalHash, Date date) {
        return "AWS4-HMAC-SHA256" + "\n" +
                dateFormatyyyyMMddThhmmssZ.format(date) + "\n" +
                getScope(region, date) + "\n" + canonicalHash;
    }

    private Tuple2<String, String> getCanonicalRequest(HttpRequest request, String bodySha256Hash) {
        StringWriter canonicalWriter = new StringWriter();
        PrintWriter canonicalPrinter = new PrintWriter(canonicalWriter, true);

        String method = request.getRequestMethod();
        String path = request.getUrl().getRawPath();
        String rawQuery = request.getUrl().toURI().getQuery();<commit_msg>RequestSigner can now generate signing keys<commit_after>/*
 * Minimal Object Storage Library, (C) 2015 Minio, Inc.
 *
 * 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 io.minio.objectstorage.client;

import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpEncoding;
import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

class RequestSigner implements HttpExecuteInterceptor {
    private static final SimpleDateFormat dateFormatyyyyMMddThhmmssZ=new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    private static final SimpleDateFormat dateFormatyyyyMMdd = new SimpleDateFormat("yyyyMMdd");
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");

    static {
        dateFormatyyyyMMddThhmmssZ.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormatyyyyMMdd.setTimeZone(TimeZone.getTimeZone("GMT"));
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private byte[] data = new byte[0];
    private String accessKey = null;
    private String secretKey = null;
    private byte[] userProvidedSigningKey;

    public RequestSigner(byte[] data) {
        if (data == null) {
            data = new byte[0];
        }
        this.data = data;

    }

    private void signV4(HttpRequest request, byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        if ((this.accessKey == null || this.secretKey == null) && this.userProvidedSigningKey == null) {
            return;
        }

        String region = getRegion(request);

        Date signingDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime();
        addDate(request, signingDate);

        byte[] dataHashBytes = computeSha256(data);
        String dataHash = DatatypeConverter.printHexBinary(dataHashBytes).toLowerCase();

        request.getHeaders().set("x-amz-content-sha256", dataHash);
        request.getHeaders().setDate(dateFormat.format(signingDate));

        request.getHeaders().set("Host", request.getUrl().getHost());

        // get canonical request and headers to sign
        Tuple2<String, String> canonicalRequestAndHeaders = getCanonicalRequest(request, dataHash);
        String canonicalRequest = canonicalRequestAndHeaders.getFirst();
        String signedHeaders = canonicalRequestAndHeaders.getSecond();

        // get sha256 of canonical request
        byte[] canonicalHashBytes = computeSha256(canonicalRequest.getBytes("UTF-8"));
        String canonicalHash = DatatypeConverter.printHexBinary(canonicalHashBytes).toLowerCase();

        // generate key to sign
        String stringToSign = getStringToSign(region, canonicalHash, signingDate);
        byte[] signingKey = getSigningKey(signingDate, region);

        // generate signing key
        String signature = DatatypeConverter.printHexBinary(getSignature(signingKey, stringToSign)).toLowerCase();

        // generate authorization header
        String authorization = getAuthorizationHeader(signedHeaders, signature, signingDate, region);

        // set authorization header
        List<String> authorizationList = new LinkedList<String>();
        authorizationList.add(authorization);
        request.getHeaders().setAuthorization(authorizationList);

        // print debug info
//        System.out.println("--- Canonical Request ---");
//        System.out.println(canonicalRequest);
//        System.out.println("--- Canonical Hash ---");
//        System.out.println(canonicalHash);
//        System.out.println("--- String to Sign ---");
//        System.out.println(stringToSign);
//        System.out.println("--- Signing Key ---");
//        System.out.println(DatatypeConverter.printHexBinary(signingKey));
//        System.out.println("--- Signature ---");
//        System.out.println(signature);
//        System.out.println("--- Authorization ---");
//        System.out.println(authorization);
//        System.out.println("--- End ---");
    }

    private String getRegion(HttpRequest request) {
        String host = request.getUrl().getHost();
        return Regions.INSTANCE.getRegion(host);
    }

    private byte[] computeSha256(byte[] data) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        return digest.digest(data);
    }

    private void addDate(HttpRequest request, Date date) {
        String dateString = dateFormatyyyyMMddThhmmssZ.format(date);
        request.getHeaders().set("x-amz-date", dateString);
    }

    private String getAuthorizationHeader(String signedHeaders, String signature, Date date, String region) {
        return "AWS4-HMAC-SHA256 Credential=" + this.accessKey + "/" + getScope(region, date) + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
    }

    private byte[] getSignature(byte[] signingKey, String stringToSign) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        return signHmac(signingKey, stringToSign.getBytes("UTF-8"));
    }

    private byte[] getSigningKey(Date date, String region) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        if(this.accessKey != null && this.secretKey != null) {
            return generateSigningKey(date, region, this.secretKey);
        }
        return this.userProvidedSigningKey;
    }

    private static byte[] generateSigningKey(Date date, String region, String secretKey) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        String formattedDate = dateFormatyyyyMMdd.format(date);
        String dateKeyLine = "AWS4" + secretKey;
        byte[] dateKey = signHmac(dateKeyLine.getBytes("UTF-8"), formattedDate.getBytes("UTF-8"));
        byte[] dateRegionKey = signHmac(dateKey, region.getBytes("UTF-8"));
        byte[] dateRegionServiceKey = signHmac(dateRegionKey, "s3".getBytes("UTF-8"));
        return signHmac(dateRegionServiceKey, "aws4_request".getBytes("UTF-8"));
    }

    private static byte[] signHmac(byte[] curKey, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
        SecretKeySpec key = new SecretKeySpec(curKey, "HmacSHA256");
        Mac hmacSha256 = Mac.getInstance("HmacSHA256");
        hmacSha256.init(key);
        hmacSha256.update(data);
        return hmacSha256.doFinal();
    }

    private String getScope(String region, Date date) {
        String formattedDate = dateFormatyyyyMMdd.format(date);
        return formattedDate + "/" + region + "/" + "s3" + "/" + "aws4_request";
    }

    private String getStringToSign(String region, String canonicalHash, Date date) {
        return "AWS4-HMAC-SHA256" + "\n" +
                dateFormatyyyyMMddThhmmssZ.format(date) + "\n" +
                getScope(region, date) + "\n" + canonicalHash;
    }

    private Tuple2<String, String> getCanonicalRequest(HttpRequest request, String bodySha256Hash) {
        StringWriter canonicalWriter = new StringWriter();
        PrintWriter canonicalPrinter = new PrintWriter(canonicalWriter, true);

        String method = request.getRequestMethod();
        String path = request.getUrl().getRawPath();
        String rawQuery = request.getUrl().toURI().getQuery();
{'fraction_non_alphanumeric': 0.1124923076923077, 'fraction_numerical': 0.008861538461538462, 'mean_word_length': 3.5444630872483223, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1462062', 'n_tokens_mistral': 4498, 'n_tokens_neox': 4315, 'n_words': 1006}