• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tarantool / http / 19263358093

11 Nov 2025 10:55AM UTC coverage: 78.995% (+0.2%) from 78.747%
19263358093

push

github

themilchenko
roles: support `ssl_verify_client` option

Since http server supports a new `ssl_verify_client` option it is
necessary to support it in role api as well.

This patch introduces a new config parameter in httpd role with the same
`ssl_verify_client` name.

Closes #207

880 of 1114 relevant lines covered (78.99%)

79.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.04
/http/sslsocket.lua
1
local TIMEOUT_INFINITY = 500 * 365 * 86400
1✔
2
local LIMIT_INFINITY   = 2147483647
1✔
3

4
local log = require('log')
1✔
5
local ffi = require('ffi')
1✔
6
local fio = require('fio')
1✔
7
local socket = require('socket')
1✔
8
local buffer = require('buffer')
1✔
9
local clock = require('clock')
1✔
10
local errno = require('errno')
1✔
11

12
pcall(ffi.cdef, 'typedef struct SSL_METHOD {} SSL_METHOD;')
1✔
13
pcall(ffi.cdef, 'typedef struct SSL_CTX {} SSL_CTX;')
1✔
14
pcall(ffi.cdef, 'typedef struct SSL {} SSL;')
1✔
15

16
pcall(ffi.cdef, [[
2✔
17
    const SSL_METHOD *TLS_server_method(void);
18
    const SSL_METHOD *TLS_client_method(void);
19

20
    SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
21
    void SSL_CTX_free(SSL_CTX *);
22

23
    int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
24
    int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
25
    void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);
26
    typedef int (*pem_passwd_cb)(char *buf, int size, int rwflag, void *userdata);
27

28
    void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_passwd_cb cb);
29

30
    int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,
31
                                      const char *CApath);
32
    int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);
33
    void SSL_CTX_set_verify(SSL_CTX *ctx, int mode,
34
                            int (*verify_callback)(int, void *));
35

36
    SSL *SSL_new(SSL_CTX *ctx);
37
    void SSL_free(SSL *ssl);
38

39
    int SSL_set_fd(SSL *s, int fd);
40

41
    void SSL_set_connect_state(SSL *s);
42
    void SSL_set_accept_state(SSL *s);
43

44
    int SSL_write(SSL *ssl, const void *buf, int num);
45
    int SSL_read(SSL *ssl, void *buf, int num);
46

47
    int SSL_pending(const SSL *ssl);
48

49
    void ERR_clear_error(void);
50
    char *ERR_error_string(unsigned long e, char *buf);
51
    unsigned long ERR_peek_last_error(void);
52

53
    int SSL_get_error(const SSL *s, int ret_code);
54

55
    void *memmem(const void *haystack, size_t haystacklen,
56
                 const void *needle, size_t needlelen);
57
]])
1✔
58

59
local SET_VERIFY_FLAGS = {
1✔
60
    SSL_VERIFY_NONE = 0x00,
61
    SSL_VERIFY_PEER = 0x01,
62
    SSL_VERIFY_FAIL_IF_NO_PEER = 0x02,
63
}
64

65
local function slice_wait(timeout, starttime)
66
    if timeout == nil then
51✔
67
        return nil
9✔
68
    end
69

70
    return timeout - (clock.time() - starttime)
42✔
71
end
72

73
local X509_FILETYPE_PEM = 1
1✔
74

75
local function tls_server_method()
76
    return ffi.C.TLS_server_method()
19✔
77
end
78

79
local function ctx(method)
80
    ffi.C.ERR_clear_error()
20✔
81

82
    return ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free)
20✔
83
end
84

85
local function ctx_use_private_key_file(ctx, pem_file, password, password_file)
86
    ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL);
19✔
87

88
    if password ~= nil then
19✔
89
        log.info('set private key password')
2✔
90

91
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
4✔
92
            ffi.cast('void*', ffi.cast('char*', password)))
2✔
93
        local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
2✔
94
        ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
2✔
95

96
        if rc == 1 then
2✔
97
            return true
1✔
98
        end
99
        ffi.C.ERR_clear_error()
1✔
100
    end
101

102
    if password_file ~= nil then
18✔
103
        local fh = fio.open(password_file, {'O_RDONLY'})
2✔
104
        if fh == nil then
2✔
105
            return false
×
106
        end
107

108
        local is_loaded = false
2✔
109
        local password_from_file = ""
2✔
110
        local char
111
        while char ~= '' do
3✔
112
            while true do
113
                char = fh:read(1)
