Skip to content

Instantly share code, notes, and snippets.

@ericmj
Created September 22, 2015 16:55
Show Gist options
  • Save ericmj/aa01edec5297b105b180 to your computer and use it in GitHub Desktop.
Save ericmj/aa01edec5297b105b180 to your computer and use it in GitHub Desktop.
defmodule AWS do
defmodule Credentials do
defstruct [:access_key, :secret_key, :bucket, :region, :protocol]
end
def auth_s3(path, expires, credentials, datetime \\ :calendar.universal_time) do
request = canonical_request(path, expires, datetime, credentials)
string_to_sign = string_to_sign(request, datetime, credentials)
signing_key = signing_key(datetime, credentials)
signature = signature(signing_key, string_to_sign)
url(path, expires, datetime, signature, credentials)
end
def canonical_request(path, expires, datetime, cred) do
"""
GET
/#{URI.encode(path)}
#{params(expires, datetime, cred)}
host:#{cred.bucket}.s3.amazonaws.com
host
UNSIGNED-PAYLOAD
"""
|> String.rstrip
end
def string_to_sign(request, datetime, cred) do
request = :crypto.hash(:sha256, request)
|> Base.encode16(case: :lower)
"""
AWS4-HMAC-SHA256
#{datetime(datetime)}
#{scope(datetime, cred)}
#{request}
"""
|> String.rstrip
end
def signing_key(datetime, cred) do
"AWS4" <> cred.secret_key
|> hmac(date(datetime))
|> hmac(cred.region)
|> hmac("s3")
|> hmac("aws4_request")
end
def signature(signing_key, string_to_sign) do
hmac(signing_key, string_to_sign)
|> Base.encode16(case: :lower)
end
def url(path, expires, datetime, signature, cred) do
"#{cred.protocol}://#{cred.bucket}.s3.amazonaws.com/#{URI.encode(path)}?"
<> "#{params(expires, datetime, cred)}&X-Amz-Signature=#{signature}"
end
defp params(expires, datetime, cred) do
"X-Amz-Algorithm=AWS4-HMAC-SHA256&"
<> "X-Amz-Credential=#{uri_encode(credentials(datetime, cred))}&"
<> "X-Amz-Date=#{datetime(datetime)}&"
<> "X-Amz-Expires=#{expires}&"
<> "X-Amz-SignedHeaders=host"
end
defp hmac(key, data) do
:crypto.hmac(:sha256, key, data)
end
defp credentials(datetime, cred) do
"#{cred.access_key}/#{scope(datetime, cred)}"
end
defp scope(datetime, cred) do
"#{date(datetime)}/#{cred.region}/s3/aws4_request"
end
defp uri_encode(str) do
URI.encode(str, &URI.char_unreserved?/1)
end
defp date({{year, month, day}, _time}) do
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
end
defp datetime({{year, month, day}, {hour, min, sec}}) do
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2) <> "T" <>
zero_pad(hour, 2) <> zero_pad(min, 2) <> zero_pad(sec, 2) <> "Z"
end
defp zero_pad(val, count) do
num = Integer.to_string(val)
:binary.copy("0", count - byte_size(num)) <> num
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment