shmac 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: 134e0f7871345b0f56699849f2fcbb27da1b33b6
4
- data.tar.gz: 3fa782727eea6bdcd2b7a5dea3ea0de352add1e5
3
+ metadata.gz: 34daa52fbfddb3e27abe5cd0c80da5cbfbb99fcd
4
+ data.tar.gz: 7b45ab0eff281a6385baa7c6a9911f87b9211148
5
5
  SHA512:
6
- metadata.gz: fe1a5876c513ac6d943be35ad2ff21b8c4280a02f3b9c769d9cb422c5fc8efa0b348a70c1754324385f2fcbfc65f960d7174c8e71479c43ef0aafa2e275388e4
7
- data.tar.gz: 436a767db4e95c9594a6f18ded0a9e489ed422f808e4f1a1c6b19afec603a537fb6f00b77e029c574d8717ed52c17e6dc6b3bf1e3722745023085d5acc82347a
6
+ metadata.gz: 0eb6cb29738a08567fd3f5a394f9a1d48d8dabd522202d8c8355861764e9442a046d8304b512e8e00c8a66fea0ecaea3d23ca473cd768e7a31c254ec73d4ad77
7
+ data.tar.gz: e3b7c7edee5a3e11c71b146b8860e5ed39778f1cfbba0441f94a08c091b576bf70a49c914b43a87e78580a00ba914d4347c624936e0789228adbc51d9729fc7c
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -3,3 +3,6 @@ language: ruby
3
3
  rvm:
4
4
  - 2.3.1
5
5
  before_install: gem install bundler -v 1.12.5
6
+ addons:
7
+ code_climate:
8
+ repo_token: 0675be7b5a92ad9afe8e8a4143f00626067a1fe4355684273b436f41932f6cf4
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in shmac.gemspec
4
4
  gemspec
5
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Simple HMAC authentication in a semi-usable state.
4
4
 
