yubikey 1.2.1 → 1.3.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.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # yubikey
2
+
3
+ [![Build Status](https://travis-ci.org/rainforestapp/yubikey.png?branch=master)](https://travis-ci.org/rainforestapp/yubikey)
4
+
5
+ ## Description
6
+
7
+ A library to verify, decode, decrypt and parse [Yubikey](http://www.yubico.com/home/index/) one-time passwords.
8
+
9
+ ## Usage
10
+
11
+ ### OTP Decryption
12
+
13
+ ```ruby
14
+ key = 'ecde18dbe76fbd0c33330f1c354871db'
15
+ otp = 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'
16
+ token = Yubikey::OTP.new(otp, key)
17
+
18
+ p "Device public id: #{token.public_id}" #=> 'dteffuje'
19
+ p "Device secret id: #{token.secret_id}" #=> '8792ebfe26cc'
20
+ p "Device insertions: #{token.insert_counter}" #=> 19
21
+ p "Session activation counter: #{token.session_counter}" #=> 17
22
+ p "Session timestamp: #{token.timestamp}" #=> 49712
23
+ p "OTP random data: #{token.random_number}" #=> 40904
24
+ ```
25
+
26
+ ### OTP Verification
27
+ Use your own `api_key` and `api_id`, which you can get at [yubico.com](https://upgrade.yubico.com/getapikey/).
28
+
29
+ ```ruby
30
+ begin
31
+ otp = Yubikey::OTP::Verify.new(:api_id => 1234,
32
+ :api_key => 'NiSwGZBQ0gTbwXbRGWAf4kM5xXg=',
33
+ :otp => 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh')
34
+
35
+ if otp.valid?
36
+ p 'valid OTP'
37
+ elsif otp.replayed?
38
+ p 'replayed OTP'
39
+ end
40
+ rescue Yubikey::OTP::InvalidOTPError
41
+ p 'invalid OTP'
42
+ end
43
+ ```
44
+
45
+ ## Install
46
+
47
+ Yubikey is available as a gem, to install it just install the gem:
48
+
49
+ gem install yubikey
50
+
51
+ If you're using Bundler, add the gem to Gemfile.
52
+
53
+ gem 'yubikey'
54
+
55
+ Then run bundle install.
56
+
57
+ ## Copyright
58
+
59
+ ### Ruby library
60
+ Written by [Jonathan Rudenberg](https://github.com/titanous) <jon335@gmail.com>
61
+ Copyright (c) 2009 Jonathan Rudenberg
62
+ The MIT License. See LICENSE.
63
+
64
+ ### Contributors
65
+ - Carl Byström
66
+ - Erik Ruwalder
67
+ - [Russell Smith](https://github.com/ukd1)
68
+ - [Chris Lundquist](https://github.com/ChrisLundquist)
69
+ - [Maarten van Grootel](https://github.com/maartenvg)
70
+ - [Chris Benedict](https://github.com/chrisbdaemon)
data/lib/yubikey.rb CHANGED
@@ -1,12 +1,12 @@
1
- $:.unshift(File.dirname(__FILE__)) unless
2
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless
2
+ $LOAD_PATH.include?(File.dirname(__FILE__)) || $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  require 'net/https'
5
- require 'crypt/rijndael'
5
+ require 'openssl'
6
6
 
7
7
  module Yubikey; end;
8
8
 
9
9
  require 'yubikey/hex'
10
10
  require 'yubikey/modhex'
11
11
  require 'yubikey/otp'
12
- require 'yubikey/otp_verify'
12
+ require 'yubikey/otp_verify'
data/lib/yubikey/otp.rb CHANGED
@@ -15,31 +15,41 @@ class Yubikey::OTP
15
15
  attr_reader :session_counter
16
16
  # random integer used as padding and extra random noise
17
17
  attr_reader :random_number
18
-
19
-
18
+
19
+
20
20
  # Decode/decrypt a Yubikey one-time password
21
21
  #
22
22
  # [+otp+] ModHex encoded Yubikey OTP (at least 32 characters)
23
23
  # [+key+] 32-character hex AES key
24
- def initialize(otp, key)
24
+ def initialize(otp, key)
25
+
26
+ # Get the public ID first
27
+ @public_id = otp[0, 12]
28
+
29
+ # Strip prefix so otp will decode (following from yubico-c library)
30
+ otp = otp[-32,32] if otp.length > 32
31
+
25
32
  raise InvalidOTPError, 'OTP must be at least 32 characters of modhex' unless otp.modhex? && otp.length >= 32
26
33
  raise InvalidKeyError, 'Key must be 32 hex characters' unless key.hex? && key.length == 32
27
-
28
- @public_id = otp[0,otp.length-32] if otp.length > 32
29
-
34
+
35
+
30
36
  @token = Yubikey::ModHex.decode(otp[-32,32])
31
37
  @aes_key = key.to_bin
32
-
33
- @token = Crypt::Rijndael.new(@aes_key, 128).decrypt_block(@token)
34
-
38
+
39
+ decrypter = OpenSSL::Cipher.new('AES-128-ECB').decrypt
40
+ decrypter.key = @aes_key
41
+ decrypter.padding = 0
42
+
43
+ @token = decrypter.update(@token) + decrypter.final
44
+
35
45
  raise BadCRCError unless crc_valid?
36
-
46
+
37
47
  @secret_id, @insert_counter, @timestamp, @timestamp_lo, @session_counter, @random_number, @crc = @token.unpack('H12vvCCvv')
38
48
  @timestamp += @timestamp_lo * 65536
39
49
  end
40
-
50
+
41
51
  private
42
-
52
+
43
53
  def crc_valid?
44
54
  crc = 0xffff
45
55
  @token.each_byte do |b|
@@ -52,9 +62,9 @@ class Yubikey::OTP
52
62
  end
53
63
  crc == 0xf0b8
54
64
  end
55
-
65
+
56
66
  # :stopdoc:
57
67
  class InvalidOTPError < StandardError; end
58
68
  class InvalidKeyError < StandardError; end
59
69
  class BadCRCError < StandardError; end
60
- end # Yubikey::OTP
70
+ end # Yubikey::OTP
@@ -1,16 +1,26 @@
1
+ require 'base64'
2
+ require 'securerandom'
3
+
1
4
  module Yubikey
2
5
 
3
- API_URL = 'https://api.yubico.com/wsapi/'
4
- API_ID = 2549
5
- API_KEY = 'e928a7d3076516a8c8c879f42c3ea0388f3b19f'
6
-
6
+ API_URL = 'https://api.yubico.com/wsapi/2.0/'
7
+
7
8
  class OTP::Verify
8
-
9
9
  # The raw status from the Yubico server
10
10
  attr_reader :status
11
-
12
- def initialize(otp)
13
- verify("id=#{API_ID}&otp=#{otp}")
11
+
12
+ def initialize(args)
13
+ raise(ArgumentError, "Must supply API ID") if args[:api_id].nil?
14
+ raise(ArgumentError, "Must supply API Key") if args[:api_key].nil?
15
+ raise(ArgumentError, "Must supply OTP") if args[:otp].nil?
16
+
17
+ @api_key = args[:api_key]
18
+ @api_id = args[:api_id]
19
+
20
+ @url = args[:url] || API_URL
21
+ @nonce = args[:nonce] || OTP::Verify.generate_nonce(32)
22
+
23
+ verify(args)
14
24
  end
15
25
 
16
26
  def valid?
@@ -23,8 +33,10 @@ module Yubikey
23
33
 
24
34
  private
25
35
 
26
- def verify(query)
27
- uri = URI.parse(API_URL) + 'verify'
36
+ def verify(args)
37
+ query = "id=#{@api_id}&otp=#{args[:otp]}&nonce=#{@nonce}"
38
+
39
+ uri = URI.parse(@url) + 'verify'
28
40
  uri.query = query
29
41
 
30
42
  http = Net::HTTP.new(uri.host, uri.port)
@@ -33,12 +45,52 @@ module Yubikey
33
45
 
34
46
  req = Net::HTTP::Get.new(uri.request_uri)
35
47
  result = http.request(req).body
36
-
48
+
37
49
  @status = result[/status=(.*)$/,1].strip
38
50
 
39
51
  if @status == 'BAD_OTP' || @status == 'BACKEND_ERROR'
40
52
  raise OTP::InvalidOTPError, "Received error: #{@status}"
41
53
  end
54
+
55
+ if ! verify_response(result)
56
+ @status = 'BAD_RESPONSE'
57
+ return
58
+ end
59
+ end
60
+
61
+ def verify_response(result)
62
+
63
+ signature = result[/^h=(.+)$/, 1].strip
64
+ returned_nonce = result[/nonce=(.+)$/, 1]
65
+ returned_nonce.strip! unless returned_nonce.nil?
66
+
67
+ if @nonce != returned_nonce
68
+ return false
69
+ end
70
+
71
+ generated_signature = OTP::Verify.generate_hmac(result, @api_key)
72
+
73
+ return signature == generated_signature
74
+ end
75
+
76
+
77
+ def self.generate_nonce(length)
78
+ return SecureRandom.hex length/2
79
+ end
80
+
81
+
82
+ def self.generate_hmac(response, api_key)
83
+ response_params = response.split(' ')
84
+ response_params.reject! do |p|
85
+ p =~ /^h=(.+)$/
86
+ end
87
+
88
+ response_string = response_params.sort.join('&')
89
+ response_string.strip!
90
+
91
+ hmac = OpenSSL::HMAC.digest('sha1', Base64.decode64(api_key), response_string)
92
+
93
+ return Base64.encode64(hmac).strip
42
94
  end
43
95
  end # OTP::Verify
44
96
  end # Yubikey
data/spec/hex_spec.rb CHANGED
@@ -1,27 +1,26 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
1
+ # encoding: US-ASCII
3
2
  describe 'hex' do
4
- it 'should encode binary to hex' do
3
+ it 'encodes binary to hex' do
5
4
  "i\266H\034\213\253\242\266\016\217\"\027\233X\315V".to_hex.
6
5
  should == '69b6481c8baba2b60e8f22179b58cd56'
7
-
6
+
8
7
  "\354\336\030\333\347o\275\f33\017\0345Hq\333".to_hex.
9
8
  should == 'ecde18dbe76fbd0c33330f1c354871db'
10
9
  end
11
-
12
- it 'should decode hex to binary' do
10
+
11
+ it 'decodes hex to binary' do
13
12
  '69b6481c8baba2b60e8f22179b58cd56'.to_bin.
14
13
  should == "i\266H\034\213\253\242\266\016\217\"\027\233X\315V"
15
-
14
+
16
15
  'ecde18dbe76fbd0c33330f1c354871db'.to_bin.
17
16
  should == "\354\336\030\333\347o\275\f33\017\0345Hq\333"
18
17
  end
19
-
20
- it 'should know whether a string is hex' do
21
- 'ecde18dbe76fbd0c33330f1c354871db'.hex?.should == true
22
- 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'.modhex?.should == true
23
-
24
- 'foobar'.hex?.should == false
25
- 'test'.modhex?.should == false
18
+
19
+ it 'detects if a string is hex' do
20
+ 'ecde18dbe76fbd0c33330f1c354871db'.hex?.should be_true
21
+ 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'.modhex?.should be_true
22
+
23
+ 'foobar'.hex?.should be_false
24
+ 'test'.modhex?.should be_false
26
25
  end
27
- end
26
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,5 @@
1
- begin
2
- require 'spec'
3
- rescue LoadError
4
- require 'rubygems'
5
- gem 'rspec'
6
- require 'spec'
7
- end
1
+ require 'rubygems'
2
+ require 'rspec'
8
3
 
9
- $:.unshift(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
10
5
  require 'yubikey'
metadata CHANGED
@@ -1,37 +1,24 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: yubikey
3
- version: !ruby/object:Gem::Version
4
- version: 1.2.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.0
5
+ prerelease:
5
6
  platform: ruby
6
- authors:
7
+ authors:
7
8
  - Jonathan Rudenberg
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
-
12
- date: 2009-12-04 00:00:00 -05:00
13
- default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: crypt19
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
24
- version:
12
+ date: 2013-03-19 00:00:00.000000000 Z
13
+ dependencies: []
25
14
  description: A library to verify, decode, decrypt and parse Yubikey one-time passwords.
26
15
  email: jon335@gmail.com
27
16
  executables: []
28
-
29
17
  extensions: []
30
-
31
- extra_rdoc_files:
18
+ extra_rdoc_files:
32
19
  - LICENSE
33
- - README.rdoc
34
- files:
20
+ - README.md
21
+ files:
35
22
  - examples/otp.rb
36
23
  - lib/yubikey.rb
37
24
  - lib/yubikey/hex.rb
@@ -39,49 +26,35 @@ files:
39
26
  - lib/yubikey/otp.rb
40
27
  - lib/yubikey/otp_verify.rb
41
28
  - spec/hex_spec.rb
42
- - spec/spec.opts
43
29
  - spec/spec_helper.rb
44
- - spec/yubikey_modhex_spec.rb
45
- - spec/yubikey_otp_spec.rb
46
- - spec/yubikey_otp_verify_spec.rb
47
30
  - LICENSE
48
- - README.rdoc
49
- has_rdoc: true
50
- homepage: http://github.com/titanous/yubikey
31
+ - README.md
32
+ homepage: https://github.com/titanous/yubikey
51
33
  licenses: []
52
-
53
34
  post_install_message:
54
- rdoc_options:
55
- - --charset=UTF-8
35
+ rdoc_options:
56
36
  - --title
57
37
  - yubikey
58
38
  - --main
59
39
  - README.rdoc
60
- require_paths:
40
+ require_paths:
61
41
  - lib
62
- required_ruby_version: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: "0"
67
- version:
68
- required_rubygems_version: !ruby/object:Gem::Requirement
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- version: "0"
73
- version:
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
74
54
  requirements: []
75
-
76
55
  rubyforge_project: yubikey
77
- rubygems_version: 1.3.5
56
+ rubygems_version: 1.8.25
78
57
  signing_key:
79
58
  specification_version: 3
80
- summary: A library to verify, decode, decrypt and parse Yubikey one-time passwords.
81
- test_files:
82
- - spec/hex_spec.rb
83
- - spec/spec_helper.rb
84
- - spec/yubikey_modhex_spec.rb
85
- - spec/yubikey_otp_spec.rb
86
- - spec/yubikey_otp_verify_spec.rb
87
- - examples/otp.rb
59
+ summary: Yubikey library for Ruby
60
+ test_files: []
data/README.rdoc DELETED
@@ -1,49 +0,0 @@
1
- = yubikey
2
-
3
- == Description
4
-
5
- A library to verify, decode, decrypt and parse Yubikey[http://www.yubico.com/home/index/] one-time passwords.
6
-
7
- == Usage
8
-
9
- === OTP Decryption
10
-
11
- key = 'ecde18dbe76fbd0c33330f1c354871db'
12
- otp = 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'
13
- token = Yubikey::OTP.new(otp, key)
14
-
15
- p "Device public id: #{token.public_id}" #=> 'dteffuje'
16
- p "Device secret id: #{token.secret_id}" #=> '8792ebfe26cc'
17
- p "Device insertions: #{token.insert_counter}" #=> 19
18
- p "Session activation counter: #{token.session_counter}" #=> 17
19
- p "Session timestamp: #{token.timestamp}" #=> 49712
20
- p "OTP random data: #{token.random_number}" #=> 40904
21
-
22
- === OTP Verification
23
-
24
- begin
25
- otp = Yubikey::OTP::Verify.new('dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh')
26
-
27
- if otp.valid?
28
- p 'valid OTP'
29
- elsif otp.replayed?
30
- p 'replayed OTP'
31
- end
32
- rescue Yubikey::OTP::InvalidOTPError
33
- p 'invalid OTP'
34
- end
35
-
36
- == Install
37
-
38
- sudo gem install yubikey
39
-
40
- == Copyright
41
-
42
- === Ruby library
43
- Written by Jonathan Rudenberg <jon335@gmail.com>
44
- Copyright (c) 2009 Jonathan Rudenberg
45
- The MIT License. See LICENSE.
46
-
47
- === Contributors
48
- Carl Byström
49
- Erik Ruwalder
data/spec/spec.opts DELETED
@@ -1 +0,0 @@
1
- --colour
@@ -1,31 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
3
- describe 'Yubikey::Modhex' do
4
- it 'should decode modhex' do
5
- Yubikey::ModHex.decode('hknhfjbrjnlnldnhcujvddbikngjrtgh').should == "i\266H\034\213\253\242\266\016\217\"\027\233X\315V"
6
- Yubikey::ModHex.decode('urtubjtnuihvntcreeeecvbregfjibtn').should == "\354\336\030\333\347o\275\f33\017\0345Hq\333"
7
-
8
- Yubikey::ModHex.decode('dteffuje').should == "-4N\203"
9
-
10
- Yubikey::ModHex.decode('ifhgieif').should == 'test'
11
- Yubikey::ModHex.decode('hhhvhvhdhbid').should == 'foobar'
12
-
13
- Yubikey::ModHex.decode('cc').should == "\000"
14
- end
15
-
16
- it 'should raise if modhex string length uneven' do
17
- lambda { Yubikey::ModHex.decode('ifh') }.should raise_error(ArgumentError)
18
- end
19
-
20
- it 'should encode modhex' do
21
- Yubikey::ModHex.encode("i\266H\034\213\253\242\266\016\217\"\027\233X\315V").should == 'hknhfjbrjnlnldnhcujvddbikngjrtgh'
22
- Yubikey::ModHex.encode("\354\336\030\333\347o\275\f33\017\0345Hq\333").should == 'urtubjtnuihvntcreeeecvbregfjibtn'
23
-
24
- Yubikey::ModHex.encode("-4N\203").should == 'dteffuje'
25
-
26
- Yubikey::ModHex.encode('test').should == 'ifhgieif'
27
- Yubikey::ModHex.encode('foobar').should == 'hhhvhvhdhbid'
28
-
29
- Yubikey::ModHex.encode("\000").should == 'cc'
30
- end
31
- end
@@ -1,28 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
3
- describe 'Yubikey::OTP' do
4
- it 'should parse a otp' do
5
- token = Yubikey::OTP.new('dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh', 'ecde18dbe76fbd0c33330f1c354871db')
6
-
7
- token.public_id.should == 'dteffuje'
8
- token.secret_id.should == '8792ebfe26cc'
9
- token.insert_counter.should == 19
10
- token.session_counter.should == 17
11
- token.timestamp.should == 49712
12
- token.random_number.should == 40904
13
- end
14
-
15
- it 'should raise if key or otp invalid' do
16
- otp = 'hknhfjbrjnlnldnhcujvddbikngjrtgh'
17
- key = 'ecde18dbe76fbd0c33330f1c354871db'
18
-
19
- lambda { Yubikey::OTP.new(key, key) }.should raise_error(Yubikey::OTP::InvalidOTPError)
20
- lambda { Yubikey::OTP.new(otp, otp) }.should raise_error(Yubikey::OTP::InvalidKeyError)
21
-
22
- lambda { Yubikey::OTP.new(otp[0,31], key) }.should raise_error(Yubikey::OTP::InvalidOTPError)
23
- lambda { Yubikey::OTP.new(otp, key[0,31]) }.should raise_error(Yubikey::OTP::InvalidKeyError)
24
-
25
- lambda { Yubikey::OTP.new(otp[1,31]+'d', key) }.should raise_error(Yubikey::OTP::BadCRCError)
26
- end
27
-
28
- end
@@ -1,38 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
3
- describe 'Yubikey::OTP::Verify' do
4
-
5
- before do
6
- @otp = 'dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'
7
-
8
- @mock_http = mock('http')
9
- @mock_http_get = mock('http_get')
10
-
11
- Net::HTTP.should_receive(:new).with('api.yubico.com', 443).and_return(@mock_http)
12
- @mock_http.should_receive(:use_ssl=).and_return(nil)
13
- @mock_http.should_receive(:verify_mode=).and_return(nil)
14
- @mock_http.should_receive(:request).with(@mock_http_get).and_return(@mock_http_get)
15
- Net::HTTP::Get.should_receive(:new).with(/otp=#{@otp}/).and_return(@mock_http_get)
16
- end
17
-
18
- it 'should verify a valid OTP' do
19
- @mock_http_get.should_receive(:body).and_return('status=OK')
20
- otp = Yubikey::OTP::Verify.new(@otp)
21
- otp.valid?.should == true
22
- otp.replayed?.should == false
23
- end
24
-
25
- it 'should verify a replayed OTP' do
26
- @mock_http_get.should_receive(:body).and_return('status=REPLAYED_OTP')
27
- otp = Yubikey::OTP::Verify.new(@otp)
28
- otp.valid?.should == false
29
- otp.replayed?.should == true
30
- end
31
-
32
- it 'should raise on invalid OTP' do
33
- @mock_http_get.should_receive(:body).and_return('status=BAD_OTP')
34
- lambda { otp = Yubikey::OTP::Verify.new(@otp) }.should raise_error(Yubikey::OTP::InvalidOTPError)
35
- end
36
-
37
-
38
- end