Files
2026-07-13 12:32:21 +08:00

267 lines
6.4 KiB
Lua

local ffi = require "ffi"
local C = ffi.C
local bit = require "bit"
local format_error = require("resty.openssl.err").format_error
require "resty.openssl.include.ssl"
ffi.cdef [[
typedef struct ssl_method_st SSL_METHOD;
const SSL_METHOD *TLS_method(void);
const SSL_METHOD *TLS_server_method(void);
SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
void SSL_CTX_free(SSL_CTX *ctx);
int SSL_CTX_use_certificate_chain_file(SSL_CTX *ctx, const char *file);
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
SSL *SSL_new(SSL_CTX *ctx);
void SSL_free(SSL *s);
long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg);
long SSL_set_mode(SSL *ssl, long mode);
int SSL_set_fd(SSL *ssl, int fd);
void SSL_set_accept_state(SSL *ssl);
int SSL_do_handshake(SSL *ssl);
int SSL_get_error(const SSL *ssl, int ret);
int SSL_read(SSL *ssl, void *buf, int num);
int SSL_write(SSL *ssl, const void *buf, int num);
int SSL_shutdown(SSL *ssl);
typedef struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
} pollfd;
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]
local SSL = {}
local ssl_mt = { __index = SSL }
local modes = {
SSL_MODE_ENABLE_PARTIAL_WRITE = 0x001,
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x002,
SSL_MODE_AUTO_RETRY = 0x004,
SSL_MODE_NO_AUTO_CHAIN = 0x008,
SSL_MODE_RELEASE_BUFFERS = 0x010,
SSL_MODE_SEND_CLIENTHELLO_TIME = 0x020,
SSL_MODE_SEND_SERVERHELLO_TIME = 0x040,
SSL_MODE_SEND_FALLBACK_SCSV = 0x080,
SSL_MODE_ASYNC = 0x100,
SSL_MODE_DTLS_SCTP_LABEL_LENGTH_BUG = 0x400,
}
local errors = {
SSL_ERROR_NONE = 0,
SSL_ERROR_SSL = 1,
SSL_ERROR_WANT_READ = 2,
SSL_ERROR_WANT_WRITE = 3,
SSL_ERROR_WANT_X509_LOOKUP = 4,
SSL_ERROR_SYSCALL = 5,
SSL_ERROR_ZERO_RETURN = 6,
SSL_ERROR_WANT_CONNECT = 7,
SSL_ERROR_WANT_ACCEPT = 8,
SSL_ERROR_WANT_ASYNC = 9,
SSL_ERROR_WANT_ASYNC_JOB = 10,
SSL_ERROR_WANT_CLIENT_HELLO_CB = 11,
SSL_ERROR_WANT_RETRY_VERIFY = 12,
}
local SOCKET_INVALID = -1
local SSL_FILETYPE_PEM = 1
local errors_literal = {}
for k, v in pairs(errors) do
errors_literal[v] = k
end
local function ssl_set_mode(ctx, mode)
return C.SSL_ctrl(ctx, 33, mode, nil)
end
local function ssl_ctx_new(cfg)
if cfg.protocol and cfg.protocol ~= "any" then
return nil, "protocol other than 'any' is currently not supported"
elseif cfg.mode and cfg.mode ~= "server" then
return nil, "mode other than 'server' is currently not supported"
end
cfg.protocol = nil
cfg.mode = nil
local ctx = C.SSL_CTX_new(C.TLS_server_method())
if ctx == nil then
return nil, format_error("SSL_CTX_new")
end
ffi.gc(ctx, C.SSL_CTX_free)
for k, v in pairs(cfg) do
if k == "certificate" then
if C.SSL_CTX_use_certificate_chain_file(ctx, v) ~= 1 then
return nil, format_error("SSL_CTX_use_certificate_chain_file")
end
elseif k == "key" then -- password protected key is NYI
if C.SSL_CTX_use_PrivateKey_file(ctx, v, SSL_FILETYPE_PEM) ~= 1 then
return nil, format_error("SSL_CTX_use_PrivateKey_file")
end
else
return nil, "unknown option \"" .. k .. "\""
end
end
return ctx
end
local function ssl_new(ssl_ctx)
if not ssl_ctx or not ffi.istype("SSL_CTX*", ssl_ctx) then
return nil, "ssl_new: expect SSL_CTX* as first argument"
end
local ctx = C.SSL_new(ssl_ctx)
if ctx == nil then
return nil, format_error("SSL_new")
end
ffi.gc(ctx, C.SSL_free)
C.SSL_set_fd(ctx, SOCKET_INVALID)
ssl_set_mode(ctx, bit.bor(modes.SSL_MODE_ENABLE_PARTIAL_WRITE,
modes.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER))
ssl_set_mode(ctx, modes.SSL_MODE_RELEASE_BUFFERS)
C.SSL_set_accept_state(ctx) -- me is server
return ctx
end
function SSL.wrap(sock, cfg)
local ctx, err
if type(cfg) == "table" then
ctx, err = ssl_ctx_new(cfg)
if not ctx then return nil, err end
else
ctx = cfg
end
local s, err = ssl_new(ctx)
if s then
local fd = sock:getfd()
C.SSL_set_fd(s, fd)
local self = setmetatable({
ssl_ctx = ctx,
ctx = s,
fd = fd,
sock = sock,
}, ssl_mt)
return self, nil
end
return nil, err
end
local function socket_waitfd(fd, events, timeout)
local pfd = ffi.new("pollfd")
pfd.fd = fd
pfd.events = events
pfd.revents = 0
local ppfd = ffi.new("pollfd[1]", pfd)
local wait = timeout and 1 or -1
while true do
local ret = C.poll(ppfd, 1, wait)
timeout = timeout and timeout - 1
if ret ~= -1 then
break
end
end
end
local POLLIN = 1
local POLLOUT = 2
local function handle_ssl_io(self, cb, ...)
local err, code
while true do
err = cb(self.ctx, ...)
code = C.SSL_get_error(self.ctx, err)
if code == errors.SSL_ERROR_NONE then
break
elseif code == errors.SSL_ERROR_WANT_READ then
err = socket_waitfd(self.fd, POLLIN, 10)
if err then return nil, "want read: " .. err end
elseif code == errors.SSL_ERROR_WANT_WRITE then
err = socket_waitfd(self.fd, POLLOUT, 10)
if err then return nil, "want write: " .. err end
elseif code == errors.SSL_ERROR_SYSCALL then
if err == 0 then
return nil, "closed"
end
if C.ERR_peek_error() then
return nil, format_error("SSL_ERROR_SYSCALL")
end
else
return nil, errors_literal[code] or "unknown error"
end
end
end
function SSL:dohandshake()
return handle_ssl_io(self, C.SSL_do_handshake)
end
function SSL:receive(pattern)
if pattern and pattern ~= "*l" then
return nil, "receive pattern other than '*l' is currently not supported"
end
local buf = ffi.new("char[1024]")
local ret = ""
while true do
local ok, err = handle_ssl_io(self, C.SSL_read, ffi.cast("void *", buf), 1024)
if err then
if err == "SSL_ERROR_ZERO_RETURN" then
err = "closed"
end
return ok, err
end
local current = ffi.string(buf)
-- do we need to find \r?
local pos = current:find("\n")
if pos then -- found a newline
ret = ret .. current:sub(1, pos-1)
break
else
ret = ret .. current
end
end
return ret
end
function SSL:send(s)
local buf = ffi.new("char[?]", #s+1, s)
local ok, err = handle_ssl_io(self, C.SSL_write, ffi.cast("void *", buf), #s)
if err then
return ok, err
end
return true
end
function SSL:close()
self.sock:close()
return true
end
return SSL