File: clienthello.lua

package info (click to toggle)
lua-resty-core 0.1.32-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,268 kB
  • sloc: sh: 207; perl: 143; makefile: 26
file content (341 lines) | stat: -rw-r--r-- 9,301 bytes parent folder | download
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
-- Copyright (C) Yichun Zhang (agentzh)


local base = require "resty.core.base"
base.allows_subsystem('http', 'stream')


local ffi = require "ffi"
local bit  = require "bit"
local bor = bit.bor
local C = ffi.C
local ffi_str = ffi.string
local get_request = base.get_request
local error = error
local errmsg = base.get_errmsg_ptr()
local get_size_ptr = base.get_size_ptr
local FFI_OK = base.FFI_OK
local subsystem = ngx.config.subsystem
local ngx_phase = ngx.get_phase
local byte = string.byte
local lshift = bit.lshift
local table_insert = table.insert
local table_new = require "table.new"
local intp = ffi.new("int*[1]")
local get_string_buf = base.get_string_buf
local ffi_ushort_pointer_type = ffi.typeof("unsigned short *")
local ffi_cast = ffi.cast


local ngx_lua_ffi_ssl_get_client_hello_server_name
local ngx_lua_ffi_ssl_get_client_hello_ext
local ngx_lua_ffi_ssl_set_protocols
local ngx_lua_ffi_ssl_get_client_hello_ext_present
local ngx_lua_ffi_ssl_get_client_hello_ciphers


if subsystem == 'http' then
    ffi.cdef[[
    int ngx_http_lua_ffi_ssl_get_client_hello_server_name(ngx_http_request_t *r,
        const char **name, size_t *namelen, char **err);

    int ngx_http_lua_ffi_ssl_get_client_hello_ext(ngx_http_request_t *r,
        unsigned int type, const unsigned char **out, size_t *outlen,
        char **err);

    int ngx_http_lua_ffi_ssl_set_protocols(ngx_http_request_t *r,
        int protocols, char **err);

    int ngx_http_lua_ffi_ssl_get_client_hello_ext_present(ngx_http_request_t *r,
        int **extensions, size_t *extensions_len, char **err);
        /* Undefined for the stream subsystem */
    int ngx_http_lua_ffi_ssl_get_client_hello_ciphers(ngx_http_request_t *r,
        unsigned short *ciphers,  size_t ciphers_len, char **err);
        /* Undefined for the stream subsystem */
    ]]

    ngx_lua_ffi_ssl_get_client_hello_server_name =
        C.ngx_http_lua_ffi_ssl_get_client_hello_server_name
    ngx_lua_ffi_ssl_get_client_hello_ext =
        C.ngx_http_lua_ffi_ssl_get_client_hello_ext
    ngx_lua_ffi_ssl_set_protocols = C.ngx_http_lua_ffi_ssl_set_protocols
    ngx_lua_ffi_ssl_get_client_hello_ext_present =
        C.ngx_http_lua_ffi_ssl_get_client_hello_ext_present
    ngx_lua_ffi_ssl_get_client_hello_ciphers =
        C.ngx_http_lua_ffi_ssl_get_client_hello_ciphers



elseif subsystem == 'stream' then
    ffi.cdef[[
    int ngx_stream_lua_ffi_ssl_get_client_hello_server_name(
        ngx_stream_lua_request_t *r, const char **name, size_t *namelen,
        char **err);

    int ngx_stream_lua_ffi_ssl_get_client_hello_ext(
        ngx_stream_lua_request_t *r, unsigned int type,
        const unsigned char **out, size_t *outlen, char **err);

    int ngx_stream_lua_ffi_ssl_set_protocols(ngx_stream_lua_request_t *r,
        int protocols, char **err);
    ]]

    ngx_lua_ffi_ssl_get_client_hello_server_name =
        C.ngx_stream_lua_ffi_ssl_get_client_hello_server_name
    ngx_lua_ffi_ssl_get_client_hello_ext =
        C.ngx_stream_lua_ffi_ssl_get_client_hello_ext
    ngx_lua_ffi_ssl_set_protocols = C.ngx_stream_lua_ffi_ssl_set_protocols
end


local _M = { version = base.version }


local ccharpp = ffi.new("const char*[1]")
local cucharpp = ffi.new("const unsigned char*[1]")


--https://datatracker.ietf.org/doc/html/rfc8701
local TLS_GREASE = {
    [2570] = true,
    [6682] = true,
    [10794] = true,
    [14906] = true,
    [19018] = true,
    [23130] = true,
    [27242] = true,
    [31354] = true,
    [35466] = true,
    [39578] = true,
    [43690] = true,
    [47802] = true,
    [51914] = true,
    [56026] = true,
    [60138] = true,
    [64250] = true
}


