otp 0.0.9 → 0.0.10

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: 032b0bd99d66d97d8281cd5006409b1f482c2314
4
- data.tar.gz: ca0320a12fe5d73c907e8cd445dac6fd0219112f
3
+ metadata.gz: 81f98da9bacd6750a761f89590463ed0777b2b1d
4
+ data.tar.gz: 710bb562bde9f46efcdf89f5e9777839ba8dbf37
5
5
  SHA512:
6
- metadata.gz: f60da46723a92855571fcabbf0b8e0ad264093822516ebdd9d152181cb102280c92a3ce97a499b9bf661aac9adf6ff2694e2ae02294eb39d2e6d1be70d3eb8ed
7
- data.tar.gz: 8d90aba69fd87db84b94a3b0a374f817a8566d970be4820e868f16a1098b81e8de188247bc9b29c4f811a0e4779bb1c8cc43907aef5fc1b4fd974a7fd68e28bb
6
+ metadata.gz: a5921f7970174fafc13b4ce15de85f94772ae958338bab7645d9a5804e61b45a6e8ef1666e0a18075dfabb47e52d8356f6fe7a03575d6ac472b5471052a7c755
7
+ data.tar.gz: 85f05deafa16e5232185255572003d7c3c477ada47079dcd393262983694030f24cc7e540e54d503c4f19f132aee103b1cb5c3b277301efd771cb42f56adbd2c
@@ -39,8 +39,8 @@ Style/FormatString:
39
39
  Style/EachWithObject:
40
40
  Enabled: false
41
41
 
42
- Metrics/AbcSize:
43
- Max: 20
42
+ #Metrics/AbcSize:
43
+ # Max: 20
44
44
 
45
45
  Metrics/MethodLength:
46
46
  Max: 30
data/README.md CHANGED
@@ -47,11 +47,11 @@ You can use the last and post option parameters to verify several generations, i
47
47
 
48
48
  TOTP and HOTP algorithm details can be referred at the following URLs.
49
49
 
