YubiRuby 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ v1.0.0. broken api compatibility. TestCase. fixed some bugs in (mod)hex decoding. Exceptions. FakeYubikey generator. yubikey app for personal key management.
2
+
3
+ v0.1.2. backported fixes from 1.0.0
4
+
5
+ v0.1.1. fixed some bugs in hex decoding.
6
+
1
7
  v0.1.0. modhex installed as binary.
2
8
 
3
9
  v0.0.1. First release
data/Manifest CHANGED
@@ -3,8 +3,8 @@ LICENSE
3
3
  Manifest
4
4
  README
5
5
  Rakefile
6
- YubiRuby.gemspec
7
6
  bin/modhex
7
+ bin/yubikey
8
8
  ext/libyubikey/extconf.rb
9
9
  ext/libyubikey/libyubikey.c
10
10
  ext/libyubikey/ykaes.c
@@ -13,7 +13,17 @@ ext/libyubikey/ykhex.c
13
13
  ext/libyubikey/ykmodhex.c
14
14
  ext/libyubikey/ykparse.c
15
15
  ext/libyubikey/yubikey.h
16
+ lib/exceptions.rb
17
+ lib/fake_yubikey.rb
16
18
  lib/hex.rb
17
19
  lib/yubiruby.rb
18
- tests/tc_modhex.rb
19
- tests/ts_yubiruby.rb
20
+ tasks/yard_doc.rake
21
+ test/fake_yubikey_spec.rb
22
+ test/hex_spec.rb
23
+ test/modhex_spec.rb
24
+ test/path_loader.rb
25
+ test/tc_crc16.rb
26
+ test/tc_fake_yubikey.rb
27
+ test/tc_hex.rb
28
+ test/tc_modhex.rb
29
+ test/test_helper.rb
data/README CHANGED
@@ -0,0 +1,44 @@
1
+ = YubyRuby
2
+
3
+ Yubico Server wrapper in Ruby
4
+
5
+
6
+ == Installation
7
+
8
+ $ gem install YubiRuby
9
+
10
+ == Some examples
11
+
12
+ Checking an OTP
13
+
14
+ require 'rubygems'
15
+ require 'YubiRuby'
16
+
17
+ key = "6df89690b5f51bd9ac912c5004781e86" #use your AES key
18
+ y = YubiRuby::Yubikey.new(key);
19
+ puts y.key
20
+ otp = gets().strip
21
+ puts y.parse(otp)
22
+ puts "Ouput: #{y}"
23
+ puts "uid: #{y.uid}"
24
+ puts "session counter: #{y.session_counter}"
25
+ puts "capslock: #{y.triggered_by_capslock?}"
26
+ puts "timestamp low/high: #{y.timestamp_low}/#{y.timestamp_high}"
27
+ puts "session use: #{y.session_use}"
28
+ puts "random: #{y.random}"
29
+ puts "crc: #{y.crc}"
30
+ puts "crc residue: #{y.crc_residue}"
31
+ puts "crc residue ok?: #{y.crc?} (#{y.crc_residue} == #{YubiRuby::Yubikey::CRC_OK_RESIDUE})"
32
+
33
+ Generating a valid yubikey
34
+
35
+ key = YubiRuby::FakeYubikey.new
36
+ key.set_random_key
37
+ key.set_random_uid
38
+ key.public_id = "vvefeeccebek"
39
+ puts "Yubikey generated"
40
+ puts "public id:\t#{key.public_id}"
41
+ puts "key:\t\t#{key.key}"
42
+ puts "uid:\t\t#{key.uid}"
43
+ puts "And now 3 otp"
44
+ 3.times { puts key.otp }
data/Rakefile CHANGED
@@ -3,6 +3,8 @@ require 'rubygems'
3
3
  require 'rake'
4
4
  require 'echoe'
5
5
 
6
+ ENV['SPEC_OPTS'] ||= '-c'
7
+
6
8
  Echoe.new('YubiRuby') do |p|
7
9
  p.description = <<-EOF
8
10
  Yubikey integration -
@@ -40,8 +42,12 @@ EOF
40
42
  p.author = "Alessio Caiazza"
41
43
  p.email = "nolith@abisso.org"
