267 lines
6.4 KiB
Lua
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
|