Appendix C - Sample Code

Python

# Copyright 2014 Amazon.com, Inc. or its affiliates. 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.
# A copy of the License is located at
#
#  http://aws.amazon.com/apache2.0
#
# or in the "license" file accompanying this file. This file 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.

# Task 1: get message body checksum
import hashlib, hmac, urllib

# If sending a GET/DELETE: message_body should be empty
# If sending a POST: message_body should be the contents of your file or the request data
message_body = ''
body_checksum = ''
# If message body is empty, simply return empty string as checksum!
if message_body:
    body_checksum = hashlib.sha256(message_body).hexdigest()

# Task 2 Gather signature elements!
from datetime import datetime

# first element
request_type = 'GET'

# second element
uri = u'https://file-api.fillz.com/v1/orders/created/?acknowledged=false'
encoded_uri = urllib.quote(uri.lower(), ':/')

# third element
timestamp = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')

# fourth element is the body_checksum

# Task 3 create the final string
final_string = '\n'.join([request_type, encoded_uri, timestamp, body_checksum])

# Task 4 generate the request signature
secret_key = 'EXAMPLESECRETKEY'
signature = hmac.new(key=secret_key, msg=final_string, digestmod=hashlib.sha256).hexdigest()

# Task 5 submit the request

# you can use any library you like
import requests
header_dictionary = {
    'X-FillZ-Date': timestamp,
    'X-FillZ-Access-Key': 'EXAMPLEACCESSKEY',
    'X-FillZ-Signature': signature
}

response = requests.get(uri, data=message_body, headers=header_dictionary)

Java

