itsdangerousr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +3 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.md +44 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/itsdangerousr.gemspec +65 -0
- data/lib/itsdangerousr.rb +313 -0
- data/test/helper.rb +30 -0
- data/test/test_itsdangerousr.rb +214 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c4b1d444326a60bd2883c0caedb94b97766ff88
|
4
|
+
data.tar.gz: c5400e1c54f6da695dd3b2a371d17265bdfbaef7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccab08b04c56006df9fd1ad3e61c31b307f02f7a25f28f6e6fe8b719b59188bafc13daa096681d191045be64a191d629646d05c4ef955e8d95e501359645277d
|
7
|
+
data.tar.gz: e265c0df86033ff8c0450dc4c847d9e9284e43b52c5f1973193d89caf88d2a35dea5b5dbe94da3fccbb1a62efb06c199afef43166f764c349b7edb6b879ff6f0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Marcus McCurdy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#It's Dangerous to go alone take this...
|
2
|
+
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/volker48/itsdangerousr.svg?branch=master)](https://travis-ci.org/volker48/itsdangerousr)
|
5
|
+
|
6
|
+
This is a port of Python's [itsdangerous](https://github.com/mitsuhiko/itsdangerous) to Ruby
|
7
|
+
|
8
|
+
###Examples
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
require 'itsdangerousr'
|
12
|
+
|
13
|
+
serializer = Itsdangerousr::URLSafeSerializer.new('super secret key')
|
14
|
+
|
15
|
+
payload = {:message => 'Keep it secret, keep it safe', :status => 'ok'}
|
16
|
+
|
17
|
+
result = serializer.dumps(payload)
|
18
|
+
|
19
|
+
puts result
|
20
|
+
|
21
|
+
# "eyJtZXNzYWdlIjoiS2VlcCBpdCBzZWNyZXQsIGtlZXAgaXQgc2FmZSIsInN0YXR1cyI6Im9rIn0.nzD92ZMbV52tUW-yp9IWTKqYHRo"
|
22
|
+
|
23
|
+
decoded = serializer.loads(result)
|
24
|
+
|
25
|
+
puts decoded
|
26
|
+
|
27
|
+
# {:message=>"Keep it secret, keep it safe", :status=>"ok"}
|
28
|
+
```
|
29
|
+
|
30
|
+
### Contributing to itsdangerousr
|
31
|
+
|
32
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
33
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
34
|
+
* Fork the project.
|
35
|
+
* Start a feature/bugfix branch.
|
36
|
+
* Commit and push until you are happy with your contribution.
|
37
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
38
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
39
|
+
|
40
|
+
### Copyright
|
41
|
+
|
42
|
+
Copyright (c) 2014 Marcus McCurdy. See LICENSE.txt for
|
43
|
+
further details.
|
44
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "itsdangerousr"
|
18
|
+
gem.homepage = "http://github.com/volker48/itsdangerousr"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Python's itsdangerous ported to Ruby}
|
21
|
+
gem.description = %Q{Sometimes you just want to send some data to untrusted environments. But how to do this safely? The trick involves signing. Given a key only you know, you can cryptographically sign your data and hand it over to someone else. When you get the data back you can easily ensure that nobody tampered with it.}
|
22
|
+
gem.email = "marcus.mccurdy@gmail.com"
|
23
|
+
gem.authors = ["Marcus McCurdy"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Code coverage detail"
|
36
|
+
task :simplecov do
|
37
|
+
ENV['COVERAGE'] = "true"
|
38
|
+
Rake::Task['test'].execute
|
39
|
+
end
|
40
|
+
|
41
|
+
require 'reek/rake/task'
|
42
|
+
Reek::Rake::Task.new do |t|
|
43
|
+
t.fail_on_error = true
|
44
|
+
t.verbose = false
|
45
|
+
t.source_files = 'lib/**/*.rb'
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rdoc/task'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
53
|
+
|
54
|
+
rdoc.rdoc_dir = 'rdoc'
|
55
|
+
rdoc.title = "itsdangerousr-gem #{version}"
|
56
|
+
rdoc.rdoc_files.include('README*')
|
57
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: itsdangerousr 1.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "itsdangerousr"
|
9
|
+
s.version = "1.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Marcus McCurdy"]
|
14
|
+
s.date = "2014-09-01"
|
15
|
+
s.description = "Sometimes you just want to send some data to untrusted environments. But how to do this safely? The trick involves signing. Given a key only you know, you can cryptographically sign your data and hand it over to someone else. When you get the data back you can easily ensure that nobody tampered with it."
|
16
|
+
s.email = "marcus.mccurdy@gmail.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".travis.yml",
|
23
|
+
"Gemfile",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"itsdangerousr.gemspec",
|
29
|
+
"lib/itsdangerousr.rb",
|
30
|
+
"test/helper.rb",
|
31
|
+
"test/test_itsdangerousr.rb"
|
32
|
+
]
|
33
|
+
s.homepage = "http://github.com/volker48/itsdangerousr"
|
34
|
+
s.licenses = ["MIT"]
|
35
|
+
s.rubygems_version = "2.4.1"
|
36
|
+
s.summary = "Python's itsdangerous ported to Ruby"
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
s.specification_version = 4
|
40
|
+
|
41
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
42
|
+
s.add_runtime_dependency(%q<json>, [">= 1.6.0"])
|
43
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
44
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
45
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
46
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<reek>, ["~> 1.2.8"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<json>, [">= 1.6.0"])
|
50
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
51
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
52
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
53
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
54
|
+
s.add_dependency(%q<reek>, ["~> 1.2.8"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<json>, [">= 1.6.0"])
|
58
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
61
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
62
|
+
s.add_dependency(%q<reek>, ["~> 1.2.8"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'time'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
module Itsdangerousr
|
8
|
+
|
9
|
+
# 2011/01/01 in UTC
|
10
|
+
EPOCH = 1293840000
|
11
|
+
|
12
|
+
def self.base64_encode(string)
|
13
|
+
Base64.urlsafe_encode64(string).gsub(/=+$/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.base64_decode(string)
|
17
|
+
Base64.urlsafe_decode64(string + '=' * (-string.length % 4))
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.constant_time_compare(val1, val2)
|
22
|
+
check = val1.bytesize ^ val2.bytesize
|
23
|
+
val1.bytes.zip(val2.bytes) { |x, y| check |= x ^ y.to_i }
|
24
|
+
check == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
class SigningAlgorithm
|
28
|
+
|
29
|
+
def get_signature(key, value)
|
30
|
+
raise 'Not Implemented'
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify_signature(key, value, sig)
|
34
|
+
Itsdangerousr.constant_time_compare(sig, get_signature(key, value))
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class HMACAlgorithm < SigningAlgorithm
|
40
|
+
|
41
|
+
def initialize(digest_method=OpenSSL::Digest::SHA1)
|
42
|
+
@digest_method = digest_method
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_signature(key, value)
|
46
|
+
OpenSSL::HMAC.digest(@digest_method.new, key, value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Signer
|
52
|
+
|
53
|
+
def initialize(secret_key, options={})
|
54
|
+
defaults = {:salt => 'itsdangerous.Signer', :sep => '.',
|
55
|
+
:key_derivation => 'django-concat',
|
56
|
+
:digest_method => OpenSSL::Digest::SHA1,
|
57
|
+
:algorithm => HMACAlgorithm.new()}
|
58
|
+
options = defaults.merge(options)
|
59
|
+
@secret_key = secret_key
|
60
|
+
@sep = options[:sep]
|
61
|
+
@salt = options[:salt]
|
62
|
+
@key_derivation = options[:key_derivation]
|
63
|
+
@digest_method = options[:digest_method]
|
64
|
+
@algorithm = options[:algorithm]
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_signature(value)
|
68
|
+
key = derive_key()
|
69
|
+
sig = @algorithm.get_signature(key, value)
|
70
|
+
Itsdangerousr.base64_encode(sig)
|
71
|
+
end
|
72
|
+
|
73
|
+
def sign(value)
|
74
|
+
value + @sep + get_signature(value)
|
75
|
+
end
|
76
|
+
|
77
|
+
def derive_key
|
78
|
+
case @key_derivation
|
79
|
+
when 'concat'
|
80
|
+
@digest_method.digest(@salt + @secret_key)
|
81
|
+
when 'django-concat'
|
82
|
+
@digest_method.digest(@salt + 'signer' + @secret_key)
|
83
|
+
when 'hmac'
|
84
|
+
hmac = OpenSSL::HMAC.new(@secret_key, @digest_method)
|
85
|
+
hmac.update(@salt)
|
86
|
+
hmac.digest()
|
87
|
+
when 'none'
|
88
|
+
@secret_key
|
89
|
+
else
|
90
|
+
raise TypeError, 'Unknown key derivation method'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate(signed_value)
|
95
|
+
begin
|
96
|
+
unsign(signed_value)
|
97
|
+
true
|
98
|
+
rescue BadSignature
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def unsign(signed_value)
|
104
|
+
unless signed_value.include?('.')
|
105
|
+
raise BadSignature, "No #{@sep} found in signed value"
|
106
|
+
end
|
107
|
+
value, _, sig = signed_value.rpartition(@sep)
|
108
|
+
unless verify_signature(value, sig)
|
109
|
+
raise BadSignature.new("Signature #{sig} does not match", value)
|
110
|
+
end
|
111
|
+
value
|
112
|
+
end
|
113
|
+
|
114
|
+
def verify_signature(value, sig)
|
115
|
+
key = derive_key()
|
116
|
+
sig = Itsdangerousr.base64_decode(sig)
|
117
|
+
@algorithm.verify_signature(key, value, sig)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class TimestampSigner < Signer
|
123
|
+
def sign(value)
|
124
|
+
timestamp = Itsdangerousr::base64_encode((Time.now.to_i - Itsdangerousr::EPOCH).to_s)
|
125
|
+
value = value + @sep + timestamp
|
126
|
+
value + @sep + get_signature(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def unsign(value, options={})
|
130
|
+
defaults = {:max_age => nil, :return_timestamp => false}
|
131
|
+
options = defaults.merge(options)
|
132
|
+
@max_age = options[:max_age]
|
133
|
+
@return_timestamp = options[:return_timestamp]
|
134
|
+
begin
|
135
|
+
result = super(value)
|
136
|
+
sig_error = nil
|
137
|
+
rescue BadSignature => e
|
138
|
+
sig_error = e
|
139
|
+
if e.payload.nil?
|
140
|
+
result = e.paylod
|
141
|
+
else
|
142
|
+
result = ''
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
unless result.include?(@sep)
|
148
|
+
if sig_error
|
149
|
+
raise sig_error
|
150
|
+
end
|
151
|
+
raise BadTimeSignature, 'Timestamp missing'
|
152
|
+
end
|
153
|
+
|
154
|
+
value, _, timestamp = result.rpartition(@sep)
|
155
|
+
|
156
|
+
begin
|
157
|
+
timestamp = Itsdangerousr::base64_decode(timestamp).to_i
|
158
|
+
rescue StandardError
|
159
|
+
timestamp = nil
|
160
|
+
end
|
161
|
+
|
162
|
+
unless sig_error.nil?
|
163
|
+
raise BadTimeSignature.new(sig_error.message, value, timestamp)
|
164
|
+
end
|
165
|
+
|
166
|
+
if timestamp.nil?
|
167
|
+
raise BadTimeSignature.new('Malformed timestamp', value)
|
168
|
+
end
|
169
|
+
|
170
|
+
unless @max_age.nil?
|
171
|
+
age = (Time.now.to_i - Itsdangerousr::EPOCH) - timestamp.to_i
|
172
|
+
if age > @max_age
|
173
|
+
raise SignatureExpired.new("Signature age %s > %s seconds" % [age, @max_age], value,)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if @return_timestamp
|
178
|
+
return value, timestamp
|
179
|
+
end
|
180
|
+
value
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class BadSignature < StandardError
|
185
|
+
|
186
|
+
attr_reader :payload
|
187
|
+
|
188
|
+
def initialize(message, payload=nil)
|
189
|
+
super(message)
|
190
|
+
@payload = payload
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
class BadTimeSignature < BadSignature
|
196
|
+
def initialize (message, payload=nil, date_signed=nil)
|
197
|
+
super(message, payload)
|
198
|
+
@date_signed = date_signed
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class SignatureExpired < BadTimeSignature
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
class BadPayload < StandardError
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
class Serializer
|
211
|
+
|
212
|
+
def initialize(secret_key, options={})
|
213
|
+
defaults = {:salt => 'itsdangerous', :serializer => JSON,
|
214
|
+
:signer => Signer, :signer_kwargs => {}}
|
215
|
+
options = defaults.merge(options)
|
216
|
+
@secret_key = secret_key
|
217
|
+
@salt = options[:salt]
|
218
|
+
@serializer = options[:serializer]
|
219
|
+
@signer = options[:signer]
|
220
|
+
@signer_kwargs = options[:signer_kwargs]
|
221
|
+
end
|
222
|
+
|
223
|
+
def load_payload(payload, options={})
|
224
|
+
defaults = {:serializer => @serializer}
|
225
|
+
options = defaults.merge(options)
|
226
|
+
serializer = options[:serializer]
|
227
|
+
serializer.load(payload, nil, :symbolize_names => true)
|
228
|
+
end
|
229
|
+
|
230
|
+
def dump_payload(obj)
|
231
|
+
@serializer.dump(obj)
|
232
|
+
end
|
233
|
+
|
234
|
+
def make_signer(options={})
|
235
|
+
defaults = {:salt => @salt}
|
236
|
+
options = defaults.merge(options)
|
237
|
+
@signer.new(@secret_key, @signer_kwargs.merge(:salt => options[:salt]))
|
238
|
+
end
|
239
|
+
|
240
|
+
def dumps(obj, salt=@salt)
|
241
|
+
payload = dump_payload(obj)
|
242
|
+
make_signer(:salt => salt).sign(payload)
|
243
|
+
end
|
244
|
+
|
245
|
+
def loads(s, salt=@salt)
|
246
|
+
load_payload(make_signer(:salt => salt).unsign(s))
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
module URLSafeSerializerMixin
|
252
|
+
|
253
|
+
def load_payload(payload)
|
254
|
+
decompress = false
|
255
|
+
if payload.start_with?('.')
|
256
|
+
payload = payload[1..-1]
|
257
|
+
decompress = true
|
258
|
+
end
|
259
|
+
json = Itsdangerousr.base64_decode(payload)
|
260
|
+
if decompress
|
261
|
+
begin
|
262
|
+
json = Zlib::Inflate.inflate(json)
|
263
|
+
rescue => e
|
264
|
+
raise BadPayload, "Could not zlib decompress the payload before decoding the payload. #{e}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
super(json)
|
268
|
+
end
|
269
|
+
|
270
|
+
def dump_payload(obj)
|
271
|
+
json = super(obj)
|
272
|
+
is_compressed = false
|
273
|
+
compressed = Zlib::Deflate.deflate(json)
|
274
|
+
if compressed.length < json.length - 1
|
275
|
+
json = compressed
|
276
|
+
is_compressed = true
|
277
|
+
end
|
278
|
+
base64d = Itsdangerousr.base64_encode(json)
|
279
|
+
base64d.prepend('.') if is_compressed
|
280
|
+
base64d
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
class TimedSerializer < Serializer
|
286
|
+
|
287
|
+
def initialize(secret_key, options={:signer => TimestampSigner})
|
288
|
+
super(secret_key, options)
|
289
|
+
end
|
290
|
+
|
291
|
+
def loads(s, options={})
|
292
|
+
defaults = {:max_age=>nil, :return_timestamp=>false, :salt=>@salt}
|
293
|
+
options = defaults.merge(options)
|
294
|
+
signer = make_signer(:salt => options[:salt])
|
295
|
+
base64d, timestamp = signer.unsign(s, options)
|
296
|
+
payload = load_payload(base64d)
|
297
|
+
if options[:return_timestamp]
|
298
|
+
return payload, timestamp
|
299
|
+
end
|
300
|
+
payload
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
class URLSafeSerializer < Serializer
|
306
|
+
include URLSafeSerializerMixin
|
307
|
+
end
|
308
|
+
|
309
|
+
class URLSafeTimedSerializer < TimedSerializer
|
310
|
+
include URLSafeSerializerMixin
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
module SimpleCov::Configuration
|
4
|
+
def clean_filters
|
5
|
+
@filters = []
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
SimpleCov.configure do
|
10
|
+
clean_filters
|
11
|
+
load_adapter 'test_frameworks'
|
12
|
+
end
|
13
|
+
|
14
|
+
ENV["COVERAGE"] && SimpleCov.start do
|
15
|
+
add_filter "/.rvm/"
|
16
|
+
end
|
17
|
+
require 'rubygems'
|
18
|
+
require 'bundler'
|
19
|
+
begin
|
20
|
+
Bundler.setup(:default, :development)
|
21
|
+
rescue Bundler::BundlerError => e
|
22
|
+
$stderr.puts e.message
|
23
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
24
|
+
exit e.status_code
|
25
|
+
end
|
26
|
+
require 'test/unit'
|
27
|
+
|
28
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
29
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
30
|
+
require 'itsdangerousr'
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class UtilityTests < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_base64_encode
|
6
|
+
encoded = Itsdangerousr.base64_encode('this is a long test')
|
7
|
+
assert(encoded == 'dGhpcyBpcyBhIGxvbmcgdGVzdA')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_base64_decode
|
11
|
+
decoded = Itsdangerousr.base64_decode('dGhpcyBpcyBhIGxvbmcgdGVzdA')
|
12
|
+
assert(decoded == 'this is a long test')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_ctc_equal
|
16
|
+
assert(Itsdangerousr.constant_time_compare('abc123', 'abc123'))
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_ctc_val1_longer
|
21
|
+
assert(!Itsdangerousr.constant_time_compare('asdfasdfasdf', 'a'))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_ctc_val2_longer
|
25
|
+
assert(!Itsdangerousr.constant_time_compare('a', 'asdf123asdf235a'))
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_ctc_empty_string
|
29
|
+
assert(Itsdangerousr.constant_time_compare('', ''))
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class SignerTests < Test::Unit::TestCase
|
35
|
+
def setup
|
36
|
+
@signer = Itsdangerousr::Signer.new('key')
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_derive_key
|
40
|
+
sha1 = OpenSSL::Digest.new('sha1')
|
41
|
+
expected_digest = sha1.digest('itsdangerous.Signer' + 'signer' + 'key')
|
42
|
+
digest = @signer.derive_key()
|
43
|
+
assert(expected_digest == digest)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class SerializerTests < Test::Unit::TestCase
|
48
|
+
|
49
|
+
def setup
|
50
|
+
@serializer = Itsdangerousr::Serializer.new('key')
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_dump_payload
|
54
|
+
payload = {:message => 'hello', :status => 'ok'}
|
55
|
+
dumped = @serializer.dump_payload(payload)
|
56
|
+
assert(dumped == '{"message":"hello","status":"ok"}')
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_load_payload
|
60
|
+
payload = '{"message":"hello","status":"ok"}'
|
61
|
+
loaded = @serializer.load_payload(payload)
|
62
|
+
assert(loaded[:message] == 'hello')
|
63
|
+
assert(loaded[:status] == 'ok')
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_dumps
|
67
|
+
payload = {:message => 'python rules'}
|
68
|
+
dumped = @serializer.dumps(payload)
|
69
|
+
signer = Itsdangerousr::Signer.new('key', :salt => 'itsdangerous')
|
70
|
+
key = signer.derive_key()
|
71
|
+
hmaced = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, '{"message":"python rules"}'.encode('utf-8'))
|
72
|
+
encoded = Itsdangerousr.base64_encode(hmaced)
|
73
|
+
assert(dumped == "{\"message\":\"python rules\"}.#{encoded}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_dumps_loads
|
77
|
+
test_cases = [['a', 'list'], 'a string', 'a unicode string \u2019', {:a => 'dictionary'}, 42, 42.5]
|
78
|
+
test_cases.each do |test_case|
|
79
|
+
dumped = @serializer.dumps(test_case)
|
80
|
+
assert_not_equal(test_case, dumped)
|
81
|
+
loaded = @serializer.loads(dumped)
|
82
|
+
assert_equal(test_case, loaded)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class URLSafeSerializerTests < Test::Unit::TestCase
|
89
|
+
|
90
|
+
def setup
|
91
|
+
@serializer = Itsdangerousr::URLSafeSerializer.new('key')
|
92
|
+
@short_payload = {:message => 'This is a test', :status => 'ok'}
|
93
|
+
@long_payload = {:message => 'This is a longer message so we can see if it will compress something long', :lift => 'do you even, bro?', :status => 'copacetic'}
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_dump_payload_short
|
97
|
+
dumped = @serializer.dumps(@short_payload)
|
98
|
+
assert(dumped[0] != '.')
|
99
|
+
assert(dumped.include?('.'))
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_dumps_loads
|
103
|
+
test_cases = [['a', 'list'], 'a string', 'a unicode string \u2019',
|
104
|
+
{:a => 'dictionary'}, 42, 42.5,
|
105
|
+
{:message => 'this is a longer dict want to test out compression',
|
106
|
+
:status => 'great thanks for asking'}]
|
107
|
+
test_cases.each do |test_case|
|
108
|
+
dumped = @serializer.dumps(test_case)
|
109
|
+
assert_not_equal(test_case, dumped)
|
110
|
+
loaded = @serializer.loads(dumped)
|
111
|
+
assert_equal(test_case, loaded)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_load_from_python
|
116
|
+
key = "\x18j\xe4iiw\xdd\xcb\xacF\x1a\xc0\x17\xc5\x8b\xe7"
|
117
|
+
plain_text = 'this is something i want secure'
|
118
|
+
from_python = 'InRoaXMgaXMgc29tZXRoaW5nIGkgd2FudCBzZWN1cmUi.8frS9vZcZJCSLAW7zK-gGC68JKM'
|
119
|
+
s = Itsdangerousr::URLSafeSerializer.new(key)
|
120
|
+
loaded = s.loads(from_python)
|
121
|
+
assert_equal(plain_text, loaded)
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
class TimedSignerTests < Test::Unit::TestCase
|
128
|
+
def setup
|
129
|
+
@signer = Itsdangerousr::TimestampSigner.new('secret')
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_sign
|
133
|
+
signed = @signer.sign('this is a test')
|
134
|
+
assert(signed.include?('this is a test'))
|
135
|
+
assert(signed.count('.') == 2)
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_unsign_no_options
|
139
|
+
signed = @signer.sign('this is a test')
|
140
|
+
unsigned = @signer.unsign(signed)
|
141
|
+
assert(unsigned == 'this is a test')
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_sig_error
|
145
|
+
signed = @signer.sign('my test')
|
146
|
+
other_signer = Itsdangerousr::TimestampSigner.new('other secret')
|
147
|
+
assert_raise(Itsdangerousr::BadSignature, "Raise exception on bad signature") {
|
148
|
+
other_signer.unsign(signed)
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_raises_badtimesignature
|
153
|
+
non_timestamp_signed = Itsdangerousr::Signer.new('secret').sign('test')
|
154
|
+
assert_raise(Itsdangerousr::BadTimeSignature, "Raise exception on missing timestamp") {
|
155
|
+
@signer.unsign(non_timestamp_signed)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
class TimedSerializerTests < Test::Unit::TestCase
|
162
|
+
|
163
|
+
def setup
|
164
|
+
@serializer = Itsdangerousr::TimedSerializer.new('secret')
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_not_expired
|
168
|
+
original = 'My data'
|
169
|
+
payload = @serializer.dumps(original)
|
170
|
+
assert_not_equal(payload, original)
|
171
|
+
loaded = @serializer.loads(payload, :max_age => 500)
|
172
|
+
assert_equal(original, loaded)
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_expired
|
176
|
+
original = {:message => 'The files are _in_ the computer?'}
|
177
|
+
payload = @serializer.dumps(original)
|
178
|
+
assert_not_equal(payload, original)
|
179
|
+
sleep(2)
|
180
|
+
assert_raise(Itsdangerousr::SignatureExpired, "Timestamp should be too old") {
|
181
|
+
@serializer.loads(payload, :max_age => 1)
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
class URLSafeTimedSerializerTests < Test::Unit::TestCase
|
190
|
+
|
191
|
+
def setup
|
192
|
+
@serializer = Itsdangerousr::URLSafeTimedSerializer.new('secret')
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_from_python
|
196
|
+
original = {:message => 'this better work'}
|
197
|
+
from_python = 'eyJtZXNzYWdlIjoidGhpcyBiZXR0ZXIgd29yayJ9.BuZqLQ.bjbIUg3pAgW4URRFHWkzIrC4OgU'
|
198
|
+
loaded = @serializer.loads(from_python, :max_age => 60*60*24*365*100)
|
199
|
+
assert_equal(original, loaded)
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_expired
|
203
|
+
original = {:message => 'The files are _in_ the computer?'}
|
204
|
+
payload = @serializer.dumps(original)
|
205
|
+
assert_not_equal(payload, original)
|
206
|
+
sleep(2)
|
207
|
+
assert_raise(Itsdangerousr::SignatureExpired, "Timestamp should be too old") {
|
208
|
+
@serializer.loads(payload, :max_age => 1)
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: itsdangerousr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marcus McCurdy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '>='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 1.6.0
|
19
|
+
name: json
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.12'
|
33
|
+
name: rdoc
|
34
|
+
prerelease: false
|
35
|
+
type: :development
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
name: bundler
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 2.0.1
|
61
|
+
name: jeweler
|
62
|
+
prerelease: false
|
63
|
+
type: :development
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.0.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
name: simplecov
|
76
|
+
prerelease: false
|
77
|
+
type: :development
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ~>
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 1.2.8
|
89
|
+
name: reek
|
90
|
+
prerelease: false
|
91
|
+
type: :development
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.2.8
|
97
|
+
description: Sometimes you just want to send some data to untrusted environments. But how to do this safely? The trick involves signing. Given a key only you know, you can cryptographically sign your data and hand it over to someone else. When you get the data back you can easily ensure that nobody tampered with it.
|
98
|
+
email: marcus.mccurdy@gmail.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files:
|
102
|
+
- LICENSE.txt
|
103
|
+
- README.md
|
104
|
+
files:
|
105
|
+
- .travis.yml
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- VERSION
|
111
|
+
- itsdangerousr.gemspec
|
112
|
+
- lib/itsdangerousr.rb
|
113
|
+
- test/helper.rb
|
114
|
+
- test/test_itsdangerousr.rb
|
115
|
+
homepage: http://github.com/volker48/itsdangerousr
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.4.1
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Python's itsdangerous ported to Ruby
|
139
|
+
test_files: []
|