-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathotp.sql
134 lines (118 loc) · 3.96 KB
/
otp.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/*
* Author: Marcel Asio
* Created at: 2015-04-03 13:13:43 +0200
*
*/
SET client_min_messages = warning;
create language plperlu;
create function random_base32(_length int default 16)
returns text
language sql as $$
SELECT
string_agg(('{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,2,3,4,5,6,7}'::text[])[ceil(random() * 32)], '')
FROM generate_series(1, _length);
$$;
create function urlencode(in_str text)
returns text
language plpgsql
strict immutable as $$
declare
_i int4;
_temp varchar;
_ascii int4;
_result text := '';
begin
for _i in 1 .. length(in_str) loop
_temp := substr(in_str, _i, 1);
if _temp ~ '[0-9a-zA-Z:/@._?#-]+' then
_result := _result || _temp;
else
_ascii := ascii(_temp);
if _ascii > x'07ff'::int4 then
raise exception 'won''t deal with 3 (or more) byte sequences.';
end if;
if _ascii <= x'07f'::int4 then
_temp := '%'||to_hex(_ascii);
else
_temp := '%'||to_hex((_ascii & x'03f'::int4)+x'80'::int4);
_ascii := _ascii >> 6;
_temp := '%'||to_hex((_ascii & x'01f'::int4)+x'c0'::int4)
||_temp;
end if;
_result := _result || upper(_temp);
end if;
end loop;
return _result;
end;
$$;
create function provisioning_url(_email text, _secret text, _interval int, _issuer text)
returns text
language sql
strict immutable as $$
select concat(
'otpauth://totp/', urlencode(_email),
'?secret=', urlencode(_secret),
'&period=', urlencode(_interval::text),
'&issuer=', urlencode(_issuer)
);
$$;
create function pack(text)
returns text
language plperlu as $$
return pack("B*", shift);
$$;
create function unpack(text)
returns text
language plperlu as $$
return unpack("H*", shift);
$$;
create function perl_hmac(text, text)
returns text
language plperlu as $$
use Digest::HMAC_SHA1 qw/ hmac_sha1_hex /;
return hmac_sha1_hex(pack("H*", shift), pack("H*", shift));
$$;
create function generate_totp(_secret text, _interval int default 30, _length int default 6)
returns text
language plpgsql as $$
declare
_input_check int := length(_secret) % 8;
_buffer text := '';
_b32_secret text;
_key text;
_lpad_time text := lpad(to_hex(floor(extract(epoch from now()) / _interval)::int), 16, '0');
_hmac text;
_offset int;
_part1 int;
begin
IF NOT _secret ~ '^[a-z2-7]+$' THEN
RAISE EXCEPTION 'Data contains non-base32 characters';
END IF;
IF _input_check = 1 OR _input_check = 3 OR _input_check = 8 THEN
RAISE EXCEPTION 'Length of data invalid';
END IF;
with chars2bits AS (
select
character,
(index - 1)::bit(5)::text AS index
from unnest('{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,2,3,4,5,6,7}'::text[]) with ordinality as t (character, index)
)
select string_agg(c.index, '') INTO _buffer
from regexp_split_to_table(_secret, '') s
inner join chars2bits c ON (s = c.character);
IF NOT _buffer ~ ('0{' || length(_buffer) % 8 || '}$') THEN
RAISE EXCEPTION 'PADDING number of bits at the end of output buffer are not all zero';
END IF;
_b32_secret := pack(_buffer);
_key := unpack(_b32_secret);
_hmac := perl_hmac(_lpad_time, _key);
select ('x' || lpad(substring(_hmac from '.$'), 8, '0'))::bit(32)::int INTO _offset;
select ('x' || lpad(substring(_hmac, _offset * 2 + 1, 8), 8, '0'))::bit(32)::int INTO _part1;
RETURN substring((_part1 & x'7fffffff'::int)::text from '.{' || _length || '}$');
end;
$$;
create function verify_totp(_secret text, _interval int, _otp text)
returns boolean
language sql as $$
SELECT generate_totp(_secret, _interval) = _otp;
$$;