/*
* Copyright 2014 Amazon.com, Inc. or its affiliates. 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.
* A copy of the License is located at
*
*  http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

import org.apache.http.client.methods.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;


public class SigningTutorial
{
    public static final String HMAC_SHA256 = "HmacSHA256";
    public static final String ISO8601_BASIC_FORMAT = "yyyyMMdd'T'HHmmss'Z'";

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException
    {
        String secretKey = "EXAMPLESECRETKEY";

        // Task 1: get message body checksum
        // If sending a GET/DELETE: messageBody should be empty
        // If sending a POST: messageBody should be the contents of your file or the request data
        String messageBody = "";
        String bodyChecksum = "";
        if (messageBody != null && messageBody != "") {
            bodyChecksum = SigningTutorial.getChecksum(messageBody);
        }

        // Task 2: Gather signature elements

        // First element
        String method = "GET";
        // Second element
        String uri = "https://file-api.fillz.com/v1/orders/created/?acknowledged=false";
        String encodedURI = SigningTutorial.encodeURIComponent(uri);
        // Third element
        String dateTime = SigningTutorial.getCurrentUTCTime();
        // Fourth element is the body checksum

        // Task 3: create the final string
        String finalString = method + "\n" + encodedURI + "\n" + dateTime + "\n" + bodyChecksum;

        // Task 4: generate the request signature
        String signature = SigningTutorial.getHMAC(secretKey, finalString);

        // Task 5: submit the request:

        // We used apache's http client as an example, you don't have to;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpUriRequest uriRequest = null;
        if ("GET".equalsIgnoreCase(method)) {
            uriRequest = new HttpGet(uri);
        }
        else {
            uriRequest = new HttpPost(uri);
            ContentType tabType = ContentType.create("application/tab-delimited");
            ((HttpPost)uriRequest).setEntity(new StringEntity(messageBody, tabType));
        }

        // Set headers
        uriRequest.setHeader("X-FillZ-Date", dateTime);
        uriRequest.setHeader("X-FillZ-Access-Key", "EXAMPLEACCESSKEY");
        uriRequest.setHeader("X-FillZ-Signature", signature);

        // Execute the method
        CloseableHttpResponse response = httpClient.execute(uriRequest);

        try {
            // do something useful with the response and ensure it is fully consumed
            System.out.println(response.getStatusLine());
            HttpEntity entity1 = response.getEntity();
            System.out.println(EntityUtils.toString(entity1));
            EntityUtils.consume(entity1);
        } finally {
            response.close();
        }
    }

    public static String encodeURIComponent(String input) throws UnsupportedEncodingException
    {
        return URLEncoder.encode(input.toLowerCase(), "UTF-8")
                .replaceAll("\\*", "%2A")
                .replaceAll("\\%7E", "~")
                .replaceAll("\\+", "%20")
                .replaceAll("\\%3A", ":")
                .replaceAll("\\%2F", "/");
    }

    public static String getCurrentUTCTime()
    {
        TimeZone timeZone = TimeZone.getTimeZone("UTC");
        DateFormat format = new SimpleDateFormat(SigningTutorial.ISO8601_BASIC_FORMAT);
        format.setTimeZone(timeZone);
        return format.format(new Date());
    }

    public static String getChecksum(String message) throws NoSuchAlgorithmException
    {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(message.getBytes(StandardCharsets.UTF_8));
        String checksum = Hex.encodeHexString(md.digest());
        return checksum;
    }

    public static String getHMAC(String secretKey, String message) throws NoSuchAlgorithmException, InvalidKeyException
    {
        Mac sha256_HMAC = Mac.getInstance(HMAC_SHA256);
        SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), HMAC_SHA256);
        sha256_HMAC.init(secret_key);

        String hash = Hex.encodeHexString(sha256_HMAC.doFinal(message.getBytes()));
        return hash;
    }
}

C#

/*
 * Copyright 2014 Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Globalization;
using System.Security.Cryptography;

namespace FileAPI
{
    public class SigningTutorial
    {
        /// <summary>
        /// The set of accepted and valid Url characters per RFC3986.
        /// </summary>
        private const string ValidUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

        /// <summary>
        /// The set of accepted and valid Url path characters per RFC3986.
        /// </summary>
        private static string ValidPathCharacters = DetermineValidPathCharacters();

        static public void Main()
        {
            // Task 1: get message body checksum
            // If sending a GET/DELETE: messageBody should be empty
            // If sending a POST: messageBody should be the contents of your file or the request data
            string messageBody = "";
            string bodyChecksum = "";
            if (!string.IsNullOrEmpty(messageBody))
            {
                bodyChecksum = SigningTutorial.GetChecksum(messageBody);
            }

            // Task 2: Gather signature elements
            // First element
            string method = "GET";
            // Second element
            string uri = "https://file-api.fillz.com/v1/orders/created/?acknowledged=false";

            string encodedURI = SigningTutorial.UrlEncode(uri.ToLower());

            // Third element
            string timeStamp = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ");

            // Fourth element is the body checksum

            // Task 3: create the final string
            string finalString = method + "\n" + encodedURI + "\n" + timeStamp + "\n" + bodyChecksum;

            // Task 4: generate the request signature
            string secretKey = "EXAMPLESECRETKEY";
            string signature = SigningTutorial.GetHMAC(secretKey, finalString);

            // Task 5: submit the request
            WebRequest request = WebRequest.Create(uri);
            request.Method = method;

            // Set the headers
            string accessKey = "EXAMPLEACCESSKEY";
            request.Headers.Add("X-FillZ-Date", timeStamp);
            request.Headers.Add("X-FillZ-Access-Key", accessKey);
            request.Headers.Add("X-FillZ-Signature", signature);

            // Set the message body for a POST only.
            // GET/DELETE methods do not require a request body and must not have the ContentLength or ContentType set
            if ("POST".Equals(method) && !string.IsNullOrEmpty(messageBody))
            {
                byte[] messageBodyBytes = Encoding.UTF8.GetBytes(messageBody);

                // Set the content type of the data being posted.
                request.ContentType = "application/tab-delimited";

                // Set the content length of the string being posted.
                request.ContentLength = messageBodyBytes.Length;

                Stream newStream = request.GetRequestStream();
                newStream.Write(messageBodyBytes, 0, messageBodyBytes.Length);
            }

            // Execute the method
            string result = null;
            using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
            {
                StreamReader reader = new StreamReader(response.GetResponseStream());
                result = reader.ReadToEnd();
                Console.WriteLine(result);
                // other handling...
            }
        }

        private static string GetChecksum(string message)
        {
            var encoding = new System.Text.ASCIIEncoding();
            byte[] messageBytes = encoding.GetBytes(message);
            using (var sha256 = new SHA256Managed())
            {
                byte[] hashbytes = sha256.ComputeHash(messageBytes);
                string hexhash = BitConverter.ToString(hashbytes).Replace("-", "").ToLower();
                return hexhash;
            }

        }

        private static string GetHMAC(string secret, string message)
        {
            var encoding = new System.Text.ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                string hexhash = BitConverter.ToString(hashmessage).Replace("-", "").ToLower();
                return hexhash;
            }
        }

        private static string UrlEncode(string url)
        {
            StringBuilder encoded = new StringBuilder(url.Length * 2);
            string unreservedChars = String.Concat(ValidUrlCharacters, ValidPathCharacters);

            foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(url))
            {
                if (unreservedChars.IndexOf(symbol) != -1)
                {
                    encoded.Append(symbol);
                }
                else
                {
                    encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol));
                }
            }

            return encoded.ToString();
        }

        // Checks which path characters should not be encoded
        // This set will be different for .NET 4 and .NET 4.5, as
        // per http://msdn.microsoft.com/en-us/library/hh367887%28v=vs.110%29.aspx
        private static string DetermineValidPathCharacters()
        {
            const string basePathCharacters = "/:'()!*[]";

            var sb = new StringBuilder();
            foreach (var c in basePathCharacters)
            {
                var escaped = Uri.EscapeUriString(c.ToString());
                if (escaped.Length == 1 && escaped[0] == c)
                    sb.Append(c);
            }
            return sb.ToString();
        }
    }

}