5
+ [![Build Status](https://travis-ci.org/Oktavilla/shmac.svg?branch=master)](https://travis-ci.org/Oktavilla/shmac)
6
+ [![Code issues](https://codeclimate.com/github/Oktavilla/shmac/badges/gpa.svg)](https://codeclimate.com/github/Oktavilla/shmac)
7
+ [![Coverage](https://codeclimate.com/github/Oktavilla/shmac/badges/coverage.svg)](https://codeclimate.com/github/Oktavilla/shmac/coverage)
8
+
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
@@ -1,22 +1,24 @@
1
1
  require "shmac/version"
2
+ require "shmac/security"
2
3
  require "shmac/authentication"
3
4
  require "shmac/request_adapters"
4
5
 
5
6
  module Shmac
6
- def self.rails secret, request, namespace: nil
7
- authentication(secret, request, namespace: namespace, request_adapter: RequestAdapters::Rails)
7
+ def self.rails secret, request, namespace: nil, options: {}
8
+ authentication(secret, request, namespace: namespace, request_adapter: RequestAdapters::Rails, options: options)
8
9
  end
9
10
 
10
- def self.net_http secret, request, namespace: nil
11
- authentication(secret, request, namespace: namespace, request_adapter: RequestAdapters::NetHttp)
11
+ def self.net_http secret, request, namespace: nil, options: {}
12
+ authentication(secret, request, namespace: namespace, request_adapter: RequestAdapters::NetHttp, options: options)
12
13
  end
13
14
 
14
- def self.authentication secret, request, namespace: nil, request_adapter: nil
15
+ def self.authentication secret, request, namespace: nil, request_adapter: nil, options: {}
15
16
  Authentication.new(
16
17
  secret,
17
18
  request,
18
19
  header_namespace: namespace,
19
- request_adapter: request_adapter
20
+ request_adapter: request_adapter,
21
+ options: options
20
22
  )
21
23
  end
22
24
  end
@@ -1,11 +1,12 @@
1
1
  require "shmac/signature_calculator"
2
2
  require "shmac/authorization_header"
3
+ require "shmac/security"
3
4
 
4
5
  module Shmac
5
6
  class Authentication
6
7
  include Comparable
7
8
 
8
- attr_reader :secret, :header_namespace
9
+ attr_reader :secret, :header_namespace, :options
9
10
 
10
11
  def self.generate_authorization_header request, secret:, access_key:, organization:, header_namespace: nil
11
12
  AuthorizationHeader.generate(
@@ -19,29 +20,36 @@ module Shmac
19
20
  new(secret, request, header_namespace: header_namespace).signature
20
21
  end
21
22
 
22
- def initialize secret, request, header_namespace: nil, request_adapter: nil
23
+ def initialize secret, request, header_namespace: nil, request_adapter: nil, options: {}
23
24
  @secret = secret
24
25
  @request = request
25
26
  @request_adapter = request_adapter
26
27
  @header_namespace = header_namespace
28
+ self.options = options
27
29
  end
28
30
 
29
31
  def == other
30
- other.is_a?(self.class) && self.signature == other.signature
32
+ return false unless other.is_a?(self.class)
33
+
34
+ Security.secure_compare self.signature, other.signature
31
35
  end
32
36
 
33
37
  def signature
34
38
  SignatureCalculator.new(
35
39
  secret: self.secret,
36
40
  request: self.request,
37
- header_namespace: self.header_namespace
41
+ header_namespace: self.header_namespace,
42
+ options: { skip_path: self.options[:skip_path] }
38
43
  ).to_s
39
44
  end
40
45
 
41
46
  def authentic?
42
47
  return false if request.authorization.to_s.strip.empty?
48
+ return false if request.tampered_body?
49
+
50
+ given_signature = AuthorizationHeader.new(request.authorization).signature
43
51
 
44
- AuthorizationHeader.new(request.authorization).signature == self.signature
52
+ Security.secure_compare given_signature, self.signature
45
53
  end
46
54
 
47
55
  def request
@@ -51,5 +59,18 @@ module Shmac
51
59
  def request_adapter
52
60
  @request_adapter ||= ->(r) { r }
53
61
  end
62
+
63
+ private
64
+
65
+ def options= opts = {}
66
+ unknown_keys = opts.keys - default_options.keys
67
+ raise ArgumentError.new("Unknown options: #{unknown_keys.join(", ")}") if unknown_keys.any?
68
+
69
+ @options = default_options.merge(opts)
70
+ end
71
+
72
+ def default_options
73
+ { skip_path: false, validate_body_contents: true }
74
+ end
54
75
  end
55
76
  end
@@ -1,3 +1,5 @@
1
+ require "shmac/security"
2
+
1
3
  module Shmac
2
4
  class AuthorizationHeader
3
5
  include Comparable
@@ -17,7 +19,9 @@ module Shmac
17
19
  end
18
20
 
19
21
  def == other
20
- other.is_a?(self.class) && self.to_s == other.to_s
22
+ return false unless other.is_a?(self.class)
23
+
24
+ Security.secure_compare self.to_s, other.to_s
21
25
  end
22
26
 
23
27
  def to_s
@@ -1,4 +1,5 @@
1
1
  require "shmac/normalized_http_headers"
2
+ require "shmac/security"
2
3
 
3
4
  module Shmac
4
5
  class Request
@@ -24,6 +25,17 @@ module Shmac
24
25
  headers.fetch("content-md5") { headers["x-content-md5"] }
25
26
  end
26
27
 
28
+ def tampered_body?
29
+ return false unless body
30
+ return false if content_md5.to_s.strip.empty?
31
+
32
+ !content_md5_matches_body?
33
+ end
34
+
35
+ def content_md5_matches_body?
36
+ Security.secure_compare content_md5, Digest::MD5.base64digest(body)
37
+ end
38
+
27
39
  def authorization
28
40
  # Test for x-authorization for clients that have issues with standard headers
29
41
  headers.fetch("x-authorization") { headers["authorization"] }
@@ -32,10 +44,5 @@ module Shmac
32
44
  def headers= headers
33
45
  @headers = NormalizedHttpHeaders.new(headers).to_h
34
46
  end
35
-
36
- def api_version
37
- headers.fetch("x-authorization-version", 0).to_i
38
- end
39
47
  end
40
-
41
48
  end
@@ -0,0 +1,16 @@
1
+ module Shmac
2
+ module Security
3
+ # Constant time comparison of strings
4
+ # Borrowed from ActiveSupport::SecurityUtils
5
+ def self.secure_compare a, b
6
+ return false if a.empty? || b.empty?
7
+ return false unless a.bytesize == b.bytesize
8
+
9
+ l = a.unpack "C#{a.bytesize}"
10
+
11
+ res = 0
12
+ b.each_byte { |byte| res |= byte ^ l.shift }
13
+ res == 0
14
+ end
15
+ end
16
+ end
@@ -3,12 +3,13 @@ require "base64"
3
3
 
4
4
  module Shmac
5
5
  class SignatureCalculator
6
- attr_reader :secret, :request, :header_namespace
6
+ attr_reader :secret, :request, :header_namespace, :options
7
7
 
8
- def initialize secret:, request:, header_namespace: nil
8
+ def initialize secret:, request:, header_namespace: nil, options: {}
9
9
  @secret = secret
10
10
  @request = request
11
11
  @header_namespace = header_namespace.downcase if header_namespace
12
+ @options = options
12
13
  end
13
14
 
14
15
  def to_s
@@ -32,9 +33,8 @@ module Shmac
32
33
  platform_headers = canonicalized_platform_headers.to_s.strip
33
34
  parts << platform_headers unless platform_headers.empty?
34
35
 
35
- # The path is expected by spec but the DPO sends the same message (including headers) to several endpoints
36
- # We introduce an api version so we do not lose messages
37
- parts << request.path unless request.api_version > 0
36
+ # Some clients do not know to which endpoint a message is sent
37
+ parts << request.path unless options[:skip_path]
38
38
 
39
39
  parts.join("\n")
40
40
  end
@@ -1,3 +1,3 @@
1
1
  module Shmac
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shmac
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
  - Joel Junström
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-31 00:00:00.000000000 Z
11
+ date: 2016-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,6 +75,7 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
77
  - ".rspec"
78
+ - ".ruby-version"
78
79
  - ".travis.yml"
79
80
  - CODE_OF_CONDUCT.md
80
81
  - Gemfile
@@ -90,6 +91,7 @@ files:
90
91
  - lib/shmac/normalized_http_headers.rb
91
92
  - lib/shmac/request.rb
92
93
  - lib/shmac/request_adapters.rb
94
+ - lib/shmac/security.rb
93
95
  - lib/shmac/signature_calculator.rb
94
96
  - lib/shmac/version.rb
95
97
  - shmac.gemspec