YubiRuby 0.1.0 → 1.0.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/lib/hex.rb CHANGED
@@ -47,7 +47,16 @@ module YubiRuby
47
47
  #
48
48
  def self.encode( obj )
49
49
  s = obj.to_str
50
- s.unpack('U'*s.length).collect {|x| x.to_s 16}.join
50
+ s.unpack('U'*s.length).collect {|x| tmp = x.to_s 16; tmp.length == 1 ? "0#{tmp}" : tmp }.join
51
+ rescue ArgumentError
52
+ #non UTF-8 string
53
+ s = obj.to_str
54
+ out = []
55
+ 0.upto(s.length-1) do |i|
56
+ tmp = s[i].to_s 16
57
+ out << (tmp.length == 1 ? "0#{tmp}" : tmp )
58
+ end
59
+ out.join
51
60
  end
52
61
 
53
62
  # call-seq:
@@ -59,17 +68,19 @@ module YubiRuby
59
68
  end
60
69
 
61
70
  # call-seq:
62
- # YubiRuby::HEX.decode("hex string") -> "string" or ""
71
+ # YubiRuby::HEX.decode("hex string") -> "string"
63
72
  #
64
73
  # Decodes <tt>obj.to_str</tt> into a <tt>string</tt>.
65
74
  #
66
- # An <tt>hex string</tt> length must be pair, if not an
67
- # empty string is returned.
75
+ # An <tt>hex string</tt> length must be pair, if not a
76
+ # NoHexEncodedError excpetion is raised
68
77
  def self.decode( obj )
69
78
  s = obj.to_str
70
79
  dec = ""
71
- if (s.length % 2 == 0)
80
+ if hex?(s)
72
81
  (s.length/2).times { |i| dec << s[i*2,2].hex.chr }
82
+ else
83
+ raise NoHexEncodedError.new
73
84
  end
74
85
 
75
86
  return dec
@@ -82,5 +93,25 @@ module YubiRuby
82
93
  def hex_decode
83
94
  HEX.decode(self)
84
95
  end
96
+
97
+ # call-seq:
98
+ # YubiRuby::HEX.hex?("hex string") -> true or false
99
+ #
100
+ # Tests if <tt>obj.to_str</tt> is a valid a <tt>hex string</tt>.
101
+ #
102
+ # An <tt>hex string</tt> length must be pair.
103
+ def self.hex?( obj )
104
+ s = obj.to_str
105
+ return false unless (s.length % 2 == 0)
106
+ return (s.upcase =~ /[^A-F0-9]/).nil?
107
+ end
108
+
109
+ # call-seq:
110
+ # hex? -> true or false
111
+ #
112
+ # Invokes YubiRuby::HEX.hex? on +self+.
113
+ def hex?
114
+ HEX.hex?(self)
115
+ end
85
116
  end
86
117
  end
data/lib/yubiruby.rb CHANGED
@@ -27,8 +27,10 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
+ require 'exceptions'
30
31
  require 'libyubikey'
31
32
  require 'hex'
33
+ require 'fake_yubikey'
32
34
 
33
35
  module YubiRuby
34
36
  # C code version
