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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f089f43abace9a9b86c3d19ba394ca4029260c2
4
- data.tar.gz: d3f055bf4eb80034756d585770be2bffd778b798
3
+ metadata.gz: e7081d201a0f52168e49d42043f5a2dfd792b1e6
4
+ data.tar.gz: 51fd29fdd9c4c20f09d4fbcfb2bf012ed36739bb
5
5
  SHA512:
6
- metadata.gz: 6d8a718a78fed672fb5c61b1a43f9d01fc983abf08f5fa97f7156f1d14ceaba5bcd95413f2b47dec8bc154880a7cfa4530b68ffa52cb63a48866a67309249065
7
- data.tar.gz: 00be41245a855a9441a0414c1a2c10c5523df24b879f32c3b9c55d2327bb9dfe56b7382304a8e36818e870e0e4479e78830446bcef84daf2b79a410d69639671
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
- Message verification is not implemented, but will look like this:
62
-
63
- * The key ID, algorithm name, header list and provided signature will be parsed
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
@@ -16,6 +16,10 @@ module HttpSignatures
16
16
  )
17
17
  end
18
18
 
19
+ def verifier
20
+ Verifier.new(key_store: @key_store)
21
+ end
22
+
19
23
  private
20
24
 
21
25
  def signing_key
@@ -17,7 +17,7 @@ module HttpSignatures
17
17
  @names.dup
18
18
  end
19
19
 
20
- def to_s
20
+ def to_str
21
21
  @names.join(" ")
22
22
  end
23
23
 
@@ -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
@@ -20,7 +20,7 @@ module HttpSignatures
20
20
  pc = []
21
21
  pc << 'keyId="%s"' % @key.id
22
22
  pc << 'algorithm="%s"' % @algorithm.name
23
- pc << 'headers="%s"' % @header_list.to_s
23
+ pc << 'headers="%s"' % @header_list.to_str
24
24
  pc << 'signature="%s"' % signature_base64
25
25
  pc
26
26
  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
- signature = signature_parameters_for_message(message).to_str
15
- m["Signature"] = [signature]
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 signature_parameters_for_message(message)
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: signature_for_message(message),
26
+ signature: signature(message).to_str,
28
27
  )
29
28
  end
30
29
 
31
- def signature_for_message(message)
32
- @algorithm.sign(@key.secret, signing_string_for_message(message))
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
- ).to_s
40
- end
41
-
42
- class EmptyHeaderNames < StandardError; end
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
@@ -8,7 +8,7 @@ module HttpSignatures
8
8
  @message = message
9
9
  end
10
10
 
11
- def to_s
11
+ def to_str
12
12
  @header_list.to_a.map do |header|
13
13
  "%s: %s" % [header, header_value(header)]
14
14
  end.join("\n")
@@ -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
@@ -0,0 +1,13 @@
1
+ module HttpSignatures
2
+ class Verifier
3
+
4
+ def initialize(key_store:)
5
+ @key_store = key_store
6
+ end
7
+
8
+ def valid?(message)
9
+ Verification.new(message: message, key_store: @key_store).valid?
10
+ end
11
+
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module HttpSignatures
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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
@@ -26,10 +26,10 @@ RSpec.describe HttpSignatures::HeaderList do
26
26
  end
27
27
  end
28
28
 
29
- describe "#to_s" do
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.to_s).to eq("(request-target) date content-type")
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
- "date: #{EXAMPLE_DATE}",
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
@@ -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 "#to_s" do
22
+ describe "#to_str" do
23
23
 
24
24
  it "returns correct signing string" do
25
- expect(signing_string.to_s).to eq(
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.to_s
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.1.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-07-29 00:00:00.000000000 Z
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