Files
soldr-lua-extras/ssl.lua
T
Mikhail Kochegarov a9ba5add45 initial commit
2022-12-21 15:47:24 +10:00

318 lines
8.8 KiB
Lua

local openssl = require'openssl'
local socket = require'socket'
local ssl,pkey,x509,version = openssl.ssl,openssl.pkey,openssl.x509,openssl.version
local M = {}
local function load(path)
local f = assert(io.open(path,'r'))
if f then
local c = f:read('*all')
f:close()
return c
end
end
function M.newcontext(params)
local protocol = params.protocol and string.upper(string.sub(params.protocol,1,3))
..string.sub(params.protocol,4,-1) or 'TLSv1_2'
local ctx = ssl.ctx_new(protocol,params.ciphers)
local xkey = nil
local keydata = params.keydata or params.key and load(params.key)
local certdata = params.certdata or params.certificate and load(params.certificate)
if keydata then
if (type(params.password)=='nil') then
xkey = assert(pkey.read(keydata,true,'pem'))
elseif (type(params.password)=='string') then
xkey = assert(pkey.read(keydata,true,'pem',params.password))
elseif (type(params.password)=='function') then
local p = assert(params.password())
xkey = assert(pkey.read(keydata,true,'pem',p))
end
assert(xkey)
local xcert = nil
if certdata then
xcert = assert(x509.read(certdata))
end
assert(ctx:use(xkey,xcert))
end
if params.cafile or params.capath then
ctx:verify_locations(params.cafile,params.capath)
end
if params.cadata then
local certstore = nil
local _, _, opensslv = version(true)
if opensslv > 0x10002000 then
certstore = assert(x509.store:new())
local cert = assert(x509.read(params.cadata))
assert(certstore:add(cert))
end
ctx:cert_store(certstore)
end
unpack = unpack or table.unpack
if params.verify then
if type(params.verify) ~= "table" then
params.verify = {params.verify}
end
local luasec_flags = {
["none"] = "none",
["peer"] = "peer",
["client_once"] = "once",
["fail_if_no_peer_cert"] = "fail"
}
local verify = 0
for i,v in ipairs(params.verify) do
verify = verify + (ssl[luasec_flags[v] or v] or v)
end
ctx:verify_mode(verify)
end
if params.options then
if type(params.options) ~= "table" then
params.options = {params.options}
end
ctx:options(unpack(params.options))
end
if params.verifyext then
for k,v in pairs(params.verifyext) do
params.verifyext[k] = string.gsub(v,'lsec_','')
end
ctx:set_cert_verify(params.verifyext)
end
if params.dhparam then
ctx:set_tmp('dh',params.dhparam)
end
if params.curve then
ctx:set_tmp('ecdh',params.curve)
end
local t = {}
t.ctx = ctx
t.mode = params.mode
t.params = params
return t
end
----------------------------------------------------------
local S = {}
S.__index = {
dohandshake = function(self)
local ret,msg
socket.select({self.ssl}, {self.ssl}, self.timeout)
ret,msg = self.ssl:handshake()
while not ret do
if (msg=='want_read' or msg=='want_write') then
ret,msg = self.ssl:handshake()
else
return ret,msg
end
end
if ret then
self._bbf = assert(openssl.bio.filter('buffer'))
self._sbf = assert(openssl.bio.filter('ssl',self.ssl,'noclose'))
self.bio = assert(self._bbf:push(self._sbf))
else
msg = msg and string.gsub(msg,'_','') or msg
end
return ret,msg
end,
getpeercertificate = function(self)
self.peer,self.peerchain = self.ssl:peer()
return self.peer
end,
getpeerverification = function(self)
local r, t = self.ssl:getpeerverification()
if not r then
local tt = {}
for i,err in pairs(t) do
tt[i] = {}
tt[i][1] = string.format('error=%d string=%s depth=%s',err.error,err.error_string,err.error_depth)
end
return r,tt
end
return r
end,
getfd = function(self)
local fd = self.ssl:getfd()
return fd
end,
getpeerchain = function(self)
self.peer,self.peerchain = self.ssl:peer()
local chains = {}
--[[
print(self.peerchain,#self.peerchain)
for i=1,#self.peerchain do
table.insert(chains,self.peerchain:get(i-1))
end
--]]
if (self.peerchain) then
chains = self.peerchain:totable()
end
return chains
end,
close = function(self)
if self.ssl then
self.ssl:shutdown()
self.ssl = nil
end
end,
send = function(self,msg,i,j)
local m = msg
if i then
j = j or -1
m = string.sub(msg,i,j)
end
return self.bio:write(m) and self.bio:flush()
end,
receive = function(self,fmt,prev,force)
if type(fmt) == 'number' then
local buff = prev and {prev} or {''}
local buffsize = string.len(buff[1])
local s = nil
local len = fmt
while buffsize < len do
if not force then
local r, m = socket.select({self.ssl}, nil, self.timeout)
if #r == 0 then
return nil, 'timeout', buff
end
end
s = self.bio:read(len - buffsize)
if s == nil then
return nil, 'closed', table.concat(buff)
elseif type(s) == "string" and s ~= '' then
table.insert(buff, s)
buffsize = buffsize + string.len(s)
end
end
buff = table.concat(buff)
if buffsize > len then
s = string.sub(buff, len + 1, -1)
buff = string.sub(buff, 1, len)
end
return buff
end
fmt = fmt and string.sub(fmt, 1, 2) or '*l'
if (fmt == '*l') then
local s = nil
local buff = prev or ''
local _, _, p1, p2 = string.find(buff, '(.-)\r\n(.*)')
while not p1 do
local r, m = socket.select({self.ssl}, nil, self.timeout)
if #r == 0 then
return nil, 'timeout', buff
end
s = self.bio:gets(1024)
if s == nil then
return nil, 'closed', buff
elseif type(s) == "string" and s ~= '' then
buff = buff .. s
end
_, _, p1, p2 = string.find(buff, '(.-)\r\n(.*)')
end
return p1
end
end,
sni = function(self,arg)
if type(arg) =='string' then
self.ssl:set('hostname',arg)
elseif type(arg)=='table' then
local t = {}
for k,v in pairs(arg) do
t[k] = v.ctx
end
self.ssl:ctx():set_servername_callback(t)
end
end,
settimeout = function(self,n,b)
self.timeout = n
end,
info = function(self,field)
--[[
algbits
authentication
bits
cipher
compression
encryption
export
key
mac
protocol
--]]
local cc = self.ssl:current_cipher()
if cc then
local info = {
bits = cc.bits,
algbits = cc.algbits,
protocol = cc.protocol
}
if cc.description then
info.cipher, info.protocol, info.key,
info.authentication, info.encryption, info.mac =
string.match(cc.description,
"^(%S+)%s+(%S+)%s+Kx=(%S+)%s+Au=(%S+)%s+Enc=(%S+)%s+Mac=(%S+)")
info.export = (string.match(cc.description, "%sexport%s*$") ~= nil)
end
self.compression = self.ssl:current_compression()
if field then
return info[field]
end
return info
end
end
}
function M.wrap(sock, cfg)
local ctx, msg
if type(cfg) == "table" and not cfg.ctx then
ctx, msg = M.newcontext(cfg)
if not ctx then return nil, msg end
else
ctx = cfg
end
local s, msg = ctx.ctx:ssl(sock:getfd())
if s then
if(ctx.mode=='server') then
s:set_accept_state()
else
s:set_connect_state()
end
local t = {}
t.ssl = s
t.socket = sock
t.timeout = type(cfg)=='table' and cfg.timeout or nil
setmetatable(t,S)
return t
end
return nil, msg
end
function M.loadcertificate(pem)
return openssl.x509.read(pem,'pem')
end
return M