66✔
114
                if char == '\n' or char == '' then
33✔
115
                    break
116
                end
117
                password_from_file = password_from_file .. char
30✔
118
            end
119

120
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx,
6✔
121
                ffi.cast('void*', ffi.cast('char*', password_from_file)))
3✔
122
            local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
3✔
123
            ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL);
3✔
124

125
            if rc == 1 then
3✔
126
                is_loaded = true
2✔
127
                break
2✔
128
            end
129

130
            ffi.C.ERR_clear_error()
1✔
131
            password_from_file = ''
1✔
132
        end
133

134
        fh:close()
2✔
135

136
        return is_loaded
2✔
137
    end
138

139
    local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM)
16✔
140
    if rc ~= 1 then
16✔
141
        ffi.C.ERR_clear_error()
2✔
142
        return false
2✔
143
    end
144

145
    return true
14✔
146
end
147

148
local function ctx_use_certificate_file(ctx, pem_file)
149
    if ffi.C.SSL_CTX_use_certificate_file(ctx, pem_file, X509_FILETYPE_PEM) ~= 1 then
17✔
150
        ffi.C.ERR_clear_error()
×
151
        return false
×
152
    end
153
    return true
17✔
154
end
155

156
local function ctx_load_verify_locations(ctx, ca_file)
157
    if ffi.C.SSL_CTX_load_verify_locations(ctx, ca_file, box.NULL) ~= 1 then
13✔
158
        ffi.C.ERR_clear_error()
1✔
159
        return false
1✔
160
    end
161
    return true
12✔
162
end
163

164
local function ctx_set_cipher_list(ctx, str)
165
    if ffi.C.SSL_CTX_set_cipher_list(ctx, str) ~= 1 then
2✔
166
        return false
1✔
167
    end
168
    return true
1✔
169
end
170

171
local function ctx_set_verify(ctx, flags)
172
    ffi.C.SSL_CTX_set_verify(ctx, flags, box.NULL)
12✔
173
end
174

175
local default_ctx = ctx(ffi.C.TLS_server_method())
1✔
176

177
local SSL_ERROR_WANT_READ   = 2
1✔
178
local SSL_ERROR_WANT_WRITE  = 3
1✔
179
local SSL_ERROR_SYSCALL     = 5 -- Look at error stack/return value/errno.
1✔
180
local SSL_ERROR_ZERO_RETURN = 6
1✔
181

182
local sslsocket = {
1✔
183
}
184
sslsocket.__index = sslsocket
1✔
185

186
local WAIT_FOR_READ =1
1✔
187
local WAIT_FOR_WRITE =2
1✔
188

189
function sslsocket.write(self, data, timeout)
1✔
190
    local start = clock.time()
9✔
191

192
    local size = #data
9✔
193
    local s = ffi.cast('const char *', data)
9✔
194

195
    local mode = WAIT_FOR_WRITE
9✔
196

197
    while true do
198
        local rc = nil
9✔
199
        if mode == WAIT_FOR_READ then
9✔
200
            rc = self.sock:readable(slice_wait(timeout, start))
×
201
        elseif mode == WAIT_FOR_WRITE then
9✔
202
            rc = self.sock:writable(slice_wait(timeout, start))
27✔
203
        else
204
            assert(false)
×
205
        end
206

207
        if not rc then
9✔
208
            self.sock._errno = errno.ETIMEDOUT
×
209
            return nil, 'Timeout exceeded'
×
210
        end
211

212
        ffi.C.ERR_clear_error()
9✔
213
        local num = ffi.C.SSL_write(self.ssl, s, size);
9✔
214
        if num <= 0 then
9✔
215
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
×
216
            if ssl_error == SSL_ERROR_WANT_WRITE then
×
217
                mode = WAIT_FOR_WRITE
×
218
            elseif ssl_error == SSL_ERROR_WANT_READ then
×
219
                mode = WAIT_FOR_READ
×
220
            elseif ssl_error == SSL_ERROR_SYSCALL then
×
221
                return nil, self.sock:error()
×
222
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
×
223
                return 0
×
224
            else
225
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
×
226
                log.info(error_string)
×
227
                return nil, error_string
×
228
            end
229
        else
230
            return num
9✔
231
        end
232
    end
233
end
234

235
function sslsocket.close(self)
1✔
236
    return self.sock:close()
×
237
end
238

239
function sslsocket.error(self)
1✔
240
    local error_string =
241
        ffi.string(ffi.C.ERR_error_string(ffi.C.ERR_peek_last_error(), nil))
×
242

243
    return self.sock:error() or error_string
