rotp 1.6.1 → 2.0.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/.travis.yml +0 -1
- data/README.markdown +21 -8
- data/lib/rotp/base32.rb +1 -5
- data/lib/rotp/hotp.rb +1 -1
- data/lib/rotp/otp.rb +16 -2
- data/lib/rotp/totp.rb +4 -4
- data/lib/rotp/version.rb +1 -1
- data/rotp.gemspec +2 -2
- data/spec/base_spec.rb +2 -1
- data/spec/hotp_spec.rb +24 -22
- data/spec/totp_spec.rb +23 -15
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c42bc8b1ce8ab11bb556cef750ae50187a6640d
|
4
|
+
data.tar.gz: 165cbc7edb89427967f2777b7d8705b45486f18d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac2979aa19949bedb563e5fc407171efec7d80785e0246d19fed92bc78fca4b64eacdd1ef5c01a848f37c77a7d5bbbc03dc84e1e49d5f84bd55710ffc36b33af
|
7
|
+
data.tar.gz: 8349669bdc320ff49ec55eb487eaaf38c352a52faa95bde3cfa8fde06b5ed7b889db670a254d2b2fd8b36286cb13bbaf03b86579466430e6b3d4850db1b0a770
|
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -25,23 +25,23 @@ This is compatible with Google Authenticator apps available for Android and iPho
|
|
25
25
|
### Time based OTP's
|
26
26
|
|
27
27
|
totp = ROTP::TOTP.new("base32secret3232")
|
28
|
-
totp.now # => 492039
|
28
|
+
totp.now # => "492039"
|
29
29
|
|
30
30
|
# OTP verified for current time
|
31
|
-
totp.verify(492039) # => true
|
31
|
+
totp.verify("492039") # => true
|
32
32
|
sleep 30
|
33
|
-
totp.verify(492039) # => false
|
33
|
+
totp.verify("492039") # => false
|
34
34
|
|
35
35
|
### Counter based OTP's
|
36
36
|
|
37
37
|
hotp = ROTP::HOTP.new("base32secretkey3232")
|
38
|
-
hotp.at(0) # => 260182
|
39
|
-
hotp.at(1) # =>
|
40
|
-
hotp.at(1401) # => 316439
|
38
|
+
hotp.at(0) # => "260182"
|
39
|
+
hotp.at(1) # => "055283"
|
40
|
+
hotp.at(1401) # => "316439"
|
41
41
|
|
42
42
|
# OTP verified with a counter
|
43
|
-
totp.verify(316439, 1401) # => true
|
44
|
-
totp.verify(316439, 1402) # => false
|
43
|
+
totp.verify("316439", 1401) # => true
|
44
|
+
totp.verify("316439", 1402) # => false
|
45
45
|
|
46
46
|
### Generating a Base32 Secret key
|
47
47
|
|
@@ -92,6 +92,19 @@ Now run the following and compare the output
|
|
92
92
|
|
93
93
|
### Changelog
|
94
94
|
|
95
|
+
|
96
|
+
#### 2.0.0
|
97
|
+
|
98
|
+
- Move to only comparing string OTP's.
|
99
|
+
|
100
|
+
#### 1.7.1
|
101
|
+
|
102
|
+
- Revert to former API
|
103
|
+
|
104
|
+
#### 1.7.0
|
105
|
+
|
106
|
+
- Move to only comparing string OTP's. See mdp/rotp/issues/32 - Moved to 2.0.0
|
107
|
+
|
95
108
|
#### 1.6.1
|
96
109
|
|
97
110
|
- Remove deprecation warning in Ruby 2.1.0 (@ylansegal)
|
data/lib/rotp/base32.rb
CHANGED
@@ -40,11 +40,7 @@ module ROTP
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def decode_quint(q)
|
43
|
-
|
44
|
-
d
|
45
|
-
else
|
46
|
-
raise Base32Error, "Invalid Base32 Character - '#{q}'"
|
47
|
-
end
|
43
|
+
CHARS.index(q.downcase) or raise(Base32Error, "Invalid Base32 Character - '#{q}'")
|
48
44
|
end
|
49
45
|
|
50
46
|
end
|
data/lib/rotp/hotp.rb
CHANGED
data/lib/rotp/otp.rb
CHANGED
@@ -20,7 +20,7 @@ module ROTP
|
|
20
20
|
# @option padded [Boolean] (false) Output the otp as a 0 padded string
|
21
21
|
# Usually either the counter, or the computed integer
|
22
22
|
# based on the Unix timestamp
|
23
|
-
def generate_otp(input, padded=
|
23
|
+
def generate_otp(input, padded=true)
|
24
24
|
hmac = OpenSSL::HMAC.digest(
|
25
25
|
OpenSSL::Digest.new(digest),
|
26
26
|
byte_secret,
|
@@ -42,7 +42,10 @@ module ROTP
|
|
42
42
|
private
|
43
43
|
|
44
44
|
def verify(input, generated)
|
45
|
-
input.
|
45
|
+
unless input.is_a?(String) && generated.is_a?(String)
|
46
|
+
raise ArgumentError, "ROTP only verifies strings - See: https://github.com/mdp/rotp/issues/32"
|
47
|
+
end
|
48
|
+
time_constant_compare(input, generated)
|
46
49
|
end
|
47
50
|
|
48
51
|
def byte_secret
|
@@ -74,5 +77,16 @@ module ROTP
|
|
74
77
|
uri + params_str
|
75
78
|
end
|
76
79
|
|
80
|
+
private
|
81
|
+
|
82
|
+
# constant-time compare the strings
|
83
|
+
def time_constant_compare(a, b)
|
84
|
+
return false if a.empty? || b.empty? || a.bytesize != b.bytesize
|
85
|
+
l = a.unpack "C#{a.bytesize}"
|
86
|
+
res = 0
|
87
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
88
|
+
res == 0
|
89
|
+
end
|
90
|
+
|
77
91
|
end
|
78
92
|
end
|
data/lib/rotp/totp.rb
CHANGED
@@ -15,8 +15,8 @@ module ROTP
|
|
15
15
|
# Accepts either a Unix timestamp integer or a Time object.
|
16
16
|
# Time objects will be adjusted to UTC automatically
|
17
17
|
# @param [Time/Integer] time the time to generate an OTP for
|
18
|
-
# @option [Boolean] padding (
|
19
|
-
def at(time, padding=
|
18
|
+
# @option [Boolean] padding (true) Issue the number as a 0 padded string
|
19
|
+
def at(time, padding=true)
|
20
20
|
unless time.class == Time
|
21
21
|
time = Time.at(time.to_i)
|
22
22
|
end
|
@@ -25,7 +25,7 @@ module ROTP
|
|
25
25
|
|
26
26
|
# Generate the current time OTP
|
27
27
|
# @return [Integer] the OTP as an integer
|
28
|
-
def now(padding=
|
28
|
+
def now(padding=true)
|
29
29
|
generate_otp(timecode(Time.now), padding)
|
30
30
|
end
|
31
31
|
|
@@ -37,7 +37,7 @@ module ROTP
|
|
37
37
|
|
38
38
|
# Verifies the OTP passed in against the current time OTP
|
39
39
|
# and adjacent intervals up to +drift+.
|
40
|
-
# @param [String
|
40
|
+
# @param [String] otp the OTP to check against
|
41
41
|
# @param [Integer] drift the number of seconds that the client
|
42
42
|
# and server are allowed to drift apart
|
43
43
|
def verify_with_drift(otp, drift, time = Time.now)
|
data/lib/rotp/version.rb
CHANGED
data/rotp.gemspec
CHANGED
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
|
-
s.add_development_dependency('rake')
|
24
|
-
s.add_development_dependency('rspec')
|
23
|
+
s.add_development_dependency('rake', '~>10.1.0')
|
24
|
+
s.add_development_dependency('rspec', '~>2.13.0')
|
25
25
|
if RUBY_VERSION < "1.9"
|
26
26
|
s.add_development_dependency('timecop', "~>0.5.9.2")
|
27
27
|
else
|
data/spec/base_spec.rb
CHANGED
@@ -9,7 +9,8 @@ describe "the Base32 implementation" do
|
|
9
9
|
ROTP::Base32.random_base32(32).length.should == 32
|
10
10
|
end
|
11
11
|
it "raise a sane error on a bad decode" do
|
12
|
-
expect { ROTP::Base32.decode("4BCDEFG234BCDEF1") }.to
|
12
|
+
expect { ROTP::Base32.decode("4BCDEFG234BCDEF1") }.to \
|
13
|
+
raise_error(ROTP::Base32::Base32Error, "Invalid Base32 Character - '1'")
|
13
14
|
end
|
14
15
|
it "should correctly decode a string" do
|
15
16
|
ROTP::Base32.decode("F").unpack('H*').first.should == "28"
|
data/spec/hotp_spec.rb
CHANGED
@@ -5,14 +5,16 @@ describe ROTP::HOTP do
|
|
5
5
|
|
6
6
|
subject { ROTP::HOTP.new('a' * 32) }
|
7
7
|
|
8
|
-
it "should generate a
|
9
|
-
subject.at(@counter).should == 161024
|
8
|
+
it "should generate a string OTP given a count" do
|
9
|
+
subject.at(@counter).should == "161024"
|
10
10
|
end
|
11
|
-
it "should generate a number
|
12
|
-
subject.at(@counter,
|
11
|
+
it "should generate a number if padding is set to false" do
|
12
|
+
subject.at(@counter, false).should == 161024
|
13
13
|
end
|
14
|
-
it "should verify a number" do
|
15
|
-
|
14
|
+
it "should not verify a number" do
|
15
|
+
expect {
|
16
|
+
subject.verify(161024, @counter)
|
17
|
+
}.to raise_error
|
16
18
|
end
|
17
19
|
it "should verify a string" do
|
18
20
|
subject.verify("161024", @counter).should be_true
|
@@ -26,16 +28,16 @@ describe ROTP::HOTP do
|
|
26
28
|
|
27
29
|
context "with retries" do
|
28
30
|
it "should verify that retry is a valid number" do
|
29
|
-
subject.verify_with_retries(161024, @counter, -1).should be_false
|
30
|
-
subject.verify_with_retries(161024, @counter, 0).should be_false
|
31
|
+
subject.verify_with_retries("161024", @counter, -1).should be_false
|
32
|
+
subject.verify_with_retries("161024", @counter, 0).should be_false
|
31
33
|
end
|
32
34
|
|
33
35
|
it "should verify up to the total number of retries and return the counter" do
|
34
|
-
subject.verify_with_retries(161024, @counter - 10, 10).should == @counter
|
36
|
+
subject.verify_with_retries("161024", @counter - 10, 10).should == @counter
|
35
37
|
end
|
36
38
|
|
37
39
|
it "should verify that retry is a valid number" do
|
38
|
-
subject.verify_with_retries(161024, @counter - 20, 10).should be_false
|
40
|
+
subject.verify_with_retries("161024", @counter - 20, 10).should be_false
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -45,20 +47,20 @@ describe "HOTP example values from the rfc" do
|
|
45
47
|
# 12345678901234567890 in Base32
|
46
48
|
# GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
|
47
49
|
hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
|
48
|
-
hotp.at(0).should ==(755224)
|
49
|
-
hotp.at(1).should ==(287082)
|
50
|
-
hotp.at(2).should ==(359152)
|
51
|
-
hotp.at(3).should ==(969429)
|
52
|
-
hotp.at(4).should ==(338314)
|
53
|
-
hotp.at(5).should ==(254676)
|
54
|
-
hotp.at(6).should ==(287922)
|
55
|
-
hotp.at(7).should ==(162583)
|
56
|
-
hotp.at(8).should ==(399871)
|
57
|
-
hotp.at(9).should ==(520489)
|
50
|
+
hotp.at(0).should ==("755224")
|
51
|
+
hotp.at(1).should ==("287082")
|
52
|
+
hotp.at(2).should ==("359152")
|
53
|
+
hotp.at(3).should ==("969429")
|
54
|
+
hotp.at(4).should ==("338314")
|
55
|
+
hotp.at(5).should ==("254676")
|
56
|
+
hotp.at(6).should ==("287922")
|
57
|
+
hotp.at(7).should ==("162583")
|
58
|
+
hotp.at(8).should ==("399871")
|
59
|
+
hotp.at(9).should ==("520489")
|
58
60
|
end
|
59
61
|
it "should verify an OTP and not allow reuse" do
|
60
62
|
hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
|
61
|
-
hotp.verify(520489, 9).should be_true
|
62
|
-
hotp.verify(520489, 10).should be_false
|
63
|
+
hotp.verify("520489", 9).should be_true
|
64
|
+
hotp.verify("520489", 10).should be_false
|
63
65
|
end
|
64
66
|
end
|
data/spec/totp_spec.rb
CHANGED
@@ -6,16 +6,24 @@ describe ROTP::TOTP do
|
|
6
6
|
subject { ROTP::TOTP.new("JBSWY3DPEHPK3PXP") }
|
7
7
|
|
8
8
|
it "should generate a number given a number" do
|
9
|
-
subject.at(@now).should == 68212
|
9
|
+
subject.at(@now, false).should == 68212
|
10
10
|
end
|
11
|
-
|
12
|
-
|
11
|
+
|
12
|
+
it "should generate a number as a padded string by default" do
|
13
|
+
subject.at(@now).should == "068212"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Users of the lib
|
17
|
+
it "should not verify a number" do
|
18
|
+
expect {
|
19
|
+
subject.verify(68212, @now)
|
20
|
+
}.to raise_error
|
13
21
|
end
|
14
|
-
it "should verify
|
15
|
-
subject.verify(68212, @now).should
|
22
|
+
it "should not verify an unpadded string" do
|
23
|
+
subject.verify("68212", @now).should be_false
|
16
24
|
end
|
17
25
|
it "should verify a string" do
|
18
|
-
subject.verify("
|
26
|
+
subject.verify("068212", @now).should be_true
|
19
27
|
end
|
20
28
|
|
21
29
|
it "should output its provisioning URI" do
|
@@ -50,10 +58,10 @@ describe ROTP::TOTP do
|
|
50
58
|
|
51
59
|
context "with drift" do
|
52
60
|
it "should verify a number" do
|
53
|
-
subject.verify_with_drift(
|
61
|
+
subject.verify_with_drift("068212", 0, @now).should be_true
|
54
62
|
end
|
55
63
|
it "should verify a string" do
|
56
|
-
subject.verify_with_drift("
|
64
|
+
subject.verify_with_drift("068212", 0, @now).should be_true
|
57
65
|
end
|
58
66
|
it "should verify a slightly old number" do
|
59
67
|
subject.verify_with_drift(subject.at(@now - 30), 60, @now).should be_true
|
@@ -78,30 +86,30 @@ end
|
|
78
86
|
describe "TOTP example values from the documented output" do
|
79
87
|
it "should match the RFC" do
|
80
88
|
totp = ROTP::TOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
|
81
|
-
totp.at(1111111111).should ==(
|
82
|
-
totp.at(1234567890).should ==(
|
83
|
-
totp.at(2000000000).should ==(279037)
|
89
|
+
totp.at(1111111111).should ==("050471")
|
90
|
+
totp.at(1234567890).should ==("005924")
|
91
|
+
totp.at(2000000000).should ==("279037")
|
84
92
|
end
|
85
93
|
|
86
94
|
it "should match the Google Authenticator output" do
|
87
95
|
totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
|
88
96
|
Timecop.freeze(Time.at(1297553958)) do
|
89
|
-
totp.now.should ==(102705)
|
97
|
+
totp.now.should ==("102705")
|
90
98
|
end
|
91
99
|
end
|
92
100
|
it "should match Dropbox 26 char secret output" do
|
93
101
|
totp = ROTP::TOTP.new("tjtpqea6a42l56g5eym73go2oa")
|
94
102
|
Timecop.freeze(Time.at(1378762454)) do
|
95
|
-
totp.now.should ==(747864)
|
103
|
+
totp.now.should ==("747864")
|
96
104
|
end
|
97
105
|
end
|
98
106
|
it "should validate a time based OTP" do
|
99
107
|
totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
|
100
108
|
Timecop.freeze(Time.at(1297553958)) do
|
101
|
-
totp.verify(102705).should be_true
|
109
|
+
totp.verify("102705").should be_true
|
102
110
|
end
|
103
111
|
Timecop.freeze(Time.at(1297553958 + 30)) do
|
104
|
-
totp.verify(102705).should be_false
|
112
|
+
totp.verify("102705").should be_false
|
105
113
|
end
|
106
114
|
end
|
107
115
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rotp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.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: 2014-
|
11
|
+
date: 2014-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
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.1.0
|
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.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
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: 2.13.0
|
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: 2.13.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: timecop
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|