Last active
April 26, 2016 02:03
-
-
Save conorh/d135ae05da757a4c369953d744e99d97 to your computer and use it in GitHub Desktop.
Calculate AWS v4 signatures
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "uri" | |
require "time" | |
require "openssl" | |
# Calculate v4 AWS sig | |
class AWSV4Sig | |
def initialize(access_key, secret_key) | |
@access_key = access_key | |
@secret_key = secret_key | |
end | |
def auth_header(method, uri, headers, request_payload, opts = {}) | |
@uri = uri.kind_of?(URI) ? uri : URI.parse(uri) | |
@headers = headers | |
@method = method | |
@request_payload = request_payload | |
@date = headers["x-amz-date"] || headers["Date"] | |
@short_date = Time.parse(@date).strftime("%Y%m%d") | |
split_host = @uri.host.split(".") | |
@region = opts[:region] || split_host[2] | |
@service = opts[:service] || split_host[1] | |
auth_header = "AWS4-HMAC-SHA256 Credential=#{@access_key}/#{credentials}, SignedHeaders=#{signed_headers}, Signature=#{signature}" | |
end | |
def string_to_sign | |
"AWS4-HMAC-SHA256\n#{@date}\n#{credentials}\n#{digest(canonical_request)}" | |
end | |
def credentials | |
"#{@short_date}/#{@region}/#{@service}/aws4_request" | |
end | |
def signature | |
kSecret = @secret_key | |
kDate = hmac("AWS4" + kSecret, @short_date) | |
kRegion = hmac(kDate, @region) | |
kService = hmac(kRegion, @service) | |
kSigning = hmac(kService, "aws4_request") | |
hexdigest_hmac(kSigning, string_to_sign) | |
end | |
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html | |
def canonical_request | |
[ | |
@method, | |
canonical_uri, | |
normalized_query_string, | |
canonical_headers, | |
signed_headers, | |
digest(@request_payload || '') | |
].join("\n") | |
end | |
def normalized_query_string | |
if @uri.query | |
@uri.query.split('&').map {|v| [v.split("=")[0], v] }.sort_by {|v| v[0] }.map {|v| [v[1]]}.join("&") | |
else | |
"" | |
end | |
end | |
def canonical_headers | |
@headers.sort_by {|k,v| k.downcase} .map do |k, v| | |
[k.downcase.strip, v.to_s.strip.gsub(/\s+/,' ')].join(":") | |
end.join("\n") + "\n" | |
end | |
def signed_headers | |
# We sign all the headers, so the set of signed headers is all of them :) | |
@headers.sort_by {|k,v| k.downcase }.map {|k, v| k.downcase}.join(";") | |
end | |
def canonical_uri | |
path = @uri.path | |
path = "/" if path.nil? || path == "" | |
path | |
end | |
def digest(value) | |
Digest::SHA256.new.update(value).hexdigest | |
end | |
def hmac(key, value) | |
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value) | |
end | |
def hexdigest_hmac(key, value) | |
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value) | |
end | |
end | |
# Sample | |
req = AWSV4Sig.new("AKIDEXAMPLE","wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY") | |
headers = { | |
"Host" => "example.amazonaws.com", | |
"X-Amz-Date" => "20150830T123600Z" | |
} | |
# From a sample in AWS docs - http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html | |
puts req.auth_header("GET", "http://example.amazonaws.com/?Param2=value2&Param1=value1", headers, nil, region: "us-east-1", service: "service") | |
puts "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample usage for an upload to s3