×
244
end
245

246
function sslsocket.errno(self)
1✔
247
    return self.sock:errno() or ffi.C.ERR_peek_last_error()
×
248
end
249

250
function sslsocket.fd(self)
1✔
251
    return self.sock:fd()
×
252
end
253

254
function sslsocket.nonblock(self, nb)
1✔
255
    return self.sock:nonblock(nb)
×
256
end
257

258
local function sysread(self, charptr, size, timeout)
259
    local start = clock.time()
15✔
260

261
    local mode = rawget(self, 'first_state') or WAIT_FOR_READ
15✔
262
    rawset(self, 'first_state', nil)
15✔
263

264
    while true do
265
        local rc = nil
27✔
266
        if mode == WAIT_FOR_READ then
27✔
267
            if ffi.C.SSL_pending(self.ssl) > 0 then
27✔
268
                rc = true
×
269
            else
270
                rc = self.sock:readable(slice_wait(timeout, start))
81✔
271
            end
272
        elseif mode == WAIT_FOR_WRITE then
×
273
            rc = self.sock:writable(slice_wait(timeout, start))
×
274
        else
275
            assert(false)
×
276
        end
277

278
        if not rc then
27✔
279
            self.sock._errno = errno.ETIMEDOUT
×
280
            return nil, 'Timeout exceeded'
×
281
        end
282

283
        ffi.C.ERR_clear_error()
27✔
284
        local num = ffi.C.SSL_read(self.ssl, charptr, size);
27✔
285
        if num <= 0 then
27✔
286
            local ssl_error = ffi.C.SSL_get_error(self.ssl, num);
18✔
287
            if ssl_error == SSL_ERROR_WANT_WRITE then
18✔
288
                mode = WAIT_FOR_WRITE
×
289
            elseif ssl_error == SSL_ERROR_WANT_READ then
18✔
290
                mode = WAIT_FOR_READ
12✔
291
            elseif ssl_error == SSL_ERROR_SYSCALL then
6✔
292
                return nil, self.sock:error()
×
293
            elseif ssl_error == SSL_ERROR_ZERO_RETURN then
6✔
294
                return 0
×
295
            else
296
                local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil))
6✔
297
                log.info(error_string)
6✔
298
                return nil, error_string
6✔
299
            end
300
        else
301
            return num
9✔
302
        end
303
    end
304
end
305

306
local function read(self, limit, timeout, check, ...)
307
    assert(limit >= 0)
15✔
308

309
    local start = clock.time()
15✔
310

311
    limit = math.min(limit, LIMIT_INFINITY)
15✔
312
    local rbuf = self.rbuf
15✔
313
    if rbuf == nil then
15✔
314
       rbuf = buffer.ibuf()
30✔
315
       rawset(self, 'rbuf', rbuf)
15✔
316
    end
317

318
    local len = check(self, limit, ...)
15✔
319
    if len ~= nil then
15✔
320
        local data = ffi.string(rbuf.rpos, len)
×
321
        rbuf.rpos = rbuf.rpos + len
×
322
        return data
×
323
    end
324

325
    while true do
326
        assert(rbuf:size() < limit)
30✔
327
        local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD)
30✔
328
        local data = rbuf:reserve(to_read)
15✔
329
        assert(rbuf:unused() >= to_read)
30✔
330

331
        local res, err = sysread(self, data, rbuf:unused(), slice_wait(timeout, start))
45✔
332
        if res == 0 then -- EOF.
15✔
333
            local len = rbuf:size()
×
334
            local data = ffi.string(rbuf.rpos, len)
×
335
            rbuf.rpos = rbuf.rpos + len
×
336
            return data
×
337
        elseif res ~= nil then
15✔
338
            rbuf.wpos = rbuf.wpos + res
9✔
339
            local len = check(self, limit, ...)
9✔
340
            if len ~= nil then
9✔
341
                local data = ffi.string(rbuf.rpos, len)
9✔
342
                rbuf.rpos = rbuf.rpos + len
9✔
343
                return data
9✔
344
            end
345
        else
346
            return nil, err
6✔
347
        end
348
    end
349

350
    -- Not reached.
351
end
352

353
local function check_limit(self, limit)
354
    if self.rbuf:size() >= limit then
×
355
        return limit
×
356
    end
357
    return nil
×
358
end
359

360
local function check_delimiter(self, limit, eols)
361
    if limit == 0 then
24✔
362
        return 0
×
363
    end
364
    local rbuf = self.rbuf
24✔
365
    if rbuf:size() == 0 then
