Skip to content

Commit

Permalink
Fixes handnot2#2
Browse files Browse the repository at this point in the history
  • Loading branch information
handnot2 committed Apr 9, 2017
1 parent a20a188 commit af55c6f
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 4 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Changelog

### v0.2.0

Enhancements:

- Support precomputed content hash (issue #2)

Fixes:

- Correct "cryto" to "crypto" (issue #1)
3 changes: 2 additions & 1 deletion lib/sigaws.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Sigaws do
| `:method` | A string value -- `GET`, `POST`, `PUT`, etc (defaults to `GET`) |
| `:params`<br/>&nbsp; | A map of query parameters -- merged with the query string in the given url (defaults to an empty map) |
| `:headers` | A map of request headers (defaults to an empty map) |
| `:body`<br/>&nbsp; | A string value -- use proper encoder (json or urlencoder) to convert to binary (defaults to an empty string) |
| `:body`<br/>&nbsp; | A string value (use appropriate encoder) or `:unsigned` or `{:content_hash, hash}` (defaults to an empty string) |
| `:signed_at`<br/>&nbsp; | `DateTime` in UTC or a string in the form `YYYMMDDTHHmmSSZ` (defults to current time in UTC) |
| `:expires_in` | Optional expiration in seconds since the signing time |
| `:region` | A string value |
Expand Down Expand Up @@ -265,6 +265,7 @@ defmodule Sigaws do

@body_error {:error, :invalid_input, "body"}
defp body_opt(%{body: :unsigned}), do: {:ok, :unsigned}
defp body_opt(%{body: {:content_hash, hash}}), do: {:ok, {:content_hash, hash}}
defp body_opt(%{body: b}) when is_binary(b), do: {:ok, b}
defp body_opt(%{body: _}), do: @body_error
defp body_opt(_), do: {:ok, ""}
Expand Down
1 change: 1 addition & 0 deletions lib/sigaws/signer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,6 @@ defmodule Sigaws.Signer do
defp payload_hash(nil), do: @empty_str_hash
defp payload_hash(""), do: @empty_str_hash
defp payload_hash(:unsigned), do: "UNSIGNED-PAYLOAD"
defp payload_hash({:content_hash, hash}), do: hash
defp payload_hash(payload), do: payload |> Util.hexdigest()
end
13 changes: 11 additions & 2 deletions lib/sigaws/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,23 @@ defmodule Sigaws.Util do
@hash_alg |> HMAC.init(key) |> HMAC.update(data) |> HMAC.compute()
end

@doc false
def hexdigest(data) do
@doc """
Calculate lower case hex encoded SHA256 digest/hash of the given binary or stream.
"""
@spec hexdigest(binary | Enumerable.t) :: binary
def hexdigest(data) when is_binary(data) do
@hash_alg
|> Hash.init()
|> Hash.update(data)
|> Hash.compute()
|> Base.encode16(case: :lower)
end
def hexdigest(enumerable) do
enumerable
|> Enum.reduce(Hash.init(@hash_alg), &(Hash.update(&2, &1)))
|> Hash.compute()
|> Base.encode16(case: :lower)
end

@spec expired?({:ok, DateTime.t} | {:error, atom, binary}, integer) ::
:ok | {:error, atom, binary}
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Sigaws.Mixfile do
use Mix.Project

@version "0.1.0"
@version "0.2.0"
@description """
An Elixir library to sign and verify HTTP requests using AWS Signature V4.
"""
Expand Down
74 changes: 74 additions & 0 deletions test/sigaws_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -531,4 +531,78 @@ defmodule SigawsTest do
provider: VerificationProvider
)
end

test "PUT with signed body verified using content hash" do
body = "signed content"
opts = [method: "PUT", region: "us-east-1", service: "d3", access_key: "ak1", secret: "sk1", body: body]
assert {:ok, sig_data, _info} = Sigaws.sign_req("http://localhost/", opts)
assert_all_sig_headers(sig_data)

assert {:ok, _} = Sigaws.verify("http://localhost/",
method: "PUT",
body: {:content_hash, Map.get(sig_data, "X-Amz-Content-Sha256")},
headers: sig_data,
provider: VerificationProvider
)
end

test "PUT using content hash for signing" do
body = "signed content"
hash = Sigaws.Util.hexdigest(body)
opts = [method: "PUT", region: "us-east-1", service: "d3", access_key: "ak1", secret: "sk1", body: {:content_hash, hash}]
assert {:ok, sig_data, _info} = Sigaws.sign_req("http://localhost/", opts)
assert_all_sig_headers(sig_data)

assert {:ok, _} = Sigaws.verify("http://localhost/",
method: "PUT",
body: body,
headers: sig_data,
provider: VerificationProvider
)
end

test "PUT using content hash for signing - tampering" do
body = "signed content"
hash = Sigaws.Util.hexdigest(body)
opts = [method: "PUT", region: "us-east-1", service: "d3", access_key: "ak1", secret: "sk1", body: {:content_hash, hash}]
assert {:ok, sig_data, _info} = Sigaws.sign_req("http://localhost/", opts)
assert_all_sig_headers(sig_data)

assert {:error, _, _} = Sigaws.verify("http://localhost/",
method: "PUT",
body: body <> "tampered",
headers: sig_data,
provider: VerificationProvider
)
end

test "PUT using content hash for signing - file" do
file = "test/sigaws_test.exs"
hash = File.stream!(file) |> Sigaws.Util.hexdigest()
opts = [method: "PUT", region: "us-east-1", service: "d3", access_key: "ak1", secret: "sk1", body: {:content_hash, hash}]
assert {:ok, sig_data, _info} = Sigaws.sign_req("http://localhost/", opts)
assert_all_sig_headers(sig_data)

assert {:ok, _} = Sigaws.verify("http://localhost/",
method: "PUT",
body: File.read!(file),
headers: sig_data,
provider: VerificationProvider
)
end

test "PUT using content hash for signing and verification - file" do
file = "test/sigaws_test.exs"
hash = File.stream!(file) |> Sigaws.Util.hexdigest()
opts = [method: "PUT", region: "us-east-1", service: "d3", access_key: "ak1", secret: "sk1", body: {:content_hash, hash}]
assert {:ok, sig_data, _info} = Sigaws.sign_req("http://localhost/", opts)
assert_all_sig_headers(sig_data)

assert {:ok, _} = Sigaws.verify("http://localhost/",
method: "PUT",
body: {:content_hash, hash},
headers: sig_data,
provider: VerificationProvider
)
end
end

0 comments on commit af55c6f

Please sign in to comment.