Created
April 12, 2014 00:13
-
-
Save katlogic/10511191 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------ | |
-- basic TLS/SSL3.1 implementation | |
-- $Id: ssl.lua,v 1.13 2006-12-02 21:25:58 kt Exp $ | |
------------------------------------ | |
require "clua" | |
require "util" | |
write_allow = true | |
local function DEBUG() | |
end | |
--local function prettyhex() | |
--end | |
local DEBUG = print | |
---------------------------------- | |
-- base64 decoder | |
---------------------------------- | |
local b64t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
local btab = {} | |
local mask = "([%a%d%+/\r\n=]?)" | |
local packs = { "6n", "6n6n", "6n6n6n", "6n6n6n6n"} | |
for idx=1, #b64t, 1 do btab[b64t:sub(idx,idx)] = idx - 1 end | |
local function decode_base64(str) | |
str = string.gsub(str, "[\r\n]", "") | |
local bufout = buf.alloc(#str) | |
for c1,c2,c3,c4 in string.gmatch(str , mask..mask..mask..mask) do | |
local v = { btab[c1], btab[c2], btab[c3], btab[c4] } | |
if not v[1] then break end | |
bufout:pack(packs[#v], unpack(v)) | |
end | |
return bufout | |
end | |
local function decode_pem(str, mark) | |
return string.sub(decode_base64(string.match(str, "%-%-%-%-%-BEGIN " .. mark .. "%-%-%-%-%-(.-)%-%-%-%-%-END " .. mark.."%-%-%-%-%-")), 1) | |
end | |
local function wouldblock() | |
local e = err.no | |
return e == "EWOULDBLOCK" or e == "EAGAIN" or e == "EINTR" | |
end | |
---------------------------------- | |
-- asn1 decoder | |
---------------------------------- | |
local asn1tab = { | |
[2] = "INT", | |
[3] = "BITSTR", | |
[4] = "BYTESTR", | |
[5] = "NULL", | |
[6] = "OID", | |
[0x13] = "PRINTABLE", | |
[0x30] = "SEQ", | |
[0x31] = "SET", | |
[0x80] = "ITAG", | |
[0xa0] = "ETAG" | |
} | |
-- return: obj, type, rest | |
local function asn1(str) | |
local typ, long, ln = buf.unpack(str, "8n1n7n") | |
local skip = 2 | |
-- print("ASN1 typ=",typ," ln=", ln) | |
typ = asn1tab[typ] | |
-- long form | |
if (long == 1) then | |
assert(ln <= 4) | |
skip = $ + ln | |
ln = buf.unpack(str, 2, ln*8 .. "n") | |
end | |
--print("skip=\$skip\ ln=\$ln\") | |
return string.sub(str, skip + 1, skip + ln), typ, string.sub(str, skip + 1 + ln) | |
end | |
------------------------------------------ | |
-- certificate decoder, returns public key | |
------------------------------------------ | |
function decode_cert(src) | |
local modulus, exponent,_ | |
local obj,typ,rest = asn1(asn1(asn1(src))) | |
if typ == "ETAG" then | |
_,typ,rest = asn1(rest) | |
--print(typ) | |
end | |
assert(typ == "INT") | |
obj,typ,rest = asn1(rest) | |
assert(typ == "SEQ") | |
obj,typ = asn1(obj) | |
-- SHA1/MD5 RSA signatures only | |
assert(obj:sub(1,8) == "\x2a\x86\x48\x86\xf7\x0d\x01\x01") | |
_,typ,rest = asn1(rest) -- issuer | |
_,typ,rest = asn1(rest) -- valid date | |
_,typ,rest = asn1(rest) -- subject | |
obj,typ,rest = asn1(rest) -- public key info in obj | |
_,typ,rest = asn1(obj) -- algorithm | |
obj,typ,rest = asn1(rest) -- public key | |
obj,typ = asn1(obj:sub(2)) -- rsa public key | |
modulus,typ,rest = asn1(obj) | |
assert(typ == "INT") | |
exponent,typ = asn1(rest) | |
assert(typ == "INT") | |
local rsa = crypto.rsa(modulus, exponent) | |
return rsa | |
end | |
------------------------------------------ | |
-- private key decoder, returns privkey | |
------------------------------------------ | |
function decode_key(src) | |
local modulus,p,q,dQ,dP,qInv | |
local obj,typ,rest = asn1(src) | |
local _ | |
assert(typ == "SEQ") | |
obj,typ,rest = asn1(obj) -- version? | |
modulus,typ,rest = asn1(rest) | |
_,typ,rest = asn1(rest) -- pub exp | |
_,typ,rest = asn1(rest) -- priv exp | |
p,typ,rest = asn1(rest) | |
q,typ,rest = asn1(rest) | |
dP,typ,rest = asn1(rest) | |
dQ,typ,rest = asn1(rest) | |
qInv,typ,rest = asn1(rest) | |
local rsa = crypto.rsa(modulus, nil, p, q, dP, dQ, qInv) | |
return rsa | |
end | |
function ssl_load_cert(path) | |
return decode_cert(decode_pem(io.open(pwd):read("*a"), "CERTIFICATE")) | |
end | |
function ssl_load_key(path) | |
return decode_key(decode_pem(io.open(pwd):read("*a"), "RSA PRIVATE KEY")) | |
end | |
function ssl_init_certkey(cert,key) | |
key = key or cert | |
local ctx = {} | |
ctx.cert = assert(decode_pem(io.open(cert):read("*a"), "CERTIFICATE")) | |
ctx.pubkey = assert(decode_cert(ctx.cert)) | |
ctx.privkey = assert(decode_key(decode_pem(io.open(key):read("*a"), "RSA PRIVATE KEY"))) | |
return ctx | |
end | |
------------------------------------------ | |
-- base ssl | |
------------------------------------------ | |
-- 20 - change cipher spec | |
-- 21 - alert proto | |
-- 22 - handshake protocol | |
-- 23 - app data | |
-- | |
-- 0 - hs_hello_request | |
-- 1 - client hello | |
-- 2 - server hello | |
-- 11 - certificate | |
-- 12 - server key xch | |
-- 13 - cert req | |
-- 14 - serv hello done | |
-- 15 - cert verify | |
-- 16 - client key xch | |
-- 20 - finished | |
-- prepare buffers | |
local function init_buf(ctx) | |
ctx.sendbuf = ctx.sendbuf or buf.alloc(16384) | |
ctx.recvbuf = ctx.recvbuf or buf.alloc(16384) | |
ctx.recvpos = ctx.recvpos or 0 | |
-- shift data, if any | |
-- if (ctx.recvbuf and ctx.recvpos and ctx.recvpos > 0) then | |
-- ctx.recvbuf:off(0) | |
-- ctx.recvbuf:append(ctx.recvbuf, ctx.recvpos) | |
-- ctx.recvpos = 0 | |
-- end | |
end | |
local function rc4_sha1_alg() | |
end | |
local cipher_suites = { | |
0x05, -- RC4-SHA1 | |
-- 0x04, -- RC4-MD5 | |
--[[ | |
0x2f, -- AES128-SHA1 | |
0x35 -- AES256-SHA1 ]] | |
} | |
local cipher_algs = { | |
[0x05] = rc4_sha1_alg, | |
} | |
--------------------------------------- | |
-- packet sending | |
--------------------------------------- | |
local function compute_hmac(ctx,src) | |
assert(ctx) | |
local writeseq = buf.pack(nil, "8B", unpack(ctx.seq)) | |
-- prettyhex("writeseq", writeseq) | |
local ourmac | |
if ctx.minor == 1 then | |
ourmac = ctx.maco: | |
update(ctx.writemaco): | |
final(ctx.maci: | |
update(ctx.writemaci): | |
update(writeseq): | |
final(src)) | |
prettyhex("computing hmac of:", writeseq..tostring(src)) | |
else | |
ourmac = ctx.maco: | |
update(ctx.writemaco): | |
final(ctx.maci: | |
update(ctx.writemaci): | |
update(writeseq): | |
update(string.sub(src,1,1)): | |
update(src, 3):final()) | |
prettyhex("computing hmac of:", writeseq..string.sub(src,1,1)..string.sub(src,4)) | |
end | |
prettyhex("result mac: ", ourmac) | |
--prettyhex("ourmac", ourmac) | |
for i=8, 1, -1 do | |
if (ctx.seq[i] < 255) then | |
ctx.seq[i] = $+1 | |
break | |
end | |
ctx.seq[i] = 0 | |
end | |
return ourmac | |
end | |
local function send_raw_packet(ctx,proto) | |
local sbuf = ctx.sendbuf | |
local wsz = sbuf:off(0) | |
DEBUG("wsz is ", wsz) | |
if (wsz <= 5) then sbuf:off(wsz) return end | |
sbuf:pack("BBBH", proto, 3, ctx.minor or 1, wsz - 5) | |
local pos = 0 | |
sbuf:off(wsz) | |
-- encrypt the packet | |
if (ctx.writec) then | |
DEBUG("SBUF", sbuf:off()) | |
-- hmac is computed on plaintext | |
sbuf:append(compute_hmac(ctx, sbuf)) | |
-- encryption in-place | |
ctx.writec:crypt(sbuf, sbuf, 5) | |
-- patch in the new size (this is so braindamaged!!!) | |
wsz = sbuf:off(3) | |
sbuf:pack("H", wsz - 5) | |
sbuf:off(wsz) | |
end | |
--prettyhex("send_raw", sbuf) | |
local _, got = ctx.s:send(sbuf, pos) | |
assert(not got) | |
--[[ | |
while pos < wsz do | |
local sent = s.s:send(sbuf, pos) | |
if (not sent and wouldblock()) then | |
s.s:writewait() | |
continue | |
else | |
assert(sent and sent > 0) | |
end | |
pos = $ + sent | |
end]] | |
sbuf:off(5) | |
-- buf:off was set to 5 by buf:pack | |
end | |
--log = io.open("hslog", "w+") | |
local function hs_log(b, off, len) | |
local qq = string.sub(b, off + 1, len and len + off) | |
log:write(qq) | |
log:flush() | |
end | |
local function update_hash(ctx,b, ptr, len) | |
assert(ctx) | |
-- hs_log(b, ptr, len) | |
ctx.hs1:update(b, ptr, len) | |
ctx.hs2:update(b, ptr, len) | |
end | |
local function send_hs_packet(ctx) | |
update_hash(ctx, ctx.sendbuf,5) | |
return send_raw_packet(ctx, 22) | |
end | |
--------------------------------------- | |
-- packet receiving | |
--------------------------------------- | |
local function init_hash(ctx) | |
ctx.hs1 = crypto.md5() | |
ctx.hs2 = crypto.sha1() | |
end | |
local function check_ver(ctx,major,minor) | |
if not ctx.minor then | |
assert(minor == 0 or minor == 1) | |
ctx.minor = minor | |
end | |
assert(major == 3 and ctx.minor == minor) | |
end | |
local recv_raw_packet | |
local function raw_recv_packet(ctx,proto) | |
local rbuf = ctx.recvbuf | |
DEBUG("inside raw_recv_packet") | |
assert(proto) | |
-- we expect "upper protocol" | |
rbuf:off(0) | |
local _, left= ctx.s:recv(rbuf, 5) | |
DEBUG(_,left,rbuf:off()) | |
assert(not left) | |
-- unpack the header | |
--print(#hdr) | |
local hproto, hmajor, hminor, hlen = buf.unpack(rbuf, "BBBH") | |
-- make sure the protocol is the requested one | |
--print(hproto, hmajor, hminor) | |
prettyhex("SSL proto header", rbuf) | |
-- v23 client hello hack | |
if (hproto >= 128 and proto == 22) then | |
local tmp, cmd,major,minor | |
tmp,hlen,cmd,major,minor = buf.unpack(rbuf, "1n15nBBB") | |
check_ver(ctx,major,minor) | |
assert(cmd == 1) | |
hlen=$-3 | |
--print(hlen) | |
update_hash(ctx, rbuf, 2, 3) | |
else | |
DEBUG(hproto, hmajor, hminor) | |
check_ver(ctx, hmajor, hminor) | |
end | |
-- must be at least 4 bytes | |
-- print("HLEN",hlen) | |
--assert(hlen >= 4) | |
rbuf:off(0) | |
-- read the whole packet | |
ctx.proto = hproto | |
ctx.recpos = 0 | |
local _, left = ctx.s:recv(rbuf, hlen) | |
assert(not left) | |
-- and decrypt it | |
if ctx.readc then | |
-- print(ctx.recpos, rbuf:off()) | |
prettyhex("encrypted packet---", rbuf) | |
ctx.readc:crypt(rbuf, rbuf) | |
------------ print(ctx.recpos, rbuf:off()) | |
prettyhex("decrypted packet---", rbuf) | |
assert(rbuf:off() >= 20) | |
if (hproto == 22 and proto == 23) then | |
return recv_raw_packet(ctx,proto) | |
end | |
assert(hproto == proto and hlen <= 16384) | |
return rbuf:off(rbuf:off()-20)-20 | |
end | |
return rbuf:off() | |
end | |
recv_raw_packet = raw_recv_packet | |
-- get one record _header_, use recv_record_data to get the actual data | |
local function recv_hs_header(ctx) | |
-- there must not be record data left | |
assert(ctx.tbr == 0) | |
-- print(ctx.recpos, s.recvbuf:off()) | |
-- we've nothing left inside the packet buffer, read a new one | |
if (ctx.recpos >= ctx.recvbuf:off()) then | |
local l = raw_recv_packet(ctx, 22) | |
-- continuation of v23 hack | |
if (ctx.proto >= 128) then | |
ctx.recpos = l | |
-- print("HASHING ", l) | |
update_hash(ctx, ctx.recvbuf, 0, l) | |
return 128, l | |
end | |
end | |
local code, len = ctx.recvbuf:unpack(ctx.recpos, "B24n") | |
assert(code and len) | |
ctx.tbr = len | |
update_hash(ctx, ctx.recvbuf, ctx.recpos, 4) | |
ctx.recpos = $+4 | |
return code, len | |
end | |
-- return as much record data as possible in this iteration, actually, | |
-- offset to recvbuf and length is returned or nil on end of record | |
local function recv_hs_data(ctx, reqlen) | |
if (ctx.tbr <= 0) then return nil end | |
-- we've reached end of packet, read new one | |
if (ctx.recpos >= ctx.recvbuf:off()) then | |
raw_recv_packet(ctx, ctx.proto) | |
end | |
local pend = ctx.recvbuf:off() | |
local pstart = ctx.recpos | |
local plen = pend - pstart | |
assert(plen > 0) | |
if (reqlen) then | |
--print(reqlen, plen) | |
assert(reqlen <= plen) | |
plen = reqlen | |
-- packet is longer than record | |
elseif (plen > ctx.tbr) then | |
local tb = ctx.tbr | |
ctx.recpos = $ + tb | |
ctx.tbr = 0 | |
update_hash(ctx, ctx.recvbuf, pstart, tb) | |
return pstart, tb | |
end | |
-- smaller or same | |
ctx.tbr = $-plen | |
ctx.recpos = $+plen | |
update_hash(ctx, ctx.recvbuf, pstart, plen) | |
return pstart, plen | |
end | |
-- return code, start, len | |
local function recv_hs(ctx) | |
local code, l = recv_hs_header(ctx) | |
if (code == 128) then | |
return code, 0, l | |
end | |
local ptr, len = recv_hs_data(ctx) | |
return code, ptr, len | |
end | |
------------------------------------- | |
-- HMAC functions | |
------------------------------------- | |
-- md5/sha1 prf part | |
local function pmac(func, hmaci, hmaco, seed, need) | |
local res = {} | |
local pos = need | |
local aX = seed | |
local s1 = func() | |
local s2 = func() | |
while pos > 0 do | |
aX = s1:update(hmaco):final(s2:update(hmaci):final(aX)) | |
local tm = s1:update(hmaco):final(s2:update(hmaci):update(aX):final(seed)) | |
table.insert(res, tm) | |
pos = pos - #tm | |
end | |
return table.concat(res) | |
end | |
-- tls prf | |
local function prf(secret, seed, len) | |
local S1 = secret:sub(1, (#secret + 1) >> 1) | |
local S2 = secret:sub((#secret >> 1) + 1) | |
local hmaci1, hmaco1 = crypto.hmac(S1) | |
local hmaci2, hmaco2 = crypto.hmac(S2) | |
local pmd5 = pmac(crypto.md5, hmaci1, hmaco1, seed, len) | |
local psha1 = pmac(crypto.sha1, hmaci2, hmaco2, seed, len) | |
local res = {} | |
for i=1, len, 1 do | |
table.insert(res, psha1:byte(i) ^^ pmd5:byte(i)) | |
end | |
return string.char(unpack(res)) | |
end | |
local pad_1_sha = string.rep("\x36", 40) | |
local pad_1_md5 = string.rep("\x36", 48) | |
local pad_2_sha = string.rep("\x5c", 40) | |
local pad_2_md5 = string.rep("\x5c", 48) | |
local function prf3(secret,rand,len) | |
local out = "" | |
local iter = 1 | |
while #out < len do | |
DEBUG("iter=",iter," #out=", #out, "salt=", string.rep(string.char(64+iter), iter)) | |
out = out..crypto.md5(secret..crypto.sha1(string.rep(string.char(64+iter), iter)..secret..rand)) | |
iter=iter+1 | |
end | |
return string.sub(out, 1, len) | |
end | |
local function ssl3_finished_digest(ctx,secret,sender) | |
return crypto.md5(secret .. pad_2_md5 .. ctx.hs1:final(sender .. secret .. pad_1_md5)) .. | |
crypto.sha1(secret .. pad_2_sha .. ctx.hs2:final(sender .. secret .. pad_1_sha)) | |
end | |
local function ssl_init(s,ctx) | |
ctx.tbr = ctx.tbr or 0 | |
ctx.recpos = ctx.recpos or 0 | |
ctx.seq = ctx.seq or { 0,0,0,0,0,0,0,0} | |
ctx.maxpad = 20; | |
ctx.s = s | |
init_hash(ctx) | |
init_buf(ctx) | |
end | |
local function compute_master(ctx,premaster,macoff,clirand,servrand) | |
local master, keyblock | |
if (ctx.minor==1) then | |
master = prf(premaster, "master secret" .. clirand .. servrand, 48) | |
keyblock = prf(master, "key expansion" .. servrand .. clirand, 72) | |
ctx.writemaci, ctx.writemaco = crypto.hmac(keyblock:sub(macoff+1,macoff+20)) -- 21 - 40 is server's mac, we dont care | |
else | |
master = prf3(premaster, clirand..servrand, 48) | |
keyblock = prf3(master, servrand..clirand, 72) | |
prettyhex("keyblock", keyblock) | |
ctx.writemaci, ctx.writemaco = keyblock:sub(macoff+1,macoff+20) .. pad_1_sha, keyblock:sub(macoff+1,macoff+20) .. pad_2_sha | |
end | |
return master, keyblock | |
end | |
local function compute_final_digest(ctx, master, seed31, seed30) | |
local sbuf = ctx.sendbuf | |
ctx.maci = crypto.sha1() | |
ctx.maco = crypto.sha1() | |
if ctx.minor==1 then | |
local finaldigest = prf(master, seed31 .. ctx.hs1:final() .. ctx.hs2:final(), 12) | |
sbuf:pack("B24ns", 20, 12, finaldigest) | |
else | |
local finaldigest = ssl3_finished_digest(ctx, master, seed30) | |
sbuf:pack("B24ns", 20, #finaldigest, finaldigest) | |
end | |
--print("final digest", phex(finaldigest)) | |
end | |
function ssl_server(s, ctx, sctx) | |
ssl_init(s, ctx) | |
local sbuf = ctx.sendbuf | |
local rbuf = ctx.recvbuf | |
----------------------- | |
-- client hello | |
----------------------- | |
local code, ptr, len = recv_hs(ctx) | |
local ciphid, clirand | |
-- client v23 hello hack | |
if (code == 128) then | |
local istls | |
local cslen, sidlen, chlen = rbuf:unpack("HHH") | |
DEBUG("SIDLEN",sidlen,chlen) | |
assert(sidlen == 0) | |
for i=0,cslen-1,3 do | |
istls, ciphid = rbuf:unpack(3 + i, "BH") | |
if (istls == 0 and cipher_algs[ciphid]) then | |
break | |
end | |
ciphid = nil | |
end | |
clirand = rbuf:unpack(6 + cslen, chlen.."S") | |
clirand = string.rep("\000", 32 - chlen) .. clirand | |
prettyhex("clirand", clirand) | |
assert(#clirand == 32) | |
else | |
-- standard TLS hello | |
assert(code == 1) | |
local major, minor, nciph | |
major, minor, clirand, nciph = rbuf:unpack(ptr, "BB32SxH") | |
assert(major == 3, minor == 1) | |
ptr=$+37 | |
for i=0,nciph-1,2 do | |
ciphid = rbuf:unpack(ptr + i, "H") | |
if (cipher_algs[ciphid]) then | |
break | |
end | |
ciphid = nil | |
end | |
end | |
assert(ciphid) | |
-- we dont care for compressions, 0 is always supported | |
-------------------- | |
-- server hello | |
------------------ | |
sbuf:off(5) | |
local servrand = crypto.random(32) | |
sbuf:pack("B24nBBsBsHx", 2, 70, 3, ctx.minor, servrand, 32, crypto.random(32), ciphid) | |
send_hs_packet(ctx) | |
--------------------- | |
-- server certificate | |
--------------------- | |
sbuf:pack("B24n24n24ns", 11, #sctx.cert + 6, #sctx.cert + 3, #sctx.cert, sctx.cert) | |
send_hs_packet(ctx) | |
--------------------- | |
-- server hello done | |
--------------------- | |
sbuf:pack("B24n", 14, 0) | |
send_hs_packet(ctx) | |
---------------------- | |
-- client key exchange | |
---------------------- | |
code, ptr, len = recv_hs(ctx) | |
-- print(code) | |
assert(code == 16) | |
local enclen = (ctx.minor==1 and rbuf:unpack(ptr, "H")) or len | |
local encpre = rbuf:unpack((ctx.minor==1) and ptr+2 or ptr, enclen.."S") | |
DEBUG("PREM", enclen,#encpre) | |
local premaster = sctx.privkey:decrypt(encpre) | |
prettyhex("decrypted premaster", premaster) | |
local master, keyblock = compute_master(ctx, premaster, 20, clirand, servrand) | |
local rc = crypto.rc4(keyblock:sub(41, 56)) | |
local wc = crypto.rc4(keyblock:sub(57,72)) | |
---------------------- | |
-- client change cipher spec | |
---------------------- | |
raw_recv_packet(ctx,20) | |
code = rbuf:unpack(ctx.recpos, "B") | |
assert(code == 1) | |
rbuf:off(0) --to make recv_hs happy | |
ctx.readc = rc | |
-------------------------- | |
-- client finished message | |
-------------------------- | |
code, ptr, len = recv_hs(ctx) | |
prettyhex("CLIENT FINISHED",rbuf) | |
DEBUG(code) | |
assert(code == 20) | |
--------------------------- | |
-- server changecipher spec | |
--------------------------- | |
sbuf:pack("B", 1) | |
send_raw_packet(ctx, 20) | |
ctx.writec = wc | |
------------------------------------------ | |
-- server finished. we actually don't care | |
------------------------------------------ | |
compute_final_digest(ctx, master, "server finished", "SRVR") | |
send_raw_packet(ctx, 22) | |
ctx.recpos = 0; | |
rbuf:off(0) | |
sbuf:off(5) | |
end | |
------------------------------------- | |
-- SSL client | |
------------------------------------- | |
-- start ssl client on the connection | |
function ssl_client(s,ctx) | |
ssl_init(s, ctx) | |
local sbuf = ctx.sendbuf | |
local rbuf = ctx.recvbuf | |
assert(sbuf) | |
assert(rbuf) | |
-------------- | |
-- helo packet | |
-------------- | |
local clirand = buf.pack(nil, "Is", | |
event.now/1000, | |
crypto.random(28)) | |
prettyhex("clirand", clirand) | |
sbuf:off(5) | |
sbuf:pack("B24nBB32SxH", | |
1, | |
-- size of message: 1+1+32+1+2+#cipher_suites*2+2 => major,minor,random,nosessid,ncipher,<ciphers>,nocompr | |
39 + #cipher_suites * 2, | |
3, 1, clirand, #cipher_suites * 2) | |
-- ciphers we can agree on | |
for i=1,#cipher_suites,1 do | |
sbuf:pack("H", cipher_suites[i]) | |
end | |
-- null compression | |
sbuf:pack("Bx", 1) | |
send_hs_packet(ctx) | |
--------------------- | |
-- server helo packet | |
--------------------- | |
local code, ptr, len = recv_hs(ctx) | |
DEBUG("PTR", ptr) | |
local major, minor, servrand, sidlen = rbuf:unpack(ptr, "BB32SB") | |
assert(code == 2 and major == 3 and minor == ctx.minor and servrand and sidlen) | |
-- major, minor, rand, sidlen | |
local cipher, comp = rbuf:unpack(ptr + 2+32+1 + sidlen, "HB") | |
assert(comp == 0 and cipher_algs[cipher]) | |
--------------------- | |
-- server certificate | |
--------------------- | |
code, len = recv_hs_header(ctx) | |
assert(code == 11) | |
ptr = recv_hs_data(ctx, 6) | |
-- get first cert size | |
local allcertlen, certlen = rbuf:unpack(ptr, "24n24n") | |
-- print("ALL", allcertlen, certlen) | |
ptr = recv_hs_data(ctx, certlen) | |
-- decode first cert | |
local serverpub = decode_cert(string.sub(rbuf, ptr + 1, ptr + certlen)) | |
allcertlen = $-certlen-3 | |
while allcertlen > 0 do | |
ptr = recv_hs_data(ctx, 3) | |
certlen = rbuf:unpack(ptr, "24n") | |
recv_hs_data(ctx, certlen) | |
allcertlen = $-certlen-3 | |
end | |
assert(allcertlen == 0) | |
--------------------- | |
-- server hello done | |
--------------------- | |
local wantcert | |
code, ptr, len = recv_hs(ctx) | |
if (code == 13) then | |
wantcert = true | |
code, ptr, len = recv_hs(ctx) | |
end | |
assert(code == 14 and not len) | |
--------------------- | |
-- client certificate | |
--------------------- | |
if wantcert then | |
-- we have nothign to offer yet | |
-- allcertlen = 0 => no certificates | |
sbuf:pack("B24n24n", 11, 3, 0) | |
send_hs_packet(ctx) | |
end | |
---------------------- | |
-- client key exchange | |
---------------------- | |
local premaster = buf.pack(nil, "BBs", 3, 1, crypto.random(46)) | |
assert(#premaster == 48) | |
local encpre = serverpub:encrypt(premaster) | |
if ctx.minor == 1 then | |
sbuf:pack("B24nHs", 16, #encpre + 2, #encpre, encpre) | |
else | |
sbuf:pack("B24ns", 16, #encpre , encpre) | |
end | |
send_hs_packet(ctx) | |
local master, keyblock = compute_master(ctx, premaster, 0, clirand, servrand) | |
local wc = crypto.rc4(keyblock:sub(41, 56)) | |
local rc = crypto.rc4(keyblock:sub(57,72)) | |
---------------------- | |
-- changecipher spec | |
---------------------- | |
sbuf:pack("B", 1) | |
send_raw_packet(ctx, 20) | |
ctx.writec = wc | |
-------------------------- | |
-- client finished message | |
-------------------------- | |
compute_final_digest(ctx, master, "client finished", "CLNT") | |
send_raw_packet(ctx, 22) | |
---------------------- | |
-- server's cipherspec | |
---------------------- | |
-- code, len = recv_record(s, 20 | |
raw_recv_packet(ctx,20) | |
code = rbuf:unpack(ctx.recpos, "B") | |
assert(code == 1) | |
-- print("all done") | |
rbuf:off(0) --to make recv_hs happy | |
ctx.readc = rc | |
------------------------------------------ | |
-- server finished. we actually don't care | |
------------------------------------------ | |
code, ptr, len = recv_hs(ctx) | |
assert(code == 20) | |
ctx.recpos = 0; | |
rbuf:off(0) | |
sbuf:off(5) | |
end | |
------------------------------------- | |
------------------------------------- | |
-- reading/writing | |
------------------------------------- | |
------------------------------------- | |
function ssl_flush(ctx) | |
send_raw_packet(ctx, 23) | |
end | |
function ssl_write(ctx,data) | |
local maxpad = ctx.maxpad | |
local total = data.off and data:off() or #data | |
local left = ctx.sendbuf:left()-maxpad | |
local pos = 0 | |
local did | |
while pos < total do | |
DEBUG("LEFT=",left,ctx.sendbuf:off()) | |
if left < 0 then | |
ssl_flush(ctx) | |
left = ctx.sendbuf:left()-maxpad | |
end | |
if (left < total-pos) then | |
did = ctx.sendbuf:append(data, pos, left) | |
else | |
did = ctx.sendbuf:append(data, pos, total-pos) | |
end | |
DEBUG("sbuf.off=", ctx.sendbuf:off()) | |
pos = $+did | |
left = $-did | |
end | |
end | |
function ssl_read(ctx,len) | |
local rbuf = ctx.recvbuf | |
local off = rbuf:off() | |
local pos = 0 | |
if (ctx.recpos == off) then | |
off = recv_raw_packet(ctx, 23) | |
--print("OFF", off) | |
assert(ctx.recpos < off) | |
end | |
--print("OFF", off, " recpos", ctx.recpos) | |
-- return whatever is left (remainder) | |
len = len or (off - ctx.recpos) | |
--print("nlen ", len) | |
-- simple case | |
if len + ctx.recpos <= off then | |
local r = string.sub(rbuf, ctx.recpos + 1, ctx.recpos + len) | |
ctx.recpos = $+len | |
return r | |
end | |
-- multipart read (slow) | |
local t = string.sub(rbuf, ctx.recpos + 1) | |
local ret = { t } | |
len = $ - #t | |
while len > 0 do | |
recv_raw_packet(ctx, 23) | |
t = string.sub(rbuf, len) | |
table.insert(ret, t) | |
len = len - #t | |
end | |
ctx.recpos = #t | |
return table.concat(ret) | |
end | |
function ssl_recvline(ctx) | |
local rbuf = ctx.recvbuf | |
local off = rbuf:off() | |
local saved = "" | |
while true do | |
DEBUG("still here?") | |
if ctx.recpos < rbuf:off() then | |
local ln, cr = string.match(rbuf, "(.-)(\r?)\n", ctx.recpos + 1) | |
if ln then | |
DEBUG("CTX", ctx.recpos, rbuf:off()) | |
ctx.recpos = $ + #ln + #cr + 1 | |
DEBUG("SSL_RECVLINE: '\$ln\'") | |
return saved .. ln | |
end | |
if ctx.recpos < rbuf:off() then | |
saved = saved .. string.sub(rbuf, ctx.recpos + 1) | |
end | |
end | |
DEBUG("waiting for packet") | |
off = recv_raw_packet(ctx, 23) | |
DEBUG("received packet") | |
assert(ctx.recpos < off) | |
end | |
end | |
write_allow = false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment