yubikey/init.lua

changeset 0
598d09faf89c
equal deleted inserted replaced
-1:000000000000 0:598d09faf89c
1 local _M = {};
2
3 local aeslua = require "aeslua";
4 local ciphermode = require "aeslua.ciphermode";
5 local crc16 = require "crc16";
6
7 local modhex2hex;
8 do
9 local modhex_map = {};
10 local modhex_chars = "cbdefghijklnrtuv";
11 for i=1,16 do
12 modhex_map[modhex_chars:sub(i,i)] = ("%x"):format(i-1);
13 end
14 function modhex2hex(modhex)
15 if modhex then
16 return (modhex:gsub(".", modhex_map));
17 end
18 end
19 end
20 _M.modhex2hex = modhex2hex;
21
22 local function hex2bin(hex)
23 if hex then
24 return (hex:gsub("..", function (c) return string.char(tonumber(c, 16)); end));
25 end
26 end
27 _M.hex2bin = hex2bin;
28
29 local function bin2hex(bin)
30 if bin then
31 return (bin:gsub(".", function (b) return ("%02x"):format(b:byte()); end));
32 end
33 end
34 _M.bin2hex = bin2hex;
35
36 local otp_parser = {};
37 local otp_parser_mt = { __index = otp_parser };
38
39 function _M.new_fetch_key_hex(fetchkeyhex)
40 return function (...) return hex2bin(fetchkeyhex(...)); end
41 end
42
43 function _M.new_otp_parser(config)
44 return setmetatable({
45 config = config,
46 }, otp_parser_mt);
47 end
48
49 function otp_parser:parse(otp, key)
50 -- Password is any extra data before the real OTP
51 local password;
52 if #otp > 32 + self.config.prefix_length then
53 password = otp:sub(1, #otp - (32 + self.config.prefix_length));
54 otp = otp:sub(#password+1);
55 end
56 local prefix = otp:sub(1, self.config.prefix_length);
57 local token = otp:sub(#prefix+1);
58 if not key then
59 key = self.config.fetch_key(prefix);
60 else
61 key = hex2bin(key);
62 end
63 if not key then return false, "no-key"; end
64 key = {key:byte(1,#key)}
65 local decrypted = ciphermode.decryptString(key, hex2bin(modhex2hex(token)), ciphermode.decryptCBC);
66 if not decrypted then return false, "decrypt-failed"; end
67 -- Extract private UID
68 local uid = decrypted:sub(1,6);
69 -- Build insertion counter (2 bytes)
70 local use1, use2 = decrypted:sub(7,8):byte(1,2);
71 local use_ctr = use1 + math.pow(2, 8)*use2;
72 -- Build timestamp (3 bytes)
73 local time1, time2, time3 = decrypted:sub(9,11):byte(1,3);
74 local timestamp = time1 + math.pow(2,8)*time2 + math.pow(2, 16)*time3;
75 -- Extract session counter (1 byte)
76 local session_ctr = decrypted:sub(12, 12):byte();
77
78 if crc16.hash(decrypted) ~= 0xf0b8 then
79 return false, "invalid-checksum";
80 end
81
82 -- Return parsed fields
83 return true, {
84 password = password;
85 public_id = prefix;
86 token = token;
87 private_id = bin2hex(uid);
88 session_counter = session_ctr;
89 use_counter = use_ctr;
90 timestamp = timestamp;
91 };
92 end
93
94 function _M.new_authenticator(config)
95 local parser = _M.new_otp_parser(config);
96 local function authenticate(self, otp, key, device_info, userdata)
97 -- Parse OTP, get device data (from config callback)
98 local ok, ret = parser:parse(otp, key);
99 if not ok then return ok, ret; end
100
101 if not device_info then
102 device_info = config.fetch_device_info(ret);
103 end
104
105 if ret.use_counter < (device_info.use_counter or 0)
106 or ((ret.use_counter == (device_info.use_counter or 0))
107 and (ret.session_counter <= (device_info.session_counter or 0))) then
108 return false, "otp-already-used";
109 end
110
111 local authed, err = config.check_credentials(ret, device_info, userdata);
112 if not authed then return authed, err; end
113
114 device_info.use_counter = ret.use_counter;
115 device_info.session_counter = ret.session_counter;
116
117 config.store_device_info(device_info, userdata);
118
119 return true, ret;
120 end
121 return { authenticate = authenticate };
122 end
123
124 return _M;

mercurial