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/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