42
44
  p.platform = Gem::Platform::RUBY
43
- p.ignore_pattern = ["tmp/**/*", "script/*"]
44
- p.development_dependencies = []
45
+ p.ignore_pattern = ["tmp/**/*", "script/*", "meta/**/*"]
46
+ p.development_dependencies = ['yard', 'rspec']
47
+ p.runtime_dependencies << ['ruby-aes-normal', '~> 1.0']
48
+ p.runtime_dependencies << ['bit-struct'] #, '~> 0.13.6']
49
+ p.spec_pattern = ['test/**/*_spec.rb']
50
+ p.has_rdoc = true
45
51
  end
46
52
 
47
53
  Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
data/YubiRuby.gemspec CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{YubiRuby}
5
- s.version = "0.1.0"
5
+ s.version = "1.0.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Alessio Caiazza"]
9
- s.date = %q{2010-02-03}
10
- s.default_executable = %q{modhex}
9
+ s.date = %q{2010-03-21}
11
10
  s.description = %q{Yubikey integration -
12
11
 
13
12
  Includes Prototypes for low-level Yubikey OTP functions
@@ -40,24 +39,37 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40
39
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
40
  }
42
41
  s.email = %q{nolith@abisso.org}
43
- s.executables = ["modhex"]
42
+ s.executables = ["modhex", "yubikey"]
44
43
  s.extensions = ["ext/libyubikey/extconf.rb"]
45
- s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "bin/modhex", "ext/libyubikey/extconf.rb", "ext/libyubikey/libyubikey.c", "ext/libyubikey/ykaes.c", "ext/libyubikey/ykcrc.c", "ext/libyubikey/ykhex.c", "ext/libyubikey/ykmodhex.c", "ext/libyubikey/ykparse.c", "ext/libyubikey/yubikey.h", "lib/hex.rb", "lib/yubiruby.rb"]
46
- s.files = ["CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "YubiRuby.gemspec", "bin/modhex", "ext/libyubikey/extconf.rb", "ext/libyubikey/libyubikey.c", "ext/libyubikey/ykaes.c", "ext/libyubikey/ykcrc.c", "ext/libyubikey/ykhex.c", "ext/libyubikey/ykmodhex.c", "ext/libyubikey/ykparse.c", "ext/libyubikey/yubikey.h", "lib/hex.rb", "lib/yubiruby.rb", "tests/tc_modhex.rb", "tests/ts_yubiruby.rb"]
44
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "bin/modhex", "bin/yubikey", "ext/libyubikey/extconf.rb", "ext/libyubikey/libyubikey.c", "ext/libyubikey/ykaes.c", "ext/libyubikey/ykcrc.c", "ext/libyubikey/ykhex.c", "ext/libyubikey/ykmodhex.c", "ext/libyubikey/ykparse.c", "ext/libyubikey/yubikey.h", "lib/exceptions.rb", "lib/fake_yubikey.rb", "lib/hex.rb", "lib/yubiruby.rb", "tasks/yard_doc.rake"]
45
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "bin/modhex", "bin/yubikey", "ext/libyubikey/extconf.rb", "ext/libyubikey/libyubikey.c", "ext/libyubikey/ykaes.c", "ext/libyubikey/ykcrc.c", "ext/libyubikey/ykhex.c", "ext/libyubikey/ykmodhex.c", "ext/libyubikey/ykparse.c", "ext/libyubikey/yubikey.h", "lib/exceptions.rb", "lib/fake_yubikey.rb", "lib/hex.rb", "lib/yubiruby.rb", "tasks/yard_doc.rake", "test/fake_yubikey_spec.rb", "test/hex_spec.rb", "test/modhex_spec.rb", "test/path_loader.rb", "test/tc_crc16.rb", "test/tc_fake_yubikey.rb", "test/tc_hex.rb", "test/tc_modhex.rb", "test/test_helper.rb", "YubiRuby.gemspec"]
47
46
  s.homepage = %q{http://bitbucket.org/nolith/yubiruby}
48
47
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "YubiRuby", "--main", "README"]
49
48
  s.require_paths = ["lib", "ext"]
50
49
  s.rubyforge_project = %q{yubiruby}