-- return server_name, err
function _M.get_client_hello_server_name()
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local sizep = get_size_ptr()

    local rc = ngx_lua_ffi_ssl_get_client_hello_server_name(r, ccharpp, sizep,
                errmsg)
    if rc == FFI_OK then
        return ffi_str(ccharpp[0], sizep[0])
    end

    -- NGX_DECLINED: no sni extension
    if rc == -5 then
        return nil
    end

    return nil, ffi_str(errmsg[0])
end

-- return extensions_table, err
function _M.get_client_hello_ext_present()
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local sizep = get_size_ptr()

    local rc = ngx_lua_ffi_ssl_get_client_hello_ext_present(r, intp,
                                                            sizep, errmsg)
-- the function used under the hood, SSL_client_hello_get1_extensions_present,
-- already excludes GREASE, thank G*d
    if rc == FFI_OK then -- Convert C array to Lua table
        local array = intp[0]
        local size = tonumber(sizep[0])
        local extensions_table = table_new(size, 0)
        for i=0, size-1, 1 do
            extensions_table[i + 1] = array[i]
        end

        return extensions_table
    end

    -- NGX_DECLINED: no extensions; very unlikely
    if rc == -5 then
        return nil
    end

    return nil, ffi_str(errmsg[0])
end

-- return ciphers_table, err
-- excluding GREASE ciphers
function _M.get_client_hello_ciphers()
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local buf = get_string_buf(256) -- 256 bytes is short[128]
    local ciphers = ffi_cast(ffi_ushort_pointer_type, buf)
    local cipher_cnt = ngx_lua_ffi_ssl_get_client_hello_ciphers(r, ciphers,
                                                                128, errmsg)
    if cipher_cnt > 0 then
        local ciphers_table = table_new(16, 0)
        local y = 1
        for i = 0, cipher_cnt - 1 do
            local cipher = tonumber(ciphers[i])
            if not TLS_GREASE[cipher] then
                ciphers_table[y] = cipher
                y = y + 1
            end
        end

        return ciphers_table
    end

    return nil, ffi_str(errmsg[0])
end

-- return ext, err
function _M.get_client_hello_ext(ext_type)
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local sizep = get_size_ptr()

    local rc = ngx_lua_ffi_ssl_get_client_hello_ext(r, ext_type, cucharpp,
                                                    sizep, errmsg)
    if rc == FFI_OK then
        return ffi_str(cucharpp[0], sizep[0])
    end

    -- NGX_DECLINED: no extension
    if rc == -5 then
        return nil
    end

    return nil, ffi_str(errmsg[0])
end

-- tls.handshake.extension.type supported_version
local supported_versions_type = 43
local versions_map = {
    [0x002] = "SSLv2",
    [0x300] = "SSLv3",
    [0x301] = "TLSv1",
    [0x302] = "TLSv1.1",
    [0x303] = "TLSv1.2",
    [0x304] = "TLSv1.3",
}

-- return types, err
function _M.get_supported_versions()
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local sizep = get_size_ptr()

    local rc = ngx_lua_ffi_ssl_get_client_hello_ext(r, supported_versions_type,
                                                    cucharpp, sizep, errmsg)

    if rc ~= FFI_OK then
        -- NGX_DECLINED: no extension
        if rc == -5 then
            return nil
        end

        return nil, ffi_str(errmsg[0])
    end

    local supported_versions_str = ffi_str(cucharpp[0], sizep[0])
    local remain_len = #supported_versions_str
    if remain_len == 0 then
        return nil
    end

    local supported_versions_len = byte(supported_versions_str, 1)
    remain_len = remain_len - 1

    if remain_len ~= supported_versions_len then
        return nil
    end
    local types = {}
    while remain_len >= 2  do
        local type_hi = byte(supported_versions_str, remain_len)
        local type_lo = byte(supported_versions_str, remain_len + 1)
        local type_id = lshift(type_hi, 8) + type_lo
        if versions_map[type_id] ~= nil then
            table_insert(types, versions_map[type_id])
        end
        remain_len = remain_len - 2
    end
    return types
end


local prot_map  = {
    ["SSLv2"] = 0x0002,
    ["SSLv3"] = 0x0004,
    ["TLSv1"] = 0x0008,
    ["TLSv1.1"] = 0x0010,
    ["TLSv1.2"] = 0x0020,
    ["TLSv1.3"] = 0x0040
}


-- return ok, err
function _M.set_protocols(protocols)
    local r = get_request()
    if not r then
        error("no request found")
    end

    if ngx_phase() ~= "ssl_client_hello" then
        error("API disabled in the current context")
    end

    local prots = 0
    for _, v in ipairs(protocols) do
        if not prot_map[v] then
            return nil, "invalid protocols failed"
        end
        prots = bor(prots, prot_map[v])
    end

    local rc = ngx_lua_ffi_ssl_set_protocols(r, prots, errmsg)
    if rc == FFI_OK then
        return true
    end

    return nil, ffi_str(errmsg[0])
end

return _M