http_signatures 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -8
- data/lib/http_signatures/context.rb +4 -0
- data/lib/http_signatures/header_list.rb +1 -1
- data/lib/http_signatures/signature.rb +25 -0
- data/lib/http_signatures/signature_parameters.rb +1 -1
- data/lib/http_signatures/signature_parameters_parser.rb +35 -0
- data/lib/http_signatures/signer.rb +10 -21
- data/lib/http_signatures/signing_string.rb +1 -1
- data/lib/http_signatures/verification.rb +53 -0
- data/lib/http_signatures/verifier.rb +13 -0
- data/lib/http_signatures/version.rb +1 -1
- data/lib/http_signatures.rb +4 -0
- data/spec/context_spec.rb +7 -1
- data/spec/header_list_spec.rb +2 -2
- data/spec/signature_parameters_parser_spec.rb +24 -0
- data/spec/signature_parameters_spec.rb +25 -0
- data/spec/signer_spec.rb +2 -5
- data/spec/signing_string_spec.rb +3 -3
- data/spec/verifier_spec.rb +42 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7081d201a0f52168e49d42043f5a2dfd792b1e6
|
4
|
+
data.tar.gz: 51fd29fdd9c4c20f09d4fbcfb2bf012ed36739bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bac9b3f9a8b8e2f5583c5d4e02fada9053c516a0df7fce6909c065c064c2489ea3662fb11eb3a6fc827f7c2a9f5386ec4ce0a03b915ca76bfce55a5c09aad47b
|
7
|
+
data.tar.gz: 0ce9bee030d5f433ed3391b420be018b59983b01c5d2ba384ba2f60ce966662b3a4533726da02927ae13422cacd58e4b9c8b0edfa894e4cb627690494716b885
|
data/README.md
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
Ruby implementation of [HTTP Signatures][draft03] draft specification;
|
4
4
|
cryptographically sign and verify HTTP requests and responses.
|
5
5
|
|
6
|
+
See also:
|
7
|
+
|
8
|
+
* https://github.com/99designs/http-signatures-php
|
9
|
+
|
6
10
|
|
7
11
|
## Usage
|
8
12
|
|
@@ -58,14 +62,9 @@ message["Authorization"]
|
|
58
62
|
|
59
63
|
### Verifying a signed message
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
from the `Signature` and/or `Authorization` header.
|
65
|
-
* The signing string will be derived by selecting the listed headers from the
|
66
|
-
message.
|
67
|
-
* The valid signature will be derived by applying the algorithm and secret key.
|
68
|
-
* The message is valid if the provided signature matches the valid signature.
|
65
|
+
```rb
|
66
|
+
$context.verifier.valid?(message) # => true or false
|
67
|
+
```
|
69
68
|
|
70
69
|
|
71
70
|
## Contributing
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module HttpSignatures
|
2
|
+
class Signature
|
3
|
+
|
4
|
+
def initialize(message:, key:, algorithm:, header_list:)
|
5
|
+
@message = message
|
6
|
+
@key = key
|
7
|
+
@algorithm = algorithm
|
8
|
+
@header_list = header_list
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_str
|
12
|
+
@algorithm.sign(@key.secret, signing_string)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def signing_string
|
18
|
+
SigningString.new(
|
19
|
+
header_list: @header_list,
|
20
|
+
message: @message,
|
21
|
+
).to_str
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module HttpSignatures
|
2
|
+
class SignatureParametersParser
|
3
|
+
|
4
|
+
def initialize(string)
|
5
|
+
@string = string
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
Hash[array_of_pairs]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def array_of_pairs
|
15
|
+
segments.map { |segment| pair(segment) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def segments
|
19
|
+
@string.split(",")
|
20
|
+
end
|
21
|
+
|
22
|
+
def pair(segment)
|
23
|
+
match = segment_pattern.match(segment)
|
24
|
+
raise Error if match.nil?
|
25
|
+
match.captures
|
26
|
+
end
|
27
|
+
|
28
|
+
def segment_pattern
|
29
|
+
%r{\A(keyId|algorithm|headers|signature)="(.*)"\z}
|
30
|
+
end
|
31
|
+
|
32
|
+
class Error < StandardError; end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -11,40 +11,29 @@ module HttpSignatures
|
|
11
11
|
|
12
12
|
def sign(message)
|
13
13
|
message.tap do |m|
|
14
|
-
|
15
|
-
m["
|
16
|
-
m["Authorization"] = [AUTHORIZATION_SCHEME + " " + signature]
|
14
|
+
m["Signature"] = [signature_parameters(message).to_str]
|
15
|
+
m["Authorization"] = [AUTHORIZATION_SCHEME + " " + signature_parameters(message).to_str]
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
19
|
private
|
21
20
|
|
22
|
-
def
|
21
|
+
def signature_parameters(message)
|
23
22
|
SignatureParameters.new(
|
24
23
|
key: @key,
|
25
24
|
algorithm: @algorithm,
|
26
25
|
header_list: @header_list,
|
27
|
-
signature:
|
26
|
+
signature: signature(message).to_str,
|
28
27
|
)
|
29
28
|
end
|
30
29
|
|
31
|
-
def
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def signing_string_for_message(message)
|
36
|
-
SigningString.new(
|
37
|
-
header_list: @header_list,
|
30
|
+
def signature(message)
|
31
|
+
Signature.new(
|
38
32
|
message: message,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
class MessageMissingHeader < StandardError
|
45
|
-
def initialize(name)
|
46
|
-
super("Message missing header '#{name}'")
|
47
|
-
end
|
33
|
+
key: @key,
|
34
|
+
algorithm: @algorithm,
|
35
|
+
header_list: @header_list,
|
36
|
+
)
|
48
37
|
end
|
49
38
|
|
50
39
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module HttpSignatures
|
2
|
+
class Verification
|
3
|
+
|
4
|
+
def initialize(message:, key_store:)
|
5
|
+
@message = message
|
6
|
+
@key_store = key_store
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
expected_signature_base64 == provided_signature_base64
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def expected_signature_base64
|
16
|
+
Base64.strict_encode64(expected_signature_raw)
|
17
|
+
end
|
18
|
+
|
19
|
+
def expected_signature_raw
|
20
|
+
Signature.new(
|
21
|
+
message: @message,
|
22
|
+
key: key,
|
23
|
+
algorithm: algorithm,
|
24
|
+
header_list: header_list,
|
25
|
+
).to_str
|
26
|
+
end
|
27
|
+
|
28
|
+
def provided_signature_base64
|
29
|
+
parsed_parameters.fetch("signature")
|
30
|
+
end
|
31
|
+
|
32
|
+
def key
|
33
|
+
@key_store.fetch(parsed_parameters["keyId"])
|
34
|
+
end
|
35
|
+
|
36
|
+
def algorithm
|
37
|
+
Algorithm.create(parsed_parameters["algorithm"])
|
38
|
+
end
|
39
|
+
|
40
|
+
def header_list
|
41
|
+
HeaderList.from_string(parsed_parameters["headers"])
|
42
|
+
end
|
43
|
+
|
44
|
+
def parsed_parameters
|
45
|
+
@_parsed_parameters ||= SignatureParametersParser.new(fetch_header("Signature")).parse
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_header(name)
|
49
|
+
@message.fetch(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/http_signatures.rb
CHANGED
@@ -5,9 +5,13 @@ require "http_signatures/context"
|
|
5
5
|
require "http_signatures/header_list"
|
6
6
|
require "http_signatures/key"
|
7
7
|
require "http_signatures/key_store"
|
8
|
+
require "http_signatures/signature"
|
8
9
|
require "http_signatures/signature_parameters"
|
10
|
+
require "http_signatures/signature_parameters_parser"
|
9
11
|
require "http_signatures/signer"
|
10
12
|
require "http_signatures/signing_string"
|
13
|
+
require "http_signatures/verification"
|
14
|
+
require "http_signatures/verifier"
|
11
15
|
require "http_signatures/version"
|
12
16
|
|
13
17
|
module HttpSignatures
|
data/spec/context_spec.rb
CHANGED
@@ -24,7 +24,13 @@ RSpec.describe HttpSignatures::Context do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it "signs without errors" do
|
27
|
-
context.signer.sign(message)
|
27
|
+
expect { context.signer.sign(message) }.to_not raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it "verifies without errors" do
|
31
|
+
signature_parameters = 'keyId="hello",algorithm="hmac-sha1",headers="date",signature="x"'
|
32
|
+
message = Net::HTTP::Get.new("/", "Date" => "x", "Signature" => signature_parameters)
|
33
|
+
expect { context.verifier.valid?(message) }.to_not raise_error
|
28
34
|
end
|
29
35
|
end
|
30
36
|
end
|
data/spec/header_list_spec.rb
CHANGED
@@ -26,10 +26,10 @@ RSpec.describe HttpSignatures::HeaderList do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
describe "#
|
29
|
+
describe "#to_str" do
|
30
30
|
it "joins normalized header names with spaces" do
|
31
31
|
list = HttpSignatures::HeaderList.new(["(request-target)", "Date", "Content-Type"])
|
32
|
-
expect(list.
|
32
|
+
expect(list.to_str).to eq("(request-target) date content-type")
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec.describe HttpSignatures::SignatureParametersParser do
|
2
|
+
|
3
|
+
subject(:parser) do
|
4
|
+
HttpSignatures::SignatureParametersParser.new(input)
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:input) do
|
8
|
+
'keyId="example",algorithm="hmac-sha1",headers="(request-target) date",signature="b64"'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#parse" do
|
12
|
+
it "returns hash with string keys matching those in the parsed string" do
|
13
|
+
expect(parser.parse).to eq(
|
14
|
+
{
|
15
|
+
"keyId" => "example",
|
16
|
+
"algorithm" => "hmac-sha1",
|
17
|
+
"headers" => "(request-target) date",
|
18
|
+
"signature" => "b64",
|
19
|
+
}
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.describe HttpSignatures::SignatureParameters do
|
2
|
+
|
3
|
+
subject(:signature_parameters) do
|
4
|
+
HttpSignatures::SignatureParameters.new(
|
5
|
+
key: key,
|
6
|
+
algorithm: algorithm,
|
7
|
+
header_list: header_list,
|
8
|
+
signature: signature,
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:key) { double("Key", id: "pda") }
|
13
|
+
let(:algorithm) { double("Algorithm", name: "hmac-test") }
|
14
|
+
let(:header_list) { double("HeaderList", to_str: "a b c") }
|
15
|
+
let(:signature) { "sigstring" }
|
16
|
+
|
17
|
+
describe "#to_str" do
|
18
|
+
it "builds parameters into string" do
|
19
|
+
expect(signature_parameters.to_str).to eq(
|
20
|
+
'keyId="pda",algorithm="hmac-test",headers="a b c",signature="c2lnc3RyaW5n"'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/spec/signer_spec.rb
CHANGED
@@ -48,11 +48,8 @@ RSpec.describe HttpSignatures::Signer do
|
|
48
48
|
it "passes correct signing string to algorithm" do
|
49
49
|
expect(algorithm).to receive(:sign).with(
|
50
50
|
"sh",
|
51
|
-
[
|
52
|
-
|
53
|
-
"content-type: text/plain",
|
54
|
-
].join("\n")
|
55
|
-
).and_return("null")
|
51
|
+
["date: #{EXAMPLE_DATE}", "content-type: text/plain"].join("\n")
|
52
|
+
).at_least(:once).and_return("null")
|
56
53
|
signer.sign(message)
|
57
54
|
end
|
58
55
|
it "returns reference to the mutated input" do
|
data/spec/signing_string_spec.rb
CHANGED
@@ -19,10 +19,10 @@ RSpec.describe HttpSignatures::SigningString do
|
|
19
19
|
Net::HTTP::Get.new("/path?query=123", "date" => DATE, "x-herring" => "red")
|
20
20
|
end
|
21
21
|
|
22
|
-
describe "#
|
22
|
+
describe "#to_str" do
|
23
23
|
|
24
24
|
it "returns correct signing string" do
|
25
|
-
expect(signing_string.
|
25
|
+
expect(signing_string.to_str).to eq(
|
26
26
|
"(request-target): get /path?query=123\n" +
|
27
27
|
"date: #{DATE}"
|
28
28
|
)
|
@@ -32,7 +32,7 @@ RSpec.describe HttpSignatures::SigningString do
|
|
32
32
|
let(:header_list) { HttpSignatures::HeaderList.from_string("nope") }
|
33
33
|
it "raises HeaderNotInMessage" do
|
34
34
|
expect {
|
35
|
-
signing_string.
|
35
|
+
signing_string.to_str
|
36
36
|
}.to raise_error(HttpSignatures::HeaderNotInMessage)
|
37
37
|
end
|
38
38
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
RSpec.describe HttpSignatures::Verifier do
|
5
|
+
|
6
|
+
DATE = "Fri, 01 Aug 2014 13:44:32 -0700"
|
7
|
+
DATE_DIFFERENT = "Fri, 01 Aug 2014 13:44:33 -0700"
|
8
|
+
|
9
|
+
subject(:verifier) { HttpSignatures::Verifier.new(key_store: key_store) }
|
10
|
+
let(:key_store) { HttpSignatures::KeyStore.new("pda" => "secret") }
|
11
|
+
let(:message) { Net::HTTP::Get.new("/path?query=123", headers) }
|
12
|
+
let(:headers) { {"Date" => DATE, "Signature" => signature_header} }
|
13
|
+
|
14
|
+
let(:signature_header) do
|
15
|
+
'keyId="%s",algorithm="%s",headers="%s",signature="%s"' % [
|
16
|
+
"pda",
|
17
|
+
"hmac-sha256",
|
18
|
+
"(request-target) date",
|
19
|
+
"cS2VvndvReuTLy52Ggi4j6UaDqGm9hMb4z0xJZ6adqU=",
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "verifies a valid message" do
|
24
|
+
expect(verifier.valid?(message)).to eq(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "rejects message with tampered path" do
|
28
|
+
message.path << "x"
|
29
|
+
expect(verifier.valid?(message)).to eq(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "rejects message with tampered date" do
|
33
|
+
message["Date"] = DATE_DIFFERENT
|
34
|
+
expect(verifier.valid?(message)).to eq(false)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "rejects message with tampered signature" do
|
38
|
+
message["Signature"] = message["Signature"].sub('signature="', 'signature="x')
|
39
|
+
expect(verifier.valid?(message)).to eq(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_signatures
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Annesley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,17 +74,24 @@ files:
|
|
74
74
|
- lib/http_signatures/header_list.rb
|
75
75
|
- lib/http_signatures/key.rb
|
76
76
|
- lib/http_signatures/key_store.rb
|
77
|
+
- lib/http_signatures/signature.rb
|
77
78
|
- lib/http_signatures/signature_parameters.rb
|
79
|
+
- lib/http_signatures/signature_parameters_parser.rb
|
78
80
|
- lib/http_signatures/signer.rb
|
79
81
|
- lib/http_signatures/signing_string.rb
|
82
|
+
- lib/http_signatures/verification.rb
|
83
|
+
- lib/http_signatures/verifier.rb
|
80
84
|
- lib/http_signatures/version.rb
|
81
85
|
- spec/algorithm_spec.rb
|
82
86
|
- spec/context_spec.rb
|
83
87
|
- spec/header_list_spec.rb
|
84
88
|
- spec/key_store_spec.rb
|
89
|
+
- spec/signature_parameters_parser_spec.rb
|
90
|
+
- spec/signature_parameters_spec.rb
|
85
91
|
- spec/signer_spec.rb
|
86
92
|
- spec/signing_string_spec.rb
|
87
93
|
- spec/spec_helper.rb
|
94
|
+
- spec/verifier_spec.rb
|
88
95
|
homepage: https://github.com/99designs/http-signatures-ruby
|
89
96
|
licenses:
|
90
97
|
- MIT
|
@@ -114,6 +121,9 @@ test_files:
|
|
114
121
|
- spec/context_spec.rb
|
115
122
|
- spec/header_list_spec.rb
|
116
123
|
- spec/key_store_spec.rb
|
124
|
+
- spec/signature_parameters_parser_spec.rb
|
125
|
+
- spec/signature_parameters_spec.rb
|
117
126
|
- spec/signer_spec.rb
|
118
127
|
- spec/signing_string_spec.rb
|
119
128
|
- spec/spec_helper.rb
|
129
|
+
- spec/verifier_spec.rb
|