51
- s.rubygems_version = %q{1.3.5}
50
+ s.rubygems_version = %q{1.3.6}
52
51
  s.summary = %q{Yubikey integration - Includes Prototypes for low-level Yubikey OTP functions witten by Simon Josefsson <simon@josefsson.org>. Copyright (c) 2006, 2007, 2008, 2009 Yubico AB All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.}
52
+ s.test_files = ["test/test_helper.rb"]
53
53
 
54
54
  if s.respond_to? :specification_version then
55
55
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
56
  s.specification_version = 3
57
57
 
58
58
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<ruby-aes-normal>, ["~> 1.0"])
60
+ s.add_runtime_dependency(%q<bit-struct>, [">= 0"])
61
+ s.add_development_dependency(%q<yard>, [">= 0"])
62
+ s.add_development_dependency(%q<rspec>, [">= 0"])
59
63
  else
64
+ s.add_dependency(%q<ruby-aes-normal>, ["~> 1.0"])
65
+ s.add_dependency(%q<bit-struct>, [">= 0"])
66
+ s.add_dependency(%q<yard>, [">= 0"])
67
+ s.add_dependency(%q<rspec>, [">= 0"])
60
68
  end
61
69
  else
70
+ s.add_dependency(%q<ruby-aes-normal>, ["~> 1.0"])
71
+ s.add_dependency(%q<bit-struct>, [">= 0"])
72
+ s.add_dependency(%q<yard>, [">= 0"])
73
+ s.add_dependency(%q<rspec>, [">= 0"])
62
74
  end
63
75
  end
data/bin/yubikey ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require 'yubiruby'
4
+
5
+
6
+ if ARGV.length > 1
7
+ msg=<<-EOF
8
+ Usage: #{$PROGRAM_NAME} [-v]
9
+
10
+ Produce an OTP data
11
+
12
+ -v for verbose output
13
+ EOF
14
+ abort msg
15
+ end
16
+
17
+ verbose = ARGV.include? "-h"
18
+
19
+ key = nil
20
+ begin
21
+ File.open(File.expand_path("~/.yubiruby")) do |f|
22
+ key = Marshal.load(f)
23
+ end
24
+ rescue Errno::ENOENT
25
+ puts "Key not found. Generating a new one."
26
+ key = YubiRuby::FakeYubikey.new
27
+ key.set_random_key
28
+ key.set_random_uid
29
+ tmp = " "*3
30
+ 0.upto(2) { |i| tmp[i] = rand(2**8)}
31
+ key.public_id = "vv" + YubiRuby::HEX.encode(tmp).modhex_encode[2..-1]
32
+ puts "Yubikey generated"
33
+ puts "public id:\t#{key.public_id}"
34
+ puts "key:\t\t#{key.key}"
35
+ puts "uid:\t\t#{key.uid}"
36
+ end
37
+
38
+ puts key.otp
39
+ File.open(File.expand_path("~/.yubiruby"), 'w+') { |f| Marshal.dump(key, f) }
@@ -33,6 +33,8 @@
33
33
  #include <stdlib.h>
34
34
  #include "yubikey.h"
35
35
 
36
+ //#undef NDEBUG
37
+
36
38
  #define MODHEX_SING_ENCODE "encode"
37
39
  #define MODHEX_SING_DECODE "decode"
38
40
  #define MODHEX_ENCODE "modhex_encode"
@@ -42,6 +44,7 @@
42
44
  static VALUE mYubiRuby;
43
45
  static VALUE mModHex;
44
46
  static VALUE cYubikey;
47
+ static VALUE mCRC16;
45
48
 
46
49
 
47
50
  /* ModHex */
@@ -92,13 +95,15 @@ static VALUE modhex_sing_decode(VALUE self, VALUE obj)
92
95
  {
93
96
  VALUE str = StringValue(obj);
94
97
  int src_size = RSTRING(str)->len;
98
+ if (rb_funcall(str, rb_intern(MODHEX_VALID), 0) == Qfalse)
99
+ rb_raise(rb_eval_string("YubiRuby::NoModHexEncodedError"), "");
95
100
  char *dst = (char*) malloc(((src_size/2)+1)*sizeof(char));
96
101
  /* ModHex decode input string SRC of length DSTSIZE/2 into output
97
102
  string DST. The output string DST is always DSTSIZE/2 large plus
98
103
  the terminating zero. */
99
104
  yubikey_modhex_decode(dst, RSTRING(str)->ptr, src_size);
100
105
 
101
- return rb_str_new2(dst);
106
+ return rb_str_new(dst, src_size/2);
102
107
  }
