mirror of
https://github.com/vxcontrol/soldr-lua-extras.git
synced 2026-07-18 09:34:43 -04:00
318 lines
8.8 KiB
Lua
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
|