rotp 3.2.0 → 3.3.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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +26 -60
- data/README.md +33 -0
- data/lib/rotp/totp.rb +1 -0
- data/lib/rotp/version.rb +1 -1
- data/rotp.gemspec +4 -4
- data/spec/lib/rotp/hotp_spec.rb +1 -1
- data/spec/lib/rotp/totp_spec.rb +18 -97
- data/spec/spec_helper.rb +5 -0
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6966922a92bffb8bf74497e36d3f2e42e63f47a5
|
4
|
+
data.tar.gz: 0dbef9d1ed476e382b243d232240458d396b3593
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3af99a2dcadd3591d235ecb8f1d0752ceadaa0b8649c1851163a7d0ce7e88bf5a564d1d32b6f3a2e2a3b290e36fd378b39e1d0a3a5225ba7ead905e3c207806f
|
7
|
+
data.tar.gz: c9504564ca0ec36d3efac011b9079d33d5038bf917ff4f5d6700556a71956fab49710304551e2d2d0a23f6c6a91dcdb7a296dd26468f6a78de8dc0584a0c35da
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,75 +1,41 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rotp (2.
|
4
|
+
rotp (3.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
8
8
|
specs:
|
9
|
-
celluloid (0.16.0)
|
10
|
-
timers (~> 4.0.0)
|
11
|
-
coderay (1.1.0)
|
12
9
|
diff-lcs (1.2.5)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
thor (>= 0.18.1)
|
24
|
-
guard-compat (1.2.0)
|
25
|
-
guard-rspec (4.5.0)
|
26
|
-
guard (~> 2.1)
|
27
|
-
guard-compat (~> 1.1)
|
28
|
-
rspec (>= 2.99.0, < 4.0)
|
29
|
-
hitimes (1.2.2)
|
30
|
-
listen (2.8.5)
|
31
|
-
celluloid (>= 0.15.2)
|
32
|
-
rb-fsevent (>= 0.9.3)
|
33
|
-
rb-inotify (>= 0.9)
|
34
|
-
lumberjack (1.0.9)
|
35
|
-
method_source (0.8.2)
|
36
|
-
nenv (0.1.1)
|
37
|
-
notiffany (0.0.3)
|
38
|
-
nenv (~> 0.1)
|
39
|
-
shellany (~> 0.0)
|
40
|
-
pry (0.10.1)
|
41
|
-
coderay (~> 1.1.0)
|
42
|
-
method_source (~> 0.8.1)
|
43
|
-
slop (~> 3.4)
|
44
|
-
rake (10.4.2)
|
45
|
-
rb-fsevent (0.9.4)
|
46
|
-
rb-inotify (0.9.5)
|
47
|
-
ffi (>= 0.5.0)
|
48
|
-
rspec (3.1.0)
|
49
|
-
rspec-core (~> 3.1.0)
|
50
|
-
rspec-expectations (~> 3.1.0)
|
51
|
-
rspec-mocks (~> 3.1.0)
|
52
|
-
rspec-core (3.1.7)
|
53
|
-
rspec-support (~> 3.1.0)
|
54
|
-
rspec-expectations (3.1.2)
|
10
|
+
docile (1.1.5)
|
11
|
+
json (1.8.3)
|
12
|
+
rake (10.5.0)
|
13
|
+
rspec (3.5.0)
|
14
|
+
rspec-core (~> 3.5.0)
|
15
|
+
rspec-expectations (~> 3.5.0)
|
16
|
+
rspec-mocks (~> 3.5.0)
|
17
|
+
rspec-core (3.5.2)
|
18
|
+
rspec-support (~> 3.5.0)
|
19
|
+
rspec-expectations (3.5.0)
|
55
20
|
diff-lcs (>= 1.2.0, < 2.0)
|
56
|
-
rspec-support (~> 3.
|
57
|
-
rspec-mocks (3.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
21
|
+
rspec-support (~> 3.5.0)
|
22
|
+
rspec-mocks (3.5.0)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.5.0)
|
25
|
+
rspec-support (3.5.0)
|
26
|
+
simplecov (0.12.0)
|
27
|
+
docile (~> 1.1.0)
|
28
|
+
json (>= 1.8, < 3)
|
29
|
+
simplecov-html (~> 0.10.0)
|
30
|
+
simplecov-html (0.10.0)
|
31
|
+
timecop (0.8.1)
|
66
32
|
|
67
33
|
PLATFORMS
|
68
34
|
ruby
|
69
35
|
|
70
36
|
DEPENDENCIES
|
71
|
-
|
72
|
-
rake (~> 10.4)
|
37
|
+
rake (~> 10.5)
|
73
38
|
rotp!
|
74
|
-
rspec (~> 3.
|
75
|
-
|
39
|
+
rspec (~> 3.5)
|
40
|
+
simplecov (~> 0.12)
|
41
|
+
timecop (~> 0.8)
|
data/README.md
CHANGED
@@ -55,6 +55,39 @@ hotp.verify("316439", 1401) # => true
|
|
55
55
|
hotp.verify("316439", 1402) # => false
|
56
56
|
```
|
57
57
|
|
58
|
+
### Verifying a Time based OTP with drift
|
59
|
+
|
60
|
+
Some users devices may be slightly behind or ahead of the actual time. ROTP allows users to verify
|
61
|
+
an OTP code with an specific amount of 'drift'
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
totp = ROTP::TOTP.new("base32secret3232")
|
65
|
+
totp.now # => "492039"
|
66
|
+
|
67
|
+
# OTP verified for current time with 120 seconds allowed drift
|
68
|
+
totp.verify_with_drift("492039", 60, Time.now - 30) # => true
|
69
|
+
totp.verify_with_drift("492039", 60, Time.now - 90) # => false
|
70
|
+
```
|
71
|
+
|
72
|
+
### Preventing reuse of Time based OTP's
|
73
|
+
|
74
|
+
In order to prevent reuse of time based tokens within the interval window (default 30 seconds)
|
75
|
+
it is necessary to store the last time an OTP was used. The following is an example of this in action:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
User.find(someUserID)
|
79
|
+
totp = ROTP::TOTP.new(user.otp_secret)
|
80
|
+
totp.now # => "492039"
|
81
|
+
|
82
|
+
user.last_otp_at # => 1472145530
|
83
|
+
|
84
|
+
# Verify the OTP
|
85
|
+
verified_at_timestamp = totp.verify_with_drift_and_prior("492039", 0, user.last_otp_at) #=> 1472145760
|
86
|
+
# Store this on the user's account
|
87
|
+
user.update(last_otp_at: verified_at_timestamp)
|
88
|
+
verified_at_timestamp = totp.verify_with_drift_and_prior("492039", 0, user.last_otp_at) #=> false
|
89
|
+
```
|
90
|
+
|
58
91
|
### Generating a Base32 Secret key
|
59
92
|
|
60
93
|
```ruby
|
data/lib/rotp/totp.rb
CHANGED
@@ -87,6 +87,7 @@ module ROTP
|
|
87
87
|
period: interval == 30 ? nil : interval,
|
88
88
|
issuer: issuer,
|
89
89
|
digits: digits == DEFAULT_DIGITS ? nil : digits,
|
90
|
+
algorithm: digest.upcase == 'SHA1' ? nil : digest.upcase,
|
90
91
|
}
|
91
92
|
encode_params("otpauth://totp/#{issuer_string}#{URI.encode(name)}", params)
|
92
93
|
end
|
data/lib/rotp/version.rb
CHANGED
data/rotp.gemspec
CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_development_dependency '
|
23
|
-
s.add_development_dependency '
|
24
|
-
s.add_development_dependency '
|
25
|
-
s.add_development_dependency '
|
22
|
+
s.add_development_dependency 'rake', '~> 10.5'
|
23
|
+
s.add_development_dependency 'rspec', '~> 3.5'
|
24
|
+
s.add_development_dependency 'timecop', '~> 0.8'
|
25
|
+
s.add_development_dependency 'simplecov', '~> 0.12'
|
26
26
|
end
|
data/spec/lib/rotp/hotp_spec.rb
CHANGED
data/spec/lib/rotp/totp_spec.rb
CHANGED
@@ -41,7 +41,7 @@ RSpec.describe ROTP::TOTP do
|
|
41
41
|
let(:token) { 68212 }
|
42
42
|
|
43
43
|
it 'raises an error' do
|
44
|
-
expect { verification }.to raise_error
|
44
|
+
expect { verification }.to raise_error(ArgumentError)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -142,37 +142,29 @@ RSpec.describe ROTP::TOTP do
|
|
142
142
|
expect(params['period'].first).to eq '60'
|
143
143
|
end
|
144
144
|
end
|
145
|
-
end
|
146
145
|
|
147
|
-
|
148
|
-
|
149
|
-
let(:drift) { 0 }
|
150
|
-
|
151
|
-
context 'numeric token' do
|
152
|
-
let(:token) { 68212 }
|
146
|
+
context 'with custom digest' do
|
147
|
+
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digest: 'sha256' }
|
153
148
|
|
154
|
-
it '
|
155
|
-
|
156
|
-
expect { verification }.to raise_error
|
149
|
+
it 'has the correct format' do
|
150
|
+
expect(uri).to match %r{\Aotpauth:\/\/totp.+}
|
157
151
|
end
|
158
|
-
end
|
159
152
|
|
160
|
-
|
161
|
-
|
153
|
+
it 'includes the secret as parameter' do
|
154
|
+
expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
|
155
|
+
end
|
162
156
|
|
163
|
-
it '
|
164
|
-
|
165
|
-
expect(verification).to be_falsey
|
157
|
+
it 'includes the digest as algorithm parameter' do
|
158
|
+
expect(params['algorithm'].first).to eq 'SHA256'
|
166
159
|
end
|
167
160
|
end
|
168
161
|
|
169
|
-
|
170
|
-
|
162
|
+
end
|
163
|
+
|
164
|
+
describe '#verify_with_drift' do
|
165
|
+
let(:verification) { totp.verify_with_drift token, drift, now }
|
166
|
+
let(:drift) { 0 }
|
171
167
|
|
172
|
-
it 'is true' do
|
173
|
-
expect(verification).to be_truthy
|
174
|
-
end
|
175
|
-
end
|
176
168
|
|
177
169
|
context 'slightly old number' do
|
178
170
|
let(:token) { totp.at now - 30 }
|
@@ -227,88 +219,15 @@ RSpec.describe ROTP::TOTP do
|
|
227
219
|
let(:drift) { 0 }
|
228
220
|
let(:prior) { nil }
|
229
221
|
|
230
|
-
context 'numeric token' do
|
231
|
-
let(:token) { 68212 }
|
232
|
-
|
233
|
-
it 'raises an error' do
|
234
|
-
# In the "old" specs this was not tested due to a typo. What is the expected behavior here?
|
235
|
-
expect { verification }.to raise_error
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
context 'unpadded string token' do
|
240
|
-
let(:token) { '68212' }
|
241
|
-
|
242
|
-
it 'is false' do
|
243
|
-
# Not sure whether this should be tested. It didn't exist in the "old" specs
|
244
|
-
expect(verification).to be_falsey
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
context 'correctly padded string token' do
|
249
|
-
let(:token) { '068212' }
|
250
|
-
|
251
|
-
it 'is true' do
|
252
|
-
expect(verification).to be_truthy
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
context 'slightly old number' do
|
257
|
-
let(:token) { totp.at now - 30 }
|
258
|
-
let(:drift) { 60 }
|
259
|
-
|
260
|
-
it 'is true' do
|
261
|
-
expect(verification).to be_truthy
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
context 'slightly new number' do
|
266
|
-
let(:token) { totp.at now + 60 }
|
267
|
-
let(:drift) { 60 }
|
268
|
-
|
269
|
-
it 'is true' do
|
270
|
-
expect(verification).to be_truthy
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
context 'outside of drift range' do
|
275
|
-
let(:token) { totp.at now - 60 }
|
276
|
-
let(:drift) { 30 }
|
277
|
-
|
278
|
-
it 'is false' do
|
279
|
-
expect(verification).to be_falsey
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
context 'drift is not multiple of TOTP interval' do
|
284
|
-
context 'slightly old number' do
|
285
|
-
let(:token) { totp.at now - 45 }
|
286
|
-
let(:drift) { 45 }
|
287
|
-
|
288
|
-
it 'is true' do
|
289
|
-
expect(verification).to be_truthy
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
context 'slightly new number' do
|
294
|
-
let(:token) { totp.at now + 40 }
|
295
|
-
let(:drift) { 40 }
|
296
|
-
|
297
|
-
it 'is true' do
|
298
|
-
expect(verification).to be_truthy
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
222
|
context 'with a prior verify' do
|
304
223
|
let(:prior) { totp.verify_with_drift_and_prior '068212', 0, nil, now }
|
305
224
|
|
306
225
|
it 'returns a timecode' do
|
226
|
+
expect(prior).to be_kind_of(Integer)
|
307
227
|
expect(prior).to be_within(30).of(now.to_i)
|
308
228
|
end
|
309
229
|
|
310
230
|
context 'reusing same token' do
|
311
|
-
|
312
231
|
it 'is false' do
|
313
232
|
expect(verification).to be_falsy
|
314
233
|
end
|
@@ -319,6 +238,8 @@ RSpec.describe ROTP::TOTP do
|
|
319
238
|
let(:drift) { 40 }
|
320
239
|
|
321
240
|
it 'is true' do
|
241
|
+
expect(verification).to be_kind_of(Integer)
|
242
|
+
expect(verification).to be_within(30).of(now.to_i)
|
322
243
|
expect(verification).to be_truthy
|
323
244
|
end
|
324
245
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rotp'
|
2
2
|
require 'timecop'
|
3
|
+
require 'simplecov'
|
3
4
|
|
4
5
|
RSpec.configure do |config|
|
5
6
|
config.disable_monkey_patching!
|
@@ -11,3 +12,7 @@ RSpec.configure do |config|
|
|
11
12
|
Timecop.return
|
12
13
|
end
|
13
14
|
end
|
15
|
+
|
16
|
+
SimpleCov.start
|
17
|
+
|
18
|
+
require_relative '../lib/rotp'
|
metadata
CHANGED
@@ -1,71 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rotp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Percival
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '10.5'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '10.5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '3.5'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '3.5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: timecop
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0.8'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0.8'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: simplecov
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.12'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
68
|
+
version: '0.12'
|
69
69
|
description: Works for both HOTP and TOTP, and includes QR Code provisioning
|
70
70
|
email:
|
71
71
|
- mark@markpercival.us
|