shmac 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: 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