rotp 1.4.6 → 1.5.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: e9938a2125556d7dcd7b8eae64d58d28e90a6d0f
4
- data.tar.gz: 053694aa169605804930a516e41294e3b78b0fe4
3
+ metadata.gz: 8a9c9c9d92e7df6e74c912f064f44aed0a2c6345
4
+ data.tar.gz: 664591b20546f52e2f216759bd0a0516ca1c6be8
5
5
  SHA512:
6
- metadata.gz: 62890466fe02ab700ce21c7f882a6f25fddeb9e35b0bd51ef5bd9567ae4a1decea947dc729c74852d8e0f471474686ac4fdad08f6c51600e83b1abb8608f8464
7
- data.tar.gz: fedda9e325a1e7513907ee8a1b2314a97b0fb55e1c6f848fc122f73031473dfad630220a943ba27af700f7eeb2418d0d0951d42f488cee2839ccebf8ab6fa609
6
+ metadata.gz: 72190c4a66d89ea2220550701fb2f7bb6b932ca93603cadad1b054b07e3e135cd752d61a87c78a87e59e71cea5cb89cae02784d508b8c51ba65797150e6d2421
7
+ data.tar.gz: 6d1a508e6379354f303338c7b42a393522602128c354fe2808ebe0b397a348588365921d48bf145ad6faed795b9428a48374aeddcfc3f9dc529855b84b2948ca
data/README.markdown CHANGED
@@ -1,7 +1,7 @@
1
1
  # ROTP - The Ruby One Time Password Library
