rotp 1.4.6 → 1.5.0

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