48✔
366
        return nil
15✔
367
    end
368

369
    local shortest
370
    for _, eol in ipairs(eols) do
27✔
371
        local data = ffi.C.memmem(rbuf.rpos, rbuf:size(), eol, #eol)
36✔
372
        if data ~= nil then
18✔
373
            local len = ffi.cast('char *', data) - rbuf.rpos + #eol
9✔
374
            if shortest == nil or shortest > len then
9✔
375
                shortest = len
9✔
376
            end
377
        end
378
    end
379
    if shortest ~= nil and shortest <= limit then
9✔
380
        return shortest
9✔
381
    elseif limit <= rbuf:size() then
×
382
        return limit
×
383
    end
384
    return nil
×
385
end
386

387
function sslsocket.read(self, opts, timeout)
1✔
388
    timeout = timeout or TIMEOUT_INFINITY
15✔
389
    if type(opts) == 'number' then
15✔
390
        return read(self, opts, timeout, check_limit)
×
391
    elseif type(opts) == 'string' then
15✔
392
        return read(self, LIMIT_INFINITY, timeout, check_delimiter, { opts })
×
393
    elseif type(opts) == 'table' then
15✔
394
        local chunk = opts.chunk or opts.size or LIMIT_INFINITY
15✔
395
        local delimiter = opts.delimiter or opts.line
15✔
396
        if delimiter == nil then
15✔
397
            return read(self, chunk, timeout, check_limit)
×
398
        elseif type(delimiter) == 'string' then
15✔
399
            return read(self, chunk, timeout, check_delimiter, { delimiter })
×
400
        elseif type(delimiter) == 'table' then
15✔
401
            return read(self, chunk, timeout, check_delimiter, delimiter)
15✔
402
        end
403
    end
404
    error('Usage: s:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)')
×
405
end
406

407
function sslsocket.readable(self, timeout)
1✔
408
    return self.sock:readable(timeout)
×
409
end
410

411
local function wrap_accepted_socket(sock, sslctx)
412
    sslctx = sslctx or default_ctx
15✔
413

414
    ffi.C.ERR_clear_error()
15✔
415
    local ssl = ffi.gc(ffi.C.SSL_new(sslctx),
30✔
416
                       ffi.C.SSL_free)
15✔
417
    if ssl == nil then
15✔
418
        sock:close()
×
419
        return nil, 'SSL_new failed'
×
420
    end
421

422
    sock:nonblock(true)
15✔
423

424
    ffi.C.ERR_clear_error()
15✔
425
    local rc = ffi.C.SSL_set_fd(ssl, sock:fd())
30✔
426
    if rc == 0 then
15✔
427
        sock:close()
×
428
        return nil, 'SSL_set_fd failed'
×
429
    end
430

431
    ffi.C.ERR_clear_error()
15✔
432
    ffi.C.SSL_set_accept_state(ssl);
15✔
433

434
    local self = setmetatable({}, sslsocket)
15✔
435
    rawset(self, 'sock', sock)
15✔
436
    rawset(self, 'ctx', sslctx)
15✔
437
    rawset(self, 'ssl', ssl)
15✔
438
    return self
15✔
439
end
440

441
local function tcp_server(host, port, handler, timeout, sslctx)
442
    sslctx = sslctx or default_ctx
15✔
443

444
    local handler_function = handler.handler
15✔
445

446
    local wrapper = function(sock, from)
447
        local self, err = wrap_accepted_socket(sock, sslctx)
15✔
448
        if not self then
15✔
449
            log.info('sslsocket.tcp_server error: %s ', err)
×
450
        else
451
            handler_function(self, from)
15✔
452
        end
453
    end
454

455
    handler.handler = wrapper
15✔
456

457
    return socket.tcp_server(host, port, handler, timeout)
15✔
458
end
459

460
return {
1✔
461
    SET_VERIFY_FLAGS = SET_VERIFY_FLAGS,
1✔
462

463
    tls_server_method = tls_server_method,
1✔
464

465
    ctx = ctx,
1✔
466
    ctx_use_private_key_file = ctx_use_private_key_file,
1✔
467
    ctx_use_certificate_file = ctx_use_certificate_file,
1✔
468
    ctx_load_verify_locations = ctx_load_verify_locations,
1✔
469
    ctx_set_cipher_list = ctx_set_cipher_list,
1✔
470
    ctx_set_verify = ctx_set_verify,
1✔
471

472
    tcp_server = tcp_server,
1✔
473

474
    wrap_accepted_socket = wrap_accepted_socket,
1✔
475
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc