uri_signature 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/lib/uri_signature.rb +101 -0
- data/lib/uri_signature/version.rb +3 -0
- data/uri_signature.gemspec +26 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8a1482fdd0e2a6698ec0b611e9a54a898674e065
|
4
|
+
data.tar.gz: 7477e503fe4028a3055fc524c407c6f226ba434b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 99b51e00cae68744b8204a2b530cdd7f7725e7c9aca3193a660151d53b24afb2e420a60a7bd8da261f93b99f327758d869c29feb13dafc4f8ccc4f621018aa05
|
7
|
+
data.tar.gz: 33f270cbc220e79521eb8ee37a6d2c4086a55d65c43bca560f54fa424552f15949ea6dd7cfba37004b9ef7ba2f14a8bbe9346f353fe1cfd9d5f5d34613686901
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# UriSignature
|
2
|
+
|
3
|
+
A general purpose way of signing a URL when you need the signature to be added
|
4
|
+
to the URL. This is useful when you need to redirect a user from one service to
|
5
|
+
another and communicate information without any risk of tampering. It's based
|
6
|
+
on a shared secret and HMAC being used to sign the URL.
|
7
|
+
|
8
|
+
This could be used in techniques described in http://broadcast.oreilly.com/2009/12/principles-for-standardized-rest-authentication.html
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'uri_signature'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install uri_signature
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
pry(main)> require 'uri_signature'
|
30
|
+
|
31
|
+
pry(main)> signed_uri = URISignature.sign("http://foobar.com?my_trusted_information=helloworld", expiry: 300, key: "my_shared_secret_key").to_s
|
32
|
+
=> "http://foobar.com?my_trusted_information=helloworld&signature=b99f3d00610361be49327938b82802c933aa4fac&signature_expires=1444691758"
|
33
|
+
|
34
|
+
pry(main)> URISignature.valid?(signed_uri, key: "my_shared_secret_key")
|
35
|
+
=> true
|
36
|
+
```
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dgvz/uri_signature.
|
41
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'uri_signature/version'
|
2
|
+
require 'addressable/uri'
|
3
|
+
|
4
|
+
class URISignature
|
5
|
+
class SignatureError < StandardError; end
|
6
|
+
class InvalidSignatureError < SignatureError; end
|
7
|
+
class ExpiredSignatureError < SignatureError; end
|
8
|
+
|
9
|
+
MINUTE = 60
|
10
|
+
|
11
|
+
# Sign the given URI and return a signed URI.
|
12
|
+
#
|
13
|
+
# @param uri [String] a valid URI
|
14
|
+
# @param expiry [Integer] How many seconds before the signature expires.
|
15
|
+
# @param key [String] The key used to sign the uri
|
16
|
+
# @return [String] The signed URI. The signature is added to the query
|
17
|
+
# params.
|
18
|
+
def self.sign(uri, expiry: 5 * MINUTE, key: ENV['HMAC_SIGNATURE_KEY'])
|
19
|
+
uri = Addressable::URI.parse(uri)
|
20
|
+
|
21
|
+
query_values = (uri.query_values || {}).to_a
|
22
|
+
|
23
|
+
query_values << ["signature_expires", (Time.now + expiry).tv_sec]
|
24
|
+
|
25
|
+
# Sort by the key to guarantee URI is always constructed in the same way
|
26
|
+
query_values = query_values.sort_by { |k, v| k }
|
27
|
+
|
28
|
+
uri.query_values = query_values
|
29
|
+
|
30
|
+
add_signature(uri, key).to_s
|
31
|
+
uri.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
# Is the given uri correctly signed.
|
35
|
+
#
|
36
|
+
# @param uri [String]
|
37
|
+
# @param raise_error [Boolean] Defaults to to true. Set this to false if
|
38
|
+
# you don't this to raise but rather return false if it's invalid. We
|
39
|
+
# default raise_error to true to be extra cautious as wanting to handle
|
40
|
+
# incorrectly signed callbacks is probably an edge case.
|
41
|
+
# @return [TrueClass, FalseClass] Will return true if correctly signed.
|
42
|
+
# Otherwise it will raise. If you really don't want it to raise you can
|
43
|
+
# pass `raise_error: false`.
|
44
|
+
# @raise [URISignature::SignatureError] if for any reason the
|
45
|
+
# signature is invalid. This can be disabled by setting
|
46
|
+
# `:raise_error => false`.
|
47
|
+
def self.valid?(uri, key: ENV['HMAC_SIGNATURE_KEY'], raise_error: true)
|
48
|
+
|
49
|
+
# First remove the signature
|
50
|
+
comparison_uri = Addressable::URI.parse(uri).clone.tap do |u|
|
51
|
+
query_values = u.query_values.clone
|
52
|
+
query_values.delete("signature")
|
53
|
+
u.query_values = query_values
|
54
|
+
end
|
55
|
+
|
56
|
+
# Then recalculate and add back in the signature
|
57
|
+
add_signature(comparison_uri, key)
|
58
|
+
|
59
|
+
# Compare the uri to the original
|
60
|
+
if secure_compare(uri, comparison_uri.to_s)
|
61
|
+
expires = Time.at(comparison_uri.query_values["signature_expires"].to_i)
|
62
|
+
if expires < Time.now
|
63
|
+
raise URISignature::ExpiredSignatureError, "The signature for #{uri} has expired."
|
64
|
+
end
|
65
|
+
true
|
66
|
+
else
|
67
|
+
raise URISignature::InvalidSignatureError, "Invalid signature provided in #{uri}."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @!private
|
72
|
+
# This method is meant to only be used in this class. Do not use it externally.
|
73
|
+
# @param uri [String]
|
74
|
+
# @param key [String]
|
75
|
+
def self.add_signature(uri, key)
|
76
|
+
unsigned_uri = uri.to_s
|
77
|
+
|
78
|
+
digest = OpenSSL::Digest.new('sha1')
|
79
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, key, unsigned_uri)
|
80
|
+
|
81
|
+
query_values = uri.query_values.to_a
|
82
|
+
query_values << ["signature", hmac]
|
83
|
+
|
84
|
+
# Sort again to ensure consistency
|
85
|
+
uri.query_values = query_values.sort_by { |k, v| k }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Use a secure string comparison method to avoid timing attacks
|
89
|
+
# (https://en.wikipedia.org/wiki/Timing_attack)
|
90
|
+
def self.secure_compare(a, b)
|
91
|
+
return false if a.empty? || b.empty? || a.bytesize != b.bytesize
|
92
|
+
l = a.unpack "C#{a.bytesize}"
|
93
|
+
|
94
|
+
res = 0
|
95
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
96
|
+
res == 0
|
97
|
+
end
|
98
|
+
|
99
|
+
private_class_method :add_signature
|
100
|
+
private_class_method :secure_compare
|
101
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'uri_signature/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "uri_signature"
|
8
|
+
spec.version = UriSignature::VERSION
|
9
|
+
spec.authors = ["Dylan Griffith"]
|
10
|
+
spec.email = ["dyl.griffith@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A general purpose way of signing a URL when you need the signature to be added to the URL.}
|
13
|
+
spec.description = %q{A general purpose way of signing a URL when you need the signature to be added to the URL.}
|
14
|
+
spec.homepage = "https://github.com/dgvz/uri_signature"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "addressable"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uri_signature
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dylan Griffith
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: addressable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A general purpose way of signing a URL when you need the signature to
|
70
|
+
be added to the URL.
|
71
|
+
email:
|
72
|
+
- dyl.griffith@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".travis.yml"
|
80
|
+
- Gemfile
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/uri_signature.rb
|
84
|
+
- lib/uri_signature/version.rb
|
85
|
+
- uri_signature.gemspec
|
86
|
+
homepage: https://github.com/dgvz/uri_signature
|
87
|
+
licenses: []
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.2.2
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A general purpose way of signing a URL when you need the signature to be
|
109
|
+
added to the URL.
|
110
|
+
test_files: []
|