http_signatures 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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