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