103
108
 
104
109
  /*
@@ -371,9 +376,13 @@ static VALUE yubikey_to_str(VALUE self)
371
376
 
372
377
  VALUE ary = rb_ary_new2(YUBIKEY_BLOCK_SIZE);
373
378
  for (i = 0; i < YUBIKEY_BLOCK_SIZE; i++) {
374
- VALUE num = INT2FIX(((uint8_t*)data->token)[i]);
379
+ uint8_t c_num = ((uint8_t*)data->token)[i];
380
+ VALUE num = INT2FIX(c_num);
375
381
  //invoke .to_s 16 on each number
376
- rb_ary_store(ary, i, rb_funcall(num, rb_intern("to_s"), 1, INT2FIX(16) ));
382
+ VALUE hexed = rb_funcall(num,rb_intern("to_s"), 1, INT2FIX(16));
383
+ if(c_num < 16) //if num < 16 prepend a '0'
384
+ hexed = rb_funcall(hexed,rb_intern("insert"), 2, INT2FIX(0),rb_str_new2("0"));
385
+ rb_ary_store(ary, i, hexed );
377
386
  }
378
387
 
379
388
  return rb_funcall(ary, rb_intern("join"), 0);
@@ -393,9 +402,9 @@ static VALUE yubikey_get_uid(VALUE self)
393
402
  }
394
403
 
395
404
  /* call-seq:
396
- * yubikey.counter -> Fixnum
405
+ * yubikey.session_counter -> Fixnum
397
406
  *
398
- * Gets the decoded token counter field.
407
+ * Gets the decoded token session counter field.
399
408
  */
400
409
  static VALUE yubikey_get_counter(VALUE self)
401
410
  {
@@ -480,6 +489,13 @@ static VALUE yubikey_check_crc(VALUE self)
480
489
  }
481
490
  /* Yubikey END */
482
491
 
492
+ /* CRC 16 */
493
+ static VALUE crc16_sing_calc(VALUE self, VALUE data)
494
+ {
495
+ VALUE str = StringValue(data);
496
+ return INT2NUM(yubikey_crc16(RSTRING(str)->ptr, RSTRING(str)->len));
497
+ }
498
+
483
499
  /* Implementation of Yubikey OTP functions.
484
500
  * This Module may be used to interact with a Yubikey
485
501
  */
@@ -523,13 +539,16 @@ void Init_libyubikey() {
523
539
  rb_define_method(cYubikey, "to_str", yubikey_to_str, 0);
524
540
  rb_define_alias(cYubikey, "to_s", "to_str");
525
541
  rb_define_method(cYubikey, "uid", yubikey_get_uid, 0);
526
- rb_define_method(cYubikey, "counter", yubikey_get_counter, 0);
542
+ rb_define_method(cYubikey, "session_counter", yubikey_get_counter, 0);
527
543
  rb_define_method(cYubikey, "triggered_by_capslock?", yubikey_get_triggered_by_capslock, 0);
528
544
  rb_define_method(cYubikey, "timestamp_low", yubikey_get_tsl, 0);
529
545
  rb_define_method(cYubikey, "timestamp_high", yubikey_get_tsh, 0);
530
- rb_define_method(cYubikey, "session", yubikey_get_session, 0);
546
+ rb_define_method(cYubikey, "session_use", yubikey_get_session, 0);
531
547
  rb_define_method(cYubikey, "random", yubikey_get_random, 0);
532
548
  rb_define_method(cYubikey, "crc", yubikey_get_crc, 0);
533
549
  rb_define_method(cYubikey, "crc_residue", yubikey_calc_crc, 0);
534
550
  rb_define_method(cYubikey, "crc?", yubikey_check_crc, 0);
551
+
552
+ mCRC16 = rb_define_module_under(mYubiRuby, "CRC16");
553
+ rb_define_singleton_method(mCRC16, "calculate", crc16_sing_calc, 1);
535
554
  }