@@ -0,0 +1,50 @@
1
+ # =======================
2
+ # = Documentation tasks =
3
+ # =======================
4
+ begin
5
+ require 'yard'
6
+ require 'yard/rake/yardoc_task'
7
+
8
+ task :documentation => :'documentation:generate'
9
+ namespace :documentation do
10
+ YARD::Rake::YardocTask.new :generate do |t|
11
+ t.files = ['lib/**/*.rb', 'ext/**/*.c']
12
+ t.options = ['--output-dir', File.join('meta', 'documentation'),
13
+ '--readme', 'README']
14
+ end
15
+
16
+ YARD::Rake::YardocTask.new :dotyardoc do |t|
17
+ t.files = ['lib/**/*.rb', 'ext/**/*.c']
18
+ t.options = ['--no-output',
19
+ '--readme', 'README']
20
+ end
21
+
22
+ class Numeric
23
+ def pretty_inspect(decimal_points = 3)
24
+ bits = self.to_s.split('.')
25
+ bits[0] = bits[0].reverse.scan(/\d{1,3}/).join(',').reverse
26
+ bits[1] = bits[1][0...decimal_points] if bits[1]
27
+ bits.join('.')
28
+ end
29
+ end
30
+
31
+ task :verify do
32
+ documentation_threshold = 50.0
33
+ doc = YARD::CLI::Yardoc.new; doc.generate = false; doc.run
34
+
35
+ percent_documented = (
36
+ YARD::Registry.all.select {|o| !o.docstring.empty? }.size /
37
+ YARD::Registry.all.size.to_f
38
+ ) * 100
39
+ puts "Documentation coverage: #{percent_documented.pretty_inspect(1)}% (threshold: #{documentation_threshold.pretty_inspect(1)}%)"
40
+ end
41
+
42
+ task :open do
43
+ system 'open ' + File.join('meta', 'documentation', 'index.html') if PLATFORM['darwin']
44
+ end
45
+ end
46
+
47
+ rescue LoadError
48
+ desc 'You need the `yard` gem to generate documentation'
49
+ task :documentation
50
+ end
@@ -0,0 +1,155 @@
1
+ require 'test/path_loader'
2
+
3
+ describe 'YubiRuby::FakeYubikey' do
4
+ @@UID = 'ffa4f1fa55e0'
5
+ @@KEY = '884b1fc18da3888b59460f6e33acabf5'
6
+
7
+ before :all do
8
+ @key = YubiRuby::FakeYubikey.new
9
+ end
10
+
11
+ it "should start in a valid state" do
12
+ @key.uid.should == "000000000000"
13
+ @key.session_use.should == 0
14
+ @key.session_counter.should == 1
15
+ @key.public_id.should == ""
16
+ end
17
+
18
+ it "should be serializable" do
19
+ @key.public_id = 'hehkhbhv'
20
+ @key.key = @@KEY
21
+ @key.uid = @@UID
22
+ 3.times { @key.init }
23
+ File.open('key.dump', 'w+') do |f|
24
+ Marshal.dump(@key, f)
25
+ end
26
+
27
+ key2 = nil
28
+ File.open('key.dump') do |f|
29
+ key2 = Marshal.load(f)
30
+ end
31
+
32
+ key2.public_id.should == @key.public_id
33
+ key2.uid.should == @key.uid
34
+ key2.data.session_counter.should == @key.session_counter+1
35
+ key2.key.should == @key.key
36
+ end
37
+
38
+ describe "#set_random_uid" do
39
+ it "should generate a hex valid string" do
40
+ @key.set_random_uid
41
+ @key.uid.hex?.should == true
42
+ end
43
+ end
44
+
45
+ describe "#set_random_key" do
46
+ it "should generate a hex valid string" do
47
+ @key.set_random_key
48
+ @key.key.hex?.should == true
49
+ end
50
+ end
51
+
52
+ describe '#otp' do
53
+ it "should generate valid otp" do
54
+ aes = @key.set_random_key
55
+ @key.set_random_uid
56
+
57
+ validator = YubiRuby::Yubikey.new aes
58
+ validator.parse(@key.otp).should == true
59
+ validator.uid.should == @key.uid
60
+ validator.session_use.should == @key.session_use
61
+ validator.session_counter.should == @key.session_counter
62
+ end
63
+
64
+ it "should produce a session overflow after 2**8 generations" do
65
+ @key = YubiRuby::FakeYubikey.new
66
+ aes = @key.set_random_key
67
+ uid = @key.set_random_uid
68
+ session = @key.session_counter
69
+ 1.upto(2**8-1) do |i|
70
+ @key.otp
71
+ @key.session_counter.should == session
72
+ @key.session_use.should == i
73
+ end
74
+ @key.otp
75
+ @key.session_counter.should == session+1
76
+ @key.session_use.should == 0
77
+ end
78
+
79
+ it "should raise YubiRuby::YubikeySessionCounterOverflow after SESSION_COUNTER_OVERFLOW initialization" do
80
+ key = YubiRuby::FakeYubikey.new
81
+ aes = key.set_random_key
82
+ uid = key.set_random_uid
83
+ (session = key.session_counter).should == 1
84
+ 2.upto(YubiRuby::FakeYubikey::Data::SESSION_COUNTER_OVERFLOW) do |i|
85
+ lambda { key.init }.should_not raise_error(YubiRuby::YubikeySessionCounterOverflow)
86
+ end
87
+ lambda { key.init }.should raise_error(YubiRuby::YubikeySessionCounterOverflow)
88
+ end
89
+
90
+ it "should starts with public_id" do
91
+ @key = YubiRuby::FakeYubikey.new
92
+ @key.key = @@KEY
93
+ @key.uid = @@UID
94
+ @key.public_id = 'hehkhbhv'
95
+ @key.otp[0,8].should == 'hehkhbhv'
96
+ end
97
+ end
98
+
99
+ describe '#generate_specific_otp_and_fake_yubikey' do
100
+ it "should work ;)" do
101
+ aes = 'df5b9d7591b0b3c2819f7a0dd7d1547d'
102
+ session = 53
103
+ tsl = 31716
104
+ tsh = 106
105
+ counter = 0
106
+ random = 30172
107
+ crc = 58822
108
+ uid = "6d8dec078841"
109
+ check_otp = 'rfechkcdkjrflgrjlirdgivnedbbtcik'
110
+ result = YubiRuby::FakeYubikey.generate_specific_otp_and_fake_yubikey(
111
+ aes, uid, session, tsl, tsh, counter, random)
112
+ mycrc = YubiRuby::CRC16.calculate(result[1].to_s[0...-2]).ones_complement 16
113
+
114
+ lambda {
115
+ YubiRuby::Yubikey.new(aes).parse(result[0]).should == true
116
+ }.should_not raise_error(TypeError)
117
+ result[1].key.should == aes
118
+ result[1].session_counter.should == session
119
+ result[1].timestamp_low.should == tsl
120
+ result[1].timestamp_high.should == tsh
121
+ result[1].session_use.should == counter
122
+ result[1].random.should == random
123
+ result[1].crc.should == crc
124
+ result[1].uid.should == uid
125
+ result[0].should == check_otp
126
+ end
127
+ end
128
+
129
+ describe "#public_id=" do
130
+ it "should accepts modhex strings" do
131
+ id = 'rfechkcdkjrflgrjlirdgivnedbbtcik'
132
+ @key.public_id = id
133
+ @key.public_id.should == id
134
+ id = 'hehkhbhv'
135
+ @key.public_id = id
136
+ @key.public_id.should == id
137
+ end
138
+
139
+ it "should accepts only modhex strings" do
140
+ id = @key.public_id
141
+ lambda {@key.public_id = "pluto"}.should raise_error(YubiRuby::NoModHexEncodedError)
142
+ @key.public_id.should == id
143
+ lambda{@key.public_id = "pip!8"}.should raise_error(YubiRuby::NoModHexEncodedError)
144
+ @key.public_id.should == id
145
+ end
146
+
147
+ it "should truncate longer string" do
148
+ id = 'rfechkcdkjrflgrjlirdgivnedbbtcikrfechkcdkjrflgrjlirdgivnedbbtcik'
149
+ (id.length > 32).should == true
150
+
151
+ @key.public_id = id
152
+ @key.public_id.should == id[0,32]
153
+ end
154
+ end
155
+ end
data/test/hex_spec.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'test/path_loader'
2
+
3
+ describe 'YubiRuby::HEX' do
4
+ describe '#encode' do
5
+ it 'should work as expected' do
6
+ str = "ciao\n"
7
+ hex = "6369616f0a"
8
+ YubiRuby::HEX.encode(str).should == hex
9
+ end
10
+
11
+ it 'should be a reversible operation' do
12
+ str = "ciao\n"
13
+ hex = YubiRuby::HEX.encode(str)
14
+ YubiRuby::HEX.decode(hex).should == str
15
+ end
16
+ end
17
+
18
+ describe '#decode' do
19
+ it 'should work as expected' do
20
+ str = "ciao\n"
21
+ hex = "6369616f0a"
22
+ YubiRuby::HEX.decode(hex).should == str
23
+ end
24
+
25
+ it 'should be a reversible operation' do
26
+ hex = "6369616f0a"
27
+ str = YubiRuby::HEX.decode(hex)
28
+ YubiRuby::HEX.encode(str).should == hex
29
+ end
30
+
31
+ it 'should raise NoHexEncodedError if decoding non Hex string' do
32
+ lambda { YubiRuby::HEX.decode('a') }.should raise_error(YubiRuby::NoHexEncodedError)
33
+ lambda {YubiRuby::HEX.decode('zebr') }.should raise_error(YubiRuby::NoHexEncodedError)
34
+ lambda {YubiRuby::HEX.decode('!af5') }.should raise_error(YubiRuby::NoHexEncodedError)
35
+ lambda {YubiRuby::HEX.decode('0x12ff') }.should raise_error(YubiRuby::NoHexEncodedError)
36
+ end
37
+ end
38
+
39
+ describe '#hex?' do
40
+ it 'should recognize valid hex' do
41
+ YubiRuby::HEX.hex?('7af5').should == true
42
+ YubiRuby::HEX.hex?('12fF').should == true
43
+ end
44
+
45
+ it 'should recognize invalid hex' do
46
+ YubiRuby::HEX.hex?('a').should == false
47
+ YubiRuby::HEX.hex?('zebr').should == false
48
+ YubiRuby::HEX.hex?('!af5').should == false
49
+ YubiRuby::HEX.hex?('0x12ff').should == false
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,81 @@
1
+ require 'test/path_loader'
2
+
3
+ describe 'YubiRuby::ModHex' do
4
+
5
+ it 'should be a reversible operation' do
6
+ str = "test"
7
+ hex = "ifhgieif"
8
+ YubiRuby::ModHex.decode(YubiRuby::ModHex.encode(str)).should == str
9
+ YubiRuby::ModHex.encode(YubiRuby::ModHex.decode(hex)).should == hex
10
+ end
11
+
12
+ describe '#encode' do
13
+ it 'should work as expected' do
14
+ str = "test"
15
+ hex = "ifhgieif"
16
+ YubiRuby::ModHex.encode(str).should == hex
17
+ end
18
+
19
+ it "should double the string lenght" do
20
+ str = '1234zz567890'
21
+ str.size.should == 12
22
+ str.modhex_encode.size.should == 24
23
+ end
24
+
25
+ end
26
+
27
+ describe '#decode' do
28
+ it 'should work as expected' do
29
+ str = "test"
30
+ hex = "ifhgieif"
31
+ YubiRuby::ModHex.decode(hex).should == str
32
+ end
33
+
34
+ it 'should raise NoModHexEncodedError if decoding non ModHex string' do
35
+ lambda { YubiRuby::ModHex.decode('a') }.should raise_error(YubiRuby::NoModHexEncodedError)
36
+ lambda {YubiRuby::ModHex.decode('zebr') }.should raise_error(YubiRuby::NoModHexEncodedError)
37
+ lambda {YubiRuby::ModHex.decode('!af5') }.should raise_error(YubiRuby::NoModHexEncodedError)
38
+ lambda {YubiRuby::ModHex.decode('0x12ff') }.should raise_error(YubiRuby::NoModHexEncodedError)
39
+ end
40
+
41
+ it "should halves the string lenght" do
42
+ otp = 'dbkutdgrnlvrhdiregebvkkrgtuefjru'
43
+ otp.size.should == 32
44
+ otp.modhex_decode.size.should == 16
45
+ end
46
+ end
47
+
48
+ describe '#hex?' do
49
+ it 'should recognize valid hex' do
50
+ YubiRuby::ModHex.modhex?('ifhgieif').should == true
51
+ YubiRuby::ModHex.modhex?('ichkicichv').should == true
52
+ end
53
+
54
+ it 'should recognize invalid hex' do
55
+ YubiRuby::ModHex.modhex?('a').should == false
56
+ YubiRuby::ModHex.modhex?('zebr').should == false
57
+ YubiRuby::ModHex.modhex?('!af5').should == false
58
+ YubiRuby::ModHex.modhex?('0x12ff').should == false
59
+ end
60
+ end
61
+ end
62
+
63
+ describe 'String' do
64
+ describe '#modhex_decode' do
65
+ it "should call YubiRuby::ModHex#decode once" do
66
+ otp = 'dbkutdgrnlvrhdiregebvkkrgtuefjru'
67
+ YubiRuby::ModHex.should_receive(:decode).once.with(otp)
68
+ YubiRuby::ModHex.should_not_receive(:encode)
69
+ otp.modhex_decode
70
+ end
71
+ end
72
+
73
+ describe '#modhex_encode' do
74
+ it "should call YubiRuby::ModHex#encode once" do
75
+ str = 'ciaociao ciao'
76
+ YubiRuby::ModHex.should_receive(:encode).once.with(str)
77
+ YubiRuby::ModHex.should_not_receive(:decode)
78
+ str.modhex_encode
79
+ end
80
+ end
81
+ end
@@ -1,6 +1,6 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
2
  $:.unshift File.join(File.dirname(__FILE__),'..','ext')
