dnschain
Version:
A blockchain-based DNS + HTTPS server that fixes HTTPS security, and more!
141 lines (118 loc) • 5.36 kB
text/coffeescript
###
dnschain
http://dnschain.org
Copyright (c) 2014 okTurtles Foundation
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
###
module.exports = (dnschain) ->
# expose these into our namespace
for k of dnschain.globals
eval "var #{k} = dnschain.globals.#{k};"
# http://tools.ietf.org/html/rfc5246#section-7.4.1
# http://stackoverflow.com/questions/17832592/extract-server-name-indication-sni-from-tls-client-hello
class HTTPSUtils
categories: {
SNI : 0
NO_SNI : 1
NOT_HTTPS : 2
INCOMPLETE : 3
}
parseHTTPS: (packet) ->
res = {}
try
res.contentType = packet.readUInt8 0
if res.contentType != 22 then return [.NOT_HTTPS, {}]
res.recordVersionMajor = packet.readUInt8 1
if res.recordVersionMajor >= 7 then return [.NOT_HTTPS, {}]
res.recordVersionMinor = packet.readUInt8 2
if res.recordVersionMinor >= 7 then return [.NOT_HTTPS, {}]
res.recordLength = packet.readUInt16BE 3
res.handshakeType = packet.readUInt8 5
if res.handshakeType != 1 then return [.NOT_HTTPS, {}]
res.handshakeLength = packet[6..8]
res.handshakeVersion = packet.readUInt16BE 9
res.random = packet[11..42]
res.sessionIDlength = packet.readUInt8 43
pos = res.sessionIDlength + 43 + 1
res.cipherSuitesLength = packet.readUInt16BE pos
pos += res.cipherSuitesLength + 2
res.compressionMethodsLength = packet.readUInt8 pos
pos += res.compressionMethodsLength + 1
res.extensionsLength = packet.readUInt16BE pos
pos += 2
extensionsEnd = pos + res.extensionsLength - 1
jump = 0
res.extensions = {}
while pos < extensionsEnd
ext = {}
ext.type = packet.readUInt16BE pos
ext.length = packet.readUInt16BE (pos+2)
jump = ext.length+4
ext.body = packet[pos..(pos+jump-1)]
res.extensions[ext.type] = ext
pos += jump
if res.extensions["0"]?
sniPos = 0
sni = res.extensions["0"]
sni.sniType = sni.body.readUInt16BE 0
sni.sniLength = sni.body.readUInt16BE 2
sni.sniList = sni.body.readUInt16BE 4
sni.sniNameType = sni.body.readUInt8 6
sni.sniNameLength = sni.body.readUInt16BE 7
sni.sniName = sni.body[9..(9+sni.sniNameLength)]
res.host = sni.sniName.toString "utf8"
if res.host.length != sni.sniNameLength then return [.NOT_HTTPS, {}]
return [.SNI, res]
else
return [.NO_SNI, {}]
catch ex
return [.INCOMPLETE, {}]
# Open a TCP socket to a remote host.
getStream: (host, port, cb) ->
try
done = (err, s) ->
done = ->
cb err, s
s = net.createConnection {host, port}, ->
done null, s
s.on "error", (err) -> s.destroy(); done err
s.on "close", -> s.destroy()
s.on "timeout", -> s.destroy(); done new Error "getStream timed out"
catch err
done err
# Received raw TCP data in chunked mode and attempt to extract Hello data
# after every chunk. Return as soon as the Hello data has been obtained.
getClientHello: (c, cb) ->
received = []
buf = new Buffer []
done = (err, category, host, buf) ->
c.removeAllListeners("data")
done = ->
cb err, category, host, buf
c.on "data", (data) =>
c.pause()
received.push data
buf = Buffer.concat received
[category, parsed] = buf
switch category
when .SNI
done null, category, parsed.host, buf
when .NO_SNI
done null, category, null, buf
when .NOT_HTTPS
done new Error "NOT HTTPS", category, null, buf
when .INCOMPLETE
c.resume()
else
done new Error "Unimplemented", category, null, buf
c.on "timeout", ->
c.destroy()
done new Error "HTTPS getClientHello timeout"
c.on "error", (err) ->
c.destroy()
done err
c.on "close", ->
c.destroy()
done new Error "HTTPS socket closed"