50
- * HOTP: An HMAC-Based One-Time Password Algorithm - http://tools.ietf.org/html/rfc4226
51
- * TOTP: Time-Based One-Time Password Algorithm - http://tools.ietf.org/html/rfc6238
50
+ * [HOTP: An HMAC-Based One-Time Password Algorithm](http://tools.ietf.org/html/rfc4226)
51
+ * [TOTP: Time-Based One-Time Password Algorithm](http://tools.ietf.org/html/rfc6238)
52
52
 
53
53
  In the OTP URI format, the value of "secret" is encoded with BASE32 algorithm.
54
54
  The Format details are described in the document of Google Authenticator.
55
55
 
56
- * The Base16, Base32, and Base64 Data Encodings - http://tools.ietf.org/html/rfc4648
57
- * Google Authenticator Key URI format - https://github.com/google/google-authenticator/wiki/Key-Uri-Format
56
+ * [The Base16, Base32, and Base64 Data Encodings](http://tools.ietf.org/html/rfc4648)
57
+ * [Google Authenticator Key URI format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
@@ -9,9 +9,7 @@ module OTP
9
9
  DEFAULT_DIGITS = 6
10
10
  DEFAULT_ALGORITHM = "SHA1"
11
11
 
12
- attr_accessor :secret
13
- attr_accessor :algorithm
14
- attr_accessor :digits
12
+ attr_accessor :secret, :algorithm, :digits
15
13
  attr_accessor :issuer, :accountname
16
14
 
17
15
  def initialize(secret=nil, algorithm=nil, digits=nil)
@@ -21,24 +19,23 @@ module OTP
21
19
  end
22
20
 
23
21
  def new_secret(num_bytes=10)
24
- s = (0...num_bytes).map{ Random.rand(256).chr }.join
25
- self.secret = OTP::Base32.encode(s)
22
+ self.raw_secret = OpenSSL::Random.random_bytes(num_bytes)
26
23
  end
27
24
 
28
- def moving_factor
29
- raise NotImplementedError
25
+ def raw_secret=(bytes)
26
+ self.secret = OTP::Base32.encode(bytes)
30
27
  end
31
28
 
32
- def otp(generation=0)
33
- hash = hmac(algorithm, OTP::Base32.decode(secret),
34
- pack_int64(moving_factor+generation))
35
- return truncate(hash)
29
+ def raw_secret
30
+ return OTP::Base32.decode(secret)
31
+ end
32
+
33
+ def moving_factor
34
+ raise NotImplementedError
36
35
  end
37
36
 
38
37
  def password(generation=0)
39
- pw = (otp(generation) % (10 ** digits)).to_s
40
- pw = "0" + pw while pw.length < digits
41
- return pw
38
+ return otp(algorithm, raw_secret, moving_factor+generation, digits)
42
39
  end
43
40
 
44
41
  def verify(given_pw, last:0, post:0)
@@ -48,18 +45,24 @@ module OTP
48
45
  return (-last..post).any?{|i| compare(password(i), given_pw) }
49
46
  end
50
47
 
51
- ## URI related methods
52
-
53
48
  def to_uri
54
- OTP::URI.format(self)
49
+ return OTP::URI.format(self)
55
50
  end
56
51
 
57
- def type_specific_uri_params
58
- raise NotImplementedError
52
+ def uri_params
53
+ params = {}
54
+ params[:secret] = secret
55
+ params[:issuer] = issuer if issuer
56
+ params[:algorithm] = algorithm if algorithm != DEFAULT_ALGORITHM
57
+ params[:digits] = digits if digits != DEFAULT_DIGITS
58
+ return params
59
59
  end
60
60
 
61
- def extract_type_specific_uri_params(query)
62
- raise NotImplementedError
61
+ def extract_uri_params(params)
62
+ self.secret = params["secret"]
63
+ self.issuer = issuer || params["issuer"]
64
+ self.algorithm = params["algorithm"] || algorithm
65
+ self.digits = (params["digits"] || digits).to_i
63
66
  end
64
67
  end
65
68
  end
@@ -13,14 +13,13 @@ module OTP
13
13
  return count
14
14
  end
15
15
 
16
- def type_specific_uri_params
17
- return {count: count}
16
+ def uri_params
17
+ return super.merge(count: count)
18
18
  end
19
19
 
20
- def extract_type_specific_uri_params(query)
21
- if value = query["count"]
22
- self.count = value.to_i
23
- end
20
+ def extract_uri_params(params)
21
+ super
22
+ self.count = (params["count"] || count).to_i
24
23
  end
25
24
  end
26
25
  end
@@ -16,16 +16,15 @@ module OTP
16
16
  return (time || Time.now).to_i / period
17
17
  end
18
18
 
19
- def type_specific_uri_params
20
- params = {}
19
+ def uri_params
20
+ params = super
21
21
  params["period"] = period if period != DEFAULT_PERIOD
22
22
  return params
23
23
  end
24
24
 
25
- def extract_type_specific_uri_params(query)
26
- if value = query["period"]
27
- self.period = value.to_i
28
- end
25
+ def extract_uri_params(params)
26
+ super
27
+ self.period = (params["period"] || period).to_i
29
28
  end
30
29
  end
31
30
  end
@@ -2,29 +2,23 @@ require "uri"
2
2
 
3
3
  module OTP
4
4
  module URI
5
- module_function
5
+ SCHEME = "otpauth"
6
6
 
7
- SCHEME = "othauth"
7
+ module_function
8
8
 
9
9
  def parse(uri_string)
10
10
  uri = ::URI.parse(uri_string)
11
- raise "URI scheme not match: #{uri.scheme}" unless uri.scheme != SCHEME
12
- otp = otp_class(uri).new
13
- m = %r{/(?:([^:]*): *)?(.+)}.match(::URI.decode(uri.path))
11
+ if uri.scheme.downcase != SCHEME
12
+ raise "URI scheme not match: #{uri.scheme}"
13
+ end
14
+ otp = type_to_class(uri).new
15
+ unless m = %r{/(?:([^:]*): *)?(.+)}.match(::URI.decode(uri.path))
16
+ raise "account name must be present: #{uri_string}"
17
+ end
14
18
  otp.issuer = m[1] if m[1]
15
19
  otp.accountname = m[2]
16
20
  query = Hash[::URI.decode_www_form(uri.query)]
17
- otp.secret = query["secret"]
18
- if value = query["algorithm"]
19
- otp.algorithm = value
20
- end
21
- if value = query["issuer"]
22
- otp.issuer = value
23
- end
24
- if value = query["digits"]
25
- otp.digits = value.to_i
26
- end
27
- otp.extract_type_specific_uri_params(query)
21
+ otp.extract_uri_params(query)
28
22
  return otp
29
23
  end
30
24
 
@@ -32,41 +26,23 @@ module OTP
32
26
  raise "secret must be set" if otp.secret.nil?
33
27
  raise "accountname must be set" if otp.accountname.nil?
34
28
  typename = otp.class.name.split("::")[-1].downcase
35
- label = otp.issuer ? "#{otp.issuer}:#{otp.accountname}" : otp.accountname
36
- params = pickup_params(otp)
37
- return "otpauth://%s/%s?%s" % [
29
+ label = otp.accountname.dup
30
+ label.prepend("#{otp.issuer}:") if otp.issuer
31
+ return "%s://%s/%s?%s" % [
32
+ SCHEME,
38
33
  ::URI.encode(typename),
39
34
  ::URI.encode(label),
40
- ::URI.encode_www_form(params)
35
+ ::URI.encode_www_form(otp.uri_params)
41
36
  ]
42
37
  end
43
38
 
44
- def otp_class(uri)
45
- case uri.host.upcase
46
- when "HOTP"
47
- OTP::HOTP
48
- when "TOTP"
49
- OTP::TOTP
50
- else
51
- raise "unknown OTP type: #{uri.host}"
52
- end
53
- end
54
-
55
- def pickup_params(otp)
56
- param_spec = [
57
- [:secret, nil],
58
- [:issuer, nil],
59
- [:algorithm, OTP::Base::DEFAULT_ALGORITHM],
60
- [:digits, OTP::Base::DEFAULT_DIGITS],
61
- ]
62
- params = param_spec.reduce({}) do |h, (name, default)|
63
- value = otp.send(name)
64
- if value && value != default
65
- h[name] = value
66
- end
67
- h
68
- end
69
- return params.merge(otp.type_specific_uri_params)
39
+ def type_to_class(uri)
40
+ klass = OTP.const_get(uri.host.upcase)
41
+ raise unless klass.is_a?(Class)
42
+ raise unless klass.ancestors.include?(OTP::Base)
43
+ return klass
44
+ rescue
45
+ raise "unknown OTP type: #{uri.host}"
70
46
  end
71
47
  end
72
48
  end
@@ -4,6 +4,13 @@ module OTP
4
4
  module Utils
5
5
  private
6
6
 
7
+ def otp(algorithm, secret, moving_factor, digits)
8
+ message = pack_int64(moving_factor)
9
+ digest = hmac(algorithm, secret, message)
10
+ num = pickup(digest)
11
+ return truncate(num, digits)
12
+ end
13
+
7
14
  def pack_int64(i)
8
15
  return [i >> 32 & 0xffffffff, i & 0xffffffff].pack("NN")
9
16
  end
@@ -14,12 +21,18 @@ module OTP
14
21
  return mac.digest
15
22
  end
16
23
 
17
- def truncate(hash)
18
- offset = hash[-1].ord & 0xf
19
- binary = hash[offset, 4]
24
+ def pickup(digest)
25
+ offset = digest[-1].ord & 0xf
26
+ binary = digest[offset, 4]
20
27
  return binary.unpack("N")[0] & 0x7fffffff
21
28
  end
22
29
 
30
+ def truncate(num, digits)
31
+ pw = (num % (10 ** digits)).to_s
32
+ pw.prepend("0") while pw.length < digits
33
+ return pw
34
+ end
35
+
23
36
  def compare(a, b)
24
37
  return a.to_i == b.to_i
25
38
  end
@@ -1,3 +1,3 @@
1
1
  module OTP
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -1,25 +1,60 @@
1
1
  require_relative "helper"
2
2
 
3
3
  class TestBase < Test::Unit::TestCase
4
- def test_base
4
+ def test_new_secret
5
5
  otp = OTP::Base.new
6
+
6
7
  otp.new_secret(20)
8
+ assert_equal(20, otp.raw_secret.length)
7
9
  assert_equal(32, otp.secret.length)
10
+
8
11
  otp.new_secret(40)
12
+ assert_equal(40, otp.raw_secret.length)
9
13
  assert_equal(64, otp.secret.length)
10
14
  end
11
15
 
12
- def test_methods_expected_to_be_override
16
+ def test_secret
17
+ otp = OTP::Base.new
18
+
19
+ otp.secret = nil
20
+ assert_nil(otp.secret)
21
+ assert_nil(otp.raw_secret)
22
+
23
+ otp.secret = ""
24
+ assert_equal("", otp.secret)
25
+ assert_equal("", otp.raw_secret)
26
+
27
+ otp.secret = "MZXW6YTBOI======"
28
+ assert_equal("MZXW6YTBOI======", otp.secret)
29
+ assert_equal("foobar", otp.raw_secret)
30
+
31
+ otp.secret = "MZXW6YTBOI"
32
+ assert_equal("MZXW6YTBOI", otp.secret)
33
+ assert_equal("foobar", otp.raw_secret)
34
+ end
35
+
36
+ def test_raw_secret
37
+ otp = OTP::Base.new
38
+
39
+ otp.raw_secret = nil
40
+ assert_nil(otp.secret)
41
+ assert_nil(otp.raw_secret)
42
+
43
+ otp.raw_secret = ""
44
+ assert_equal("", otp.secret)
45
+ assert_equal("", otp.raw_secret)
46
+
47
+ otp.raw_secret = "foobarbaz"
48
+ assert_equal("MZXW6YTBOJRGC6Q=", otp.secret)
49
+ assert_equal("foobarbaz", otp.raw_secret)
50
+ end
51
+
52
+ def test_moving_factor
13
53
  base = OTP::Base.new
54
+ hotp = OTP::HOTP.new
14
55
  totp = OTP::TOTP.new
15
-
16
- [
17
- [:moving_factor, ],
18
- [:type_specific_uri_params, ],
19
- [:extract_type_specific_uri_params, {}],
20
- ].each do |m, *args|
21
- assert_raise(NotImplementedError){ base.send(m, *args) }
22
- assert_nothing_raised{ totp.send(m, *args) }
23
- end
56
+ assert_raise(NotImplementedError){ base.moving_factor }
57
+ assert_nothing_raised{ hotp.moving_factor }
58
+ assert_nothing_raised{ totp.moving_factor }
24
59
  end
25
60
  end
@@ -7,17 +7,45 @@ class TestURI < Test::Unit::TestCase
7
7
  assert_equal("account@example.com", otp.accountname)
8
8
  assert_equal(nil, otp.issuer)
9
9
 
10
+ uri = "otpauth://totp/account@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Foo"
11
+ otp = OTP::URI.parse(uri)
12
+ assert_equal("account@example.com", otp.accountname)
13
+ assert_equal("Foo", otp.issuer)
14
+
10
15
  uri = "otpauth://totp/My%20Company:%20%20account@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"
11
16
  otp = OTP::URI.parse(uri)
12
17
  assert_equal("account@example.com", otp.accountname)
13
18
  assert_equal("My Company", otp.issuer)
14
19
 
15
- uri = "otpauth://totp/My%20Company:%20%20account@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"
20
+ uri = "otpauth://totp/My%20Company:%20%20account@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Foo"
16
21
  otp = OTP::URI.parse(uri)
17
22
  assert_equal("account@example.com", otp.accountname)
18
23
  assert_equal("My Company", otp.issuer)
19
24
  end
20
25
 
26
+ def test_totp_simple
27
+ secret = OTP::Base32.encode("12345678901234567890")
28
+ totp = OTP::TOTP.new
29
+ totp.secret = secret
30
+ totp.accountname = "account@example.com"
31
+ uri = totp.to_uri
32
+ assert_not_match(/algorithm=/, uri)
33
+ assert_not_match(/digits=/, uri)
34
+ assert_not_match(/issuer=/, uri)
35
+ assert_not_match(/period=/, uri)
36
+
37
+ otp = OTP::URI.parse(uri)
38
+ assert_equal(OTP::TOTP, otp.class)
39
+ assert_equal(secret, otp.secret)
40
+ assert_equal("SHA1", otp.algorithm)
41
+ assert_equal(6, otp.digits)
42
+ assert_equal(30, otp.period)
43
+ assert_equal("account@example.com", otp.accountname)
44
+ assert_equal(nil, otp.issuer)
45
+ totp.time = otp.time = Time.now
46
+ assert_equal(otp.password, totp.password)
47
+ end
48
+
21
49
  def test_totp
22
50
  secret = OTP::Base32.encode("12345678901234567890")
23
51
  totp = OTP::TOTP.new
@@ -41,6 +69,28 @@ class TestURI < Test::Unit::TestCase
41
69
  assert_equal(otp.password, totp.password)
42
70
  end
43
71
 
72
+ def test_hotp_simple
73
+ secret = OTP::Base32.encode("12345678901234567890")
74
+ hotp = OTP::HOTP.new
75
+ hotp.secret = secret
76
+ hotp.accountname = "account@example.com"
77
+ uri = hotp.to_uri
78
+ assert_not_match(/algorithm=/, uri)
79
+ assert_not_match(/digits=/, uri)
80
+ assert_not_match(/issuer=/, uri)
81
+ assert_match(/count=0/, uri)
82
+
83
+ otp = OTP::URI.parse(uri)
84
+ assert_equal(OTP::HOTP, otp.class)
85
+ assert_equal(secret, otp.secret)
86
+ assert_equal("SHA1", otp.algorithm)
87
+ assert_equal(6, otp.digits)
88
+ assert_equal(0, otp.count)
89
+ assert_equal("account@example.com", otp.accountname)
90
+ assert_equal(nil, otp.issuer)
91
+ assert_equal(otp.password, hotp.password)
92
+ end
93
+
44
94
  def test_hotp
45
95
  secret = OTP::Base32.encode("12345678901234567890")
46
96
  hotp = OTP::HOTP.new
@@ -64,7 +114,13 @@ class TestURI < Test::Unit::TestCase
64
114
  end
65
115
 
66
116
  def test_parse_invalid
67
- assert_raise(RuntimeError){ OTP::URI.parse("http://www.netlab.jp") }
68
- assert_raise(RuntimeError){ OTP::URI.parse("otpauth://foo") }
117
+ e = assert_raise(RuntimeError){ OTP::URI.parse("http://www.netlab.jp") }
118
+ assert_match(/URI scheme not match/, e.message)
119
+ e = assert_raise(RuntimeError){ OTP::URI.parse("otpauth://foo") }
120
+ assert_match(/unknown OTP type/, e.message)
121
+ e = assert_raise(RuntimeError){ OTP::URI.parse("otpauth://version") }
122
+ assert_match(/unknown OTP type/, e.message)
123
+ e = assert_raise(RuntimeError){ OTP::URI.parse("otpauth://totp/") }
124
+ assert_match(/account name must be present/, e.message)
69
125
  end
70
126
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuuzou Gotou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-08 00:00:00.000000000 Z
11
+ date: 2015-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler