rotp 1.6.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|