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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 925325389a0599d8e3d5fa61035be448cba2d26d
4
- data.tar.gz: e5639873115b3f91ff962250c1e0528e332a225c
3
+ metadata.gz: 5c42bc8b1ce8ab11bb556cef750ae50187a6640d
4
+ data.tar.gz: 165cbc7edb89427967f2777b7d8705b45486f18d
5
5
  SHA512:
6
- metadata.gz: 5b377c8eedeb3de0c68ccbb8f2c6896bdfe58bf62259129583bc1933685e4f5a9ee34e3b9e1558f747935f2b1214ed08b2fba64061ba81d6f648885a04eaf4d4
7
- data.tar.gz: e781069e1760f538924cecf5476eecabdf19423e8289a933ff824ce7b3c788b8369835d5f91a804ef3a9267e62688fd06a86d9fa300dddff65b2dc06d3e5aa79
6
+ metadata.gz: ac2979aa19949bedb563e5fc407171efec7d80785e0246d19fed92bc78fca4b64eacdd1ef5c01a848f37c77a7d5bbbc03dc84e1e49d5f84bd55710ffc36b33af
7
+ data.tar.gz: 8349669bdc320ff49ec55eb487eaaf38c352a52faa95bde3cfa8fde06b5ed7b889db670a254d2b2fd8b36286cb13bbaf03b86579466430e6b3d4850db1b0a770
@@ -3,4 +3,3 @@ rvm:
3
3
  - 2.1.0
4
4
  - 2.0.0
5
5
  - 1.9.3
6
- - 1.8.7
@@ -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) # => 55283
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)
@@ -40,11 +40,7 @@ module ROTP
40
40
  end
41
41
 
42
42
  def decode_quint(q)
43
- if d = CHARS.index(q.downcase)
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
@@ -4,7 +4,7 @@ module ROTP
4
4
  # @param [Integer] count counter
5
5
  # @option [Boolean] padding (false) Issue the number as a 0 padded string
6
6
  # @returns [Integer] OTP
7
- def at(count, padding=false)
7
+ def at(count, padding=true)
8
8
  generate_otp(count, padding)
9
9
  end
10
10
 
@@ -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=false)
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.to_i == generated
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
@@ -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 (false) Issue the number as a 0 padded string
19
- def at(time, padding=false)
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=false)
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/Integer] otp the OTP to check against
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)
@@ -1,3 +1,3 @@
1
1
  module ROTP
2
- VERSION = "1.6.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -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
@@ -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 raise_error(ROTP::Base32::Base32Error)
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"
@@ -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 number given a time" do
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 as a string" do
12
- subject.at(@counter, true).should == "161024"
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
- subject.verify(161024, @counter).should be_true
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
@@ -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
- it "should generate a number as a padded string" do
12
- subject.at(@now, true).should == "068212"
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 a number" do
15
- subject.verify(68212, @now).should be_true
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("68212", @now).should be_true
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(68212, 0, @now).should be_true
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("68212", 0, @now).should be_true
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 ==(50471)
82
- totp.at(1234567890).should ==(5924)
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: 1.6.1
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-01-03 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
40
+ version: 2.13.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: timecop
43
43
  requirement: !ruby/object:Gem::Requirement