3
3
 
4
- require "test/unit"
4
+ require 'yubiruby'
5
5
 
6
- require "tc_modhex"
6
+ String.send(:include, YubiRuby::HEX)
data/test/tc_crc16.rb ADDED
@@ -0,0 +1,10 @@
1
+ class TestCRC16 < Test::Unit::TestCase
2
+ def test_crc_calculate
3
+ str = YubiRuby::HEX.decode '6d8dec07884101002216a1041b7c'
4
+ otp_decoded = YubiRuby::HEX.decode '6d8dec07884101002216a1041b7c105e'
5
+ crc = 24080
6
+ assert_equal(0xF0B8, YubiRuby::CRC16.calculate(otp_decoded))
7
+ assert_equal(crc, YubiRuby::CRC16.calculate(str).ones_complement(16))
8
+ end
9
+
10
+ end
@@ -0,0 +1,89 @@
1
+ class TestFakeYubikey < Test::Unit::TestCase
2
+ def test_init
3
+ key = YubiRuby::FakeYubikey.new
4
+ assert_equal("000000000000", key.uid )
5
+ assert_equal(0, key.session_use)
6
+ assert_equal(1, key.session_counter)
7
+ end
8
+
9
+ def test_otp
10
+ key = YubiRuby::FakeYubikey.new
11
+ aes = key.set_random_key
12
+ uid = key.set_random_uid
13
+ assert(aes.hex?)
14
+ assert(uid.hex?)
15
+ validator = YubiRuby::Yubikey.new aes
16
+ assert(validator.parse(key.otp))
17
+ assert_equal(validator.uid, uid)
18
+ assert_equal(validator.session_use, key.session_use)
19
+ assert_equal(validator.session_counter, key.session_counter)
20
+ end
21
+
22
+ def test_generate_random_uid
23
+ key = YubiRuby::FakeYubikey.new
24
+ assert_equal(key.set_random_uid, key.uid)
25
+ end
26
+
27
+ def test_generate_random_key
28
+ key = YubiRuby::FakeYubikey.new
29
+ assert_equal(key.set_random_key, key.key)
30
+ end
31
+
32
+ def test_generate_specific_otp_and_fake_yubikey
33
+ aes = 'df5b9d7591b0b3c2819f7a0dd7d1547d'
34
+ session = 53
35
+ tsl = 31716
36
+ tsh = 106
37
+ counter = 0
38
+ random = 30172
39
+ crc = 58822
40
+ uid = "6d8dec078841"
41
+ check_otp = 'rfechkcdkjrflgrjlirdgivnedbbtcik'
42
+ result = YubiRuby::FakeYubikey.generate_specific_otp_and_fake_yubikey(aes, uid, session,
43
+ tsl, tsh, counter, random)
44
+ mycrc = YubiRuby::CRC16.calculate(result[1].to_s[0...-2]).ones_complement 16
45
+
46
+
47
+ YubiRuby::Yubikey.new(aes).parse(result[0])
48
+ assert_equal(aes,result[1].key)
49
+ assert_equal(session,result[1].session_counter)
50
+ assert_equal(tsl,result[1].timestamp_low)
51
+ assert_equal(tsh,result[1].timestamp_high)
52
+ assert_equal(counter,result[1].session_use)
53
+ assert_equal(random,result[1].random)
54
+ assert_equal(crc,result[1].crc)
55
+ assert_equal(uid,result[1].uid)
56
+ assert_equal(check_otp,result[0])
57
+ end
58
+
59
+ def test_session_use_overflow
60
+ key = YubiRuby::FakeYubikey.new
61
+ aes = key.set_random_key
62
+ uid = key.set_random_uid
63
+ session = key.session_counter
64
+ 1.upto(2**8-1) do |i|
65
+ key.otp
66
+ assert_equal(session, key.session_counter)
67
+ assert_equal(i, key.session_use)
68
+ end
69
+ key.otp
70
+ assert_equal(session+1, key.session_counter)
71
+ assert_equal(0, key.session_use)
72
+ end
73
+
74
+ # ruby(71000) malloc: *** error for object 0x101aa6e70:
75
+ # incorrect checksum for freed object - object was probably modified after being freed.
76
+ # def test_session_counter_overflow
77
+ # key = YubiRuby::FakeYubikey.new
78
+ # aes = key.set_random_key
79
+ # uid = key.set_random_uid
80
+ # session = key.session_counter
81
+ # assert_equal(1, session)
82
+ # 2.upto(YubiRuby::FakeYubikey::Data::SESSION_COUNTER_OVERFLOW) do |i|
83
+ # key.init
84
+ # end
85
+ # assert_raise YubiRuby::YubikeySessionCounterOverflow do
86
+ # key.init
87
+ # end
88
+ # end
89
+ end