data/lib/exceptions.rb ADDED
@@ -0,0 +1,52 @@
1
+ # Author:: Alessio Caiazza (mailto:nolith@abisso.org)
2
+ # Copyright:: Copyright (c) 2010 Alessio Caiazza
3
+ # License:: New BSD License - http://www.opensource.org/licenses/bsd-license.php
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above
14
+ # copyright notice, this list of conditions and the following
15
+ # disclaimer in the documentation and/or other materials provided
16
+ # with the distribution.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+
31
+ module YubiRuby
32
+ class NoHexEncodedError < Exception
33
+ def initialize
34
+ super('The string provided isn\'t hex encoded')
35
+ end
36
+ end
37
+
38
+ class NoModHexEncodedError < Exception
39
+ def inizialize(msg=nil)
40
+ if msg.nil? || msg.empty?
41
+ msg = 'The string provided isn\'t modhex encoded'
42
+ end
43
+ super(msg)
44
+ end
45
+ end
46
+
47
+ class YubikeySessionCounterOverflow < Exception
48
+ def initialize
49
+ super('Session Counter Overflow')
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,209 @@
1
+ # Author:: Alessio Caiazza (mailto:nolith@abisso.org)
2
+ # Copyright:: Copyright (c) 2010 Alessio Caiazza
3
+ # License:: New BSD License - http://www.opensource.org/licenses/bsd-license.php
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above
14
+ # copyright notice, this list of conditions and the following
15
+ # disclaimer in the documentation and/or other materials provided
16
+ # with the distribution.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'rubygems'
31
+ require 'ruby-aes'
32
+ require 'bit-struct'
33
+
34
+ # 1-complement is uses to computer crc field
35
+ #
36
+ #
37
+ class Numeric
38
+ def ones_complement(bits)
39
+ self ^ ((1 << bits) - 1)
40
+ end
41
+ end
42
+
43
+ module YubiRuby
44
+ #
45
+ # This class simulate a yubikey allowing you to make some tests
46
+ #
47
+ class FakeYubikey
48
+ # Maximum allowed length for a modhex encoded public id
49
+ PUBLIC_ID_MAX_LEN = 32
50
+
51
+ #
52
+ # Yubikey internal data
53
+ #
54
+ class Data < BitStruct
55
+ YUBIKEY_BLOCK_SIZE = 16
56
+ YUBIKEY_KEY_SIZE = 16
57
+ YUBIKEY_UID_SIZE = 6
58
+ SESSION_COUNTER_OVERFLOW = 0x7FFF
59
+
60
+ hex_octets :uid, YUBIKEY_UID_SIZE*8, "User ID"
61
+ unsigned :session_counter, 16, {:endian=>:little, :display_name=>"Session Counter"}
62
+ unsigned :timestamp_low, 16, {:endian=>:little, :display_name=>"Timestamp low"}
63
+ unsigned :timestamp_high, 8, "Timestamp high"
64
+ unsigned :session_use, 8, "Session Use"
65
+ unsigned :random, 16, {:endian=>:little, :display_name=>"random"}
66
+ unsigned :crc, 16, {:endian=>:little, :display_name=>"crc"}
67
+
68
+ end
69
+
70
+ attr_accessor :key
71
+ #attr_reader :public_id
72
+
73
+ def initialize
74
+ @public_id = ""
75
+ @data = YubiRuby::FakeYubikey::Data.new
76
+ @data.session_counter = 0
77
+ init
78
+ end
79
+
80
+ #
81
+ # Sets the public id of the key.
82
+ #
83
+ # public_id must be a modhex valid string no longer than
84
+ # 32 char, if longer it will be truncated.
85
+ #
86
+ # @param [String, #read] value the new public_id
87
+ # @return [String] the new public_id
88
+ def public_id=(value)
89
+ raise NoModHexEncodedError unless value.modhex?
90
+
91
+ value = value[0, PUBLIC_ID_MAX_LEN] if value.length > PUBLIC_ID_MAX_LEN
92
+ raise NoModHexEncodedError unless value.modhex?
93
+
94
+ @public_id = value
95
+ end
96
+
97
+ # Returns the value of attribute public_id.
98
+ def public_id
99
+ @public_id
100
+ end
101
+
102
+ # Simaluates the power on sequence of a YubuKey.
103
+ #
104
+ # This will increase session_counter
105
+ def init
106
+ @data.session_counter += 1
107
+ if (@data.session_counter == Data::SESSION_COUNTER_OVERFLOW+1)
108
+ raise YubikeySessionCounterOverflow.new
109
+ end
110
+ @data.session_use = 0
111
+ @data.timestamp_low = rand(2**16)
112
+ @data.timestamp_high = rand(2**8)
113
+ end
114
+
115
+ %w{session_use timestamp_low timestamp_high session_counter random crc}.each do |name|
116
+ define_method name do
117
+ @data.send name
118
+ end
119
+ end
120
+
121
+ # Generates a random key attribute
122
+ def set_random_key
123
+ @key = produce_random_bytes_in_hex Data::YUBIKEY_KEY_SIZE
124
+ end
125
+
126
+ # Generates a random uid attribute
127
+ def set_random_uid
128
+ self.uid= produce_random_bytes_in_hex(Data::YUBIKEY_UID_SIZE)
129
+ end
130
+
131
+ # Sets the attribute uid
132
+ def uid=(value)
133
+ if YubiRuby::HEX.decode(value).size == Data::YUBIKEY_UID_SIZE
134
+ @data.uid = "#{value[0,2]}:#{value[2,2]}:#{value[4,2]}:#{value[6,2]}:#{value[8,2]}:#{value[10,2]}"
135
+ else
136
+ raise NoHexEncodedError.new
137
+ end
138
+ end
139
+
140
+ # Returns the value of attribute uid.
141
+ def uid
142
+ @data.uid.gsub(':','')
143
+ end
144
+
145
+ # Simulates the pression of the yubikey's button
146
+ #
147
+ # session_use will increase and if it overflows also
148
+ # session_counter will be increased
149
+ def otp
150
+ @data.random = rand(2**16)
151
+ @data.session_use += 1
152
+ @data.session_counter +=1 if @data.session_use == 0
153
+ generate_otp
154
+ end
155
+
156
+ def to_s
157
+ @data.to_s
158
+ end
159
+
160
+ def self.generate_specific_otp_and_fake_yubikey(key, uid, session_counter,
161
+ timestamp_low, timestamp_high, session_use, random)
162
+ y_key = FakeYubikey.new
163
+ y_key.key = key
164
+ y_key.uid = uid
165
+ y_key.data.session_use = session_use
166
+ y_key.data.timestamp_low = timestamp_low
167
+ y_key.data.timestamp_high = timestamp_high
168
+ y_key.data.session_counter = session_counter
169
+ y_key.data.random = random
170
+ [y_key.generate_otp, y_key]
171
+ end
172
+
173
+ def data
174
+ @data
175
+ end
176
+
177
+ # Generates an OTP without altering any internal data.
178
+ def generate_otp
179
+ @data.crc = YubiRuby::CRC16.calculate(@data.to_s[0...-2]).ones_complement 16
180
+ data = Aes.encrypt_block(128, 'ECB', YubiRuby::HEX.decode(@key), nil,
181
+ @data.to_s).modhex_encode
182
+ "#{@public_id}#{data}"
183
+ end
184
+
185
+ #serialization support
186
+ def marshal_dump
187
+ {'key' => key, 'uid' => uid, 'session_counter' => @data.session_counter,
188
+ 'public_id' => public_id }
189
+ end
190
+
191
+ #serialization support
192
+ def marshal_load(data)
193
+ @data = YubiRuby::FakeYubikey::Data.new
194
+ self.key = data['key']
195
+ self.uid = data['uid']
196
+ self.data.session_counter = data['session_counter']
197
+ self.public_id = data['public_id']
198
+ init
199
+ end
200
+
201
+
202
+ private
203
+ def produce_random_bytes_in_hex(size)
204
+ tmp = " "*size
205
+ 0.upto(size-1) { |i| tmp[i] = rand(2**8)}
206
+ YubiRuby::HEX.encode(tmp)
207
+ end
208
+ end
209
+ end