2
2
  [![Build Status](https://secure.travis-ci.org/mdp/rotp.png)](http://travis-ci.org/mdp/rotp)
3
3
 
4
- A ruby library for generating one time passwords according to [ RFC 4226 ](http://tools.ietf.org/html/rfc4226) and the [ HOTP RFC ](http://tools.ietf.org/html/draft-mraihi-totp-timebased-00)
4
+ A ruby library for generating one time passwords (HOTP & TOTP) according to [ RFC 4226 ](http://tools.ietf.org/html/rfc4226) and [ RFC 6238 ](http://tools.ietf.org/html/rfc6238)
5
5
 
6
6
  This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail
7
7
 
@@ -92,6 +92,15 @@ Now run the following and compare the output
92
92
 
93
93
  ### Changelog
94
94
 
95
+ #### 1.5.0
96
+
97
+ - Add support for "issuer" parameter on provisioning url
98
+ - Add support for "period/interval" parameter on provisioning url
99
+
100
+ #### 1.4.6
101
+
102
+ - Revert to previous Base32
103
+
95
104
  #### 1.4.5
96
105
 
97
106
  - Fix and test correct implementation of Base32
data/lib/rotp/base32.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module ROTP
2
2
  class Base32
3
+ class Base32Error < RuntimeError; end
3
4
  CHARS = "abcdefghijklmnopqrstuvwxyz234567".each_char.to_a
4
5
 
5
6
  class << self
@@ -39,7 +40,11 @@ module ROTP
39
40
  end
40
41
 
41
42
  def decode_quint(q)
42
- CHARS.index(q.downcase)
43
+ if d = CHARS.index(q.downcase)
44
+ d
45
+ else
46
+ raise Base32Error, "Invalid Base32 Character - '#{q}'"
47
+ end
43
48
  end
44
49
 
45
50
  end
data/lib/rotp/hotp.rb CHANGED
@@ -22,7 +22,7 @@ module ROTP
22
22
  # @param [Integer] initial_count starting counter value, defaults to 0
23
23
  # @return [String] provisioning uri
24
24
  def provisioning_uri(name, initial_count=0)
25
- "otpauth://hotp/#{URI.encode(name)}?secret=#{secret}&counter=#{initial_count}"
25
+ encode_params("otpauth://hotp/#{URI.encode(name)}", :secret=>secret, :counter=>initial_count)
26
26
  end
27
27
 
28
28
  end
data/lib/rotp/otp.rb CHANGED
@@ -62,5 +62,17 @@ module ROTP
62
62
  result.reverse.join.rjust(padding, 0.chr)
63
63
  end
64
64
 
65
+ # A very simple param encoder
66
+ def encode_params(uri, params)
67
+ params_str = "?"
68
+ params.each do |k,v|
69
+ if v
70
+ params_str << "#{k}=#{CGI::escape(v.to_s)}&"
71
+ end
72
+ end
73
+ params_str.chop!
74
+ uri + params_str
75
+ end
76
+
65
77
  end
66
78
  end
data/lib/rotp/totp.rb CHANGED
@@ -1,12 +1,15 @@
1
+ DEFAULT_INTERVAL = 30
2
+
1
3
  module ROTP
2
4
  class TOTP < OTP
3
5
 
4
- attr_reader :interval
6
+ attr_reader :interval, :issuer
5
7
 
6
8
  # @option options [Integer] interval (30) the time interval in seconds for OTP
7
9
  # This defaults to 30 which is standard.
8
10
  def initialize(s, options = {})
9
- @interval = options[:interval] || 30
11
+ @interval = options[:interval] || DEFAULT_INTERVAL
12
+ @issuer = options[:issuer]
10
13
  super
11
14
  end
12
15
 
@@ -51,7 +54,8 @@ module ROTP
51
54
  # @param [String] name of the account
52
55
  # @return [String] provisioning uri
53
56
  def provisioning_uri(name)
54
- "otpauth://totp/#{URI.encode(name)}?secret=#{secret}"
57
+ encode_params("otpauth://totp/#{URI.encode(name)}",
58
+ :period => (interval==30 ? nil : interval), :issuer => issuer, :secret => secret)
55
59
  end
56
60
 
57
61
  private
data/lib/rotp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ROTP
2
- VERSION = "1.4.6"
2
+ VERSION = "1.5.0"
3
3
  end
data/spec/base_spec.rb CHANGED
@@ -8,6 +8,9 @@ describe "the Base32 implementation" do
8
8
  it "should be allow a specific length" do
9
9
  ROTP::Base32.random_base32(32).length.should == 32
10
10
  end
11
+ it "raise a sane error on a bad decode" do
12
+ expect { ROTP::Base32.decode("4BCDEFG234BCDEF1") }.to raise_error(ROTP::Base32::Base32Error)
13
+ end
11
14
  it "should correctly decode a string" do
12
15
  ROTP::Base32.decode("F").unpack('H*').first.should == "28"
13
16
  ROTP::Base32.decode("23").unpack('H*').first.should == "d6"
@@ -21,66 +24,3 @@ describe "the Base32 implementation" do
21
24
  end
22
25
  end
23
26
 
24
- describe "HOTP example values from the rfc" do
25
- it "should match the RFC" do
26
- # 12345678901234567890 in Bas32
27
- # GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
28
- hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
29
- hotp.at(0).should ==(755224)
30
- hotp.at(1).should ==(287082)
31
- hotp.at(2).should ==(359152)
32
- hotp.at(3).should ==(969429)
33
- hotp.at(4).should ==(338314)
34
- hotp.at(5).should ==(254676)
35
- hotp.at(6).should ==(287922)
36
- hotp.at(7).should ==(162583)
37
- hotp.at(8).should ==(399871)
38
- hotp.at(9).should ==(520489)
39
- end
40
- it "should verify an OTP and now allow reuse" do
41
- hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
42
- hotp.verify(520489, 9).should be_true
43
- hotp.verify(520489, 10).should be_false
44
- end
45
- it "should output its provisioning URI" do
46
- hotp = ROTP::HOTP.new("wrn3pqx5uqxqvnqr")
47
- hotp.provisioning_uri('mark@percival').should == "otpauth://hotp/mark@percival?secret=wrn3pqx5uqxqvnqr&counter=0"
48
- end
49
- end
50
-
51
- describe "TOTP example values from the rfc" do
52
- it "should match the RFC" do
53
- totp = ROTP::TOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
54
- totp.at(1111111111).should ==(50471)
55
- totp.at(1234567890).should ==(5924)
56
- totp.at(2000000000).should ==(279037)
57
- end
58
-
59
- it "should match the Google Authenticator output" do
60
- totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
61
- Timecop.freeze(Time.at(1297553958)) do
62
- totp.now.should ==(102705)
63
- end
64
- end
65
- it "should match Dropbox 26 char secret output" do
66
- totp = ROTP::TOTP.new("tjtpqea6a42l56g5eym73go2oa")
67
- Timecop.freeze(Time.at(1378762454)) do
68
- totp.now.should ==(747864)
69
- end
70
- end
71
- it "should validate a time based OTP" do
72
- totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
73
- Timecop.freeze(Time.at(1297553958)) do
74
- totp.verify(102705).should be_true
75
- end
76
- Timecop.freeze(Time.at(1297553958 + 30)) do
77
- totp.verify(102705).should be_false
78
- end
79
- end
80
-
81
-
82
- it "should output its provisioning URI" do
83
- totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
84
- totp.provisioning_uri('mark@percival').should == "otpauth://totp/mark@percival?secret=wrn3pqx5uqxqvnqr"
85
- end
86
- end
data/spec/hotp_spec.rb CHANGED
@@ -17,4 +17,34 @@ describe ROTP::HOTP do
17
17
  it "should verify a string" do
18
18
  subject.verify("161024", @counter).should be_true
19
19
  end
20
+ it "should output its provisioning URI" do
21
+ url = subject.provisioning_uri('mark@percival')
22
+ params = CGI::parse(URI::parse(url).query)
23
+ url.should match(/otpauth:\/\/hotp.+/)
24
+ params["secret"].first.should == "a" * 32
25
+ end
26
+
27
+ end
28
+
29
+ describe "HOTP example values from the rfc" do
30
+ it "should match the RFC" do
31
+ # 12345678901234567890 in Base32
32
+ # GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
33
+ hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
34
+ hotp.at(0).should ==(755224)
35
+ hotp.at(1).should ==(287082)
36
+ hotp.at(2).should ==(359152)
37
+ hotp.at(3).should ==(969429)
38
+ hotp.at(4).should ==(338314)
39
+ hotp.at(5).should ==(254676)
40
+ hotp.at(6).should ==(287922)
41
+ hotp.at(7).should ==(162583)
42
+ hotp.at(8).should ==(399871)
43
+ hotp.at(9).should ==(520489)
44
+ end
45
+ it "should verify an OTP and not allow reuse" do
46
+ hotp = ROTP::HOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
47
+ hotp.verify(520489, 9).should be_true
48
+ hotp.verify(520489, 10).should be_false
49
+ end
20
50
  end
data/spec/totp_spec.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'cgi'
1
2
  require 'spec_helper'
2
3
 
3
4
  describe ROTP::TOTP do
@@ -18,6 +19,36 @@ describe ROTP::TOTP do
18
19
  subject.verify("68212", @now).should be_true
19
20
  end
20
21
 
22
+ it "should output its provisioning URI" do
23
+ url = subject.provisioning_uri('mark@percival')
24
+ params = CGI::parse(URI::parse(url).query)
25
+ url.should match(/otpauth:\/\/totp.+/)
26
+ params["secret"].first.should == "JBSWY3DPEHPK3PXP"
27
+ end
28
+
29
+ context "with issuer" do
30
+ subject { ROTP::TOTP.new("JBSWY3DPEHPK3PXP", :issuer => "FooCo") }
31
+ it "should output its provisioning URI with issuer" do
32
+ url = subject.provisioning_uri('mark@percival')
33
+ params = CGI::parse(URI::parse(url).query)
34
+ url.should match(/otpauth:\/\/totp.+/)
35
+ params["secret"].first.should == "JBSWY3DPEHPK3PXP"
36
+ params["issuer"].first.should == "FooCo"
37
+ end
38
+ end
39
+
40
+ context "with non default interval" do
41
+ subject { ROTP::TOTP.new("JBSWY3DPEHPK3PXP", :interval => 60) }
42
+ it "should output its provisioning URI with issuer" do
43
+ url = subject.provisioning_uri('mark@percival')
44
+ params = CGI::parse(URI::parse(url).query)
45
+ url.should match(/otpauth:\/\/totp.+/)
46
+ params["secret"].first.should == "JBSWY3DPEHPK3PXP"
47
+ params["period"].first.should == "60"
48
+ end
49
+ end
50
+
51
+
21
52
  context "with drift" do
22
53
  it "should verify a number" do
23
54
  subject.verify_with_drift(68212, 0, @now).should be_true
@@ -44,3 +75,34 @@ describe ROTP::TOTP do
44
75
  end
45
76
  end
46
77
  end
78
+
79
+ describe "TOTP example values from the documented output" do
80
+ it "should match the RFC" do
81
+ totp = ROTP::TOTP.new("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
82
+ totp.at(1111111111).should ==(50471)
83
+ totp.at(1234567890).should ==(5924)
84
+ totp.at(2000000000).should ==(279037)
85
+ end
86
+
87
+ it "should match the Google Authenticator output" do
88
+ totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
89
+ Timecop.freeze(Time.at(1297553958)) do
90
+ totp.now.should ==(102705)
91
+ end
92
+ end
93
+ it "should match Dropbox 26 char secret output" do
94
+ totp = ROTP::TOTP.new("tjtpqea6a42l56g5eym73go2oa")
95
+ Timecop.freeze(Time.at(1378762454)) do
96
+ totp.now.should ==(747864)
97
+ end
98
+ end
99
+ it "should validate a time based OTP" do
100
+ totp = ROTP::TOTP.new("wrn3pqx5uqxqvnqr")
101
+ Timecop.freeze(Time.at(1297553958)) do
102
+ totp.verify(102705).should be_true
103
+ end
104
+ Timecop.freeze(Time.at(1297553958 + 30)) do
105
+ totp.verify(102705).should be_false
106
+ end
107
+ end
108
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.6
4
+ version: 1.5.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: 2013-11-01 00:00:00.000000000 Z
11
+ date: 2013-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake