|
1 | 1 | require 'spec_helper' |
2 | 2 |
|
3 | 3 | RSpec.describe ROTP::Base32 do |
4 | | - describe '.random_base32' do |
| 4 | + describe '.random' do |
5 | 5 | context 'without arguments' do |
6 | | - let(:base32) { ROTP::Base32.random_base32 } |
| 6 | + let(:base32) { ROTP::Base32.random } |
7 | 7 |
|
8 | | - it 'is 32 characters long' do |
| 8 | + it 'is 20 bytes (160 bits) long (resulting in a 32 character base32 code)' do |
| 9 | + expect(ROTP::Base32.decode(base32).length).to eq 20 |
9 | 10 | expect(base32.length).to eq 32 |
10 | 11 | end |
11 | 12 |
|
12 | 13 | it 'is base32 charset' do |
13 | | - expect(base32).to match(/\A[a-z2-7]+\z/) |
| 14 | + expect(base32).to match(/\A[A-Z2-7]+\z/) |
14 | 15 | end |
15 | 16 | end |
16 | 17 |
|
17 | 18 | context 'with arguments' do |
18 | | - let(:base32) { ROTP::Base32.random_base32 32 } |
| 19 | + let(:base32) { ROTP::Base32.random 48 } |
19 | 20 |
|
20 | | - it 'allows a specific length' do |
21 | | - expect(base32.length).to eq 32 |
| 21 | + it 'returns the appropriate byte length code' do |
| 22 | + expect(ROTP::Base32.decode(base32).length).to eq 48 |
22 | 23 | end |
23 | 24 | end |
24 | 25 | end |
|
33 | 34 |
|
34 | 35 | context 'valid input data' do |
35 | 36 | it 'correctly decodes a string' do |
36 | | - expect(ROTP::Base32.decode('F').unpack('H*').first).to eq '28' |
37 | | - expect(ROTP::Base32.decode('23').unpack('H*').first).to eq 'd6' |
38 | | - expect(ROTP::Base32.decode('234').unpack('H*').first).to eq 'd6f8' |
39 | | - expect(ROTP::Base32.decode('234A').unpack('H*').first).to eq 'd6f800' |
40 | | - expect(ROTP::Base32.decode('234B').unpack('H*').first).to eq 'd6f810' |
41 | | - expect(ROTP::Base32.decode('234BCD').unpack('H*').first).to eq 'd6f8110c' |
42 | | - expect(ROTP::Base32.decode('234BCDE').unpack('H*').first).to eq 'd6f8110c80' |
43 | | - expect(ROTP::Base32.decode('234BCDEFG').unpack('H*').first).to eq 'd6f8110c8530' |
44 | | - expect(ROTP::Base32.decode('234BCDEFG234BCDEFG').unpack('H*').first).to eq 'd6f8110c8536b7c0886429' |
| 37 | + expect(ROTP::Base32.decode('2EB7C66WC5TSO').unpack('H*').first).to eq 'd103f17bd6176727' |
| 38 | + expect(ROTP::Base32.decode('Y6Y5ZCAC7NABCHSJ').unpack('H*').first).to eq 'c7b1dc8802fb40111e49' |
| 39 | + end |
| 40 | + |
| 41 | + it 'correctly decode strings with trailing bits (not a multiple of 8)' do |
| 42 | + # Dropbox style 26 characters (26*5==130 bits or 16.25 bytes, but chopped to 128) |
| 43 | + # Matches the behavior of Google Authenticator, drops extra 2 empty bits |
| 44 | + expect(ROTP::Base32.decode('YVT6Z2XF4BQJNBMTD7M6QBQCEM').unpack('H*').first).to eq 'c567eceae5e0609685931fd9e8060223' |
| 45 | + |
| 46 | + # For completeness, test all the possibilities allowed by Google Authenticator |
| 47 | + # Drop the incomplete empty extra 4 bits (28*5==140bits or 17.5 bytes, chopped to 136 bits) |
| 48 | + expect(ROTP::Base32.decode('5GGZQB3WN6LD7V3L5HPDYTQUANEQ').unpack('H*').first).to eq 'e98d9807766f963fd76be9de3c4e140349' |
| 49 | + |
45 | 50 | end |
46 | 51 |
|
47 | 52 | context 'with padding' do |
48 | 53 | it 'correctly decodes a string' do |
49 | | - expect(ROTP::Base32.decode('F==').unpack('H*').first).to eq '28' |
| 54 | + expect(ROTP::Base32.decode('234A===').unpack('H*').first).to eq 'd6f8' |
50 | 55 | end |
51 | 56 | end |
| 57 | + |
52 | 58 | end |
53 | 59 | end |
| 60 | + |
| 61 | + describe '.encode' do |
| 62 | + context 'encode input data' do |
| 63 | + it 'correctly encodes data' do |
| 64 | + expect(ROTP::Base32.encode(hex_to_bin('3c204da94294ff82103ee34e96f74b48'))).to eq 'HQQE3KKCST7YEEB64NHJN52LJA' |
| 65 | + end |
| 66 | + end |
| 67 | + end |
| 68 | + |
| 69 | +end |
| 70 | + |
| 71 | +def hex_to_bin(s) |
| 72 | + s.scan(/../).map { |x| x.hex }.pack('c*') |
54 | 73 | end |
0 commit comments