yubikey 1.2.1 → 1.3.0

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