在NodeJS中使用协议实现STARTTLS

我正在尝试将STARTTLS升级添加到现有的协议(目前工作在纯文本中)。

首先,我使用一个简单的基于行的回显服务器(这是一个可怕的混乱,没有error handling或处理数据包到行 – 但它通常只是作为控制台一次发送一行一行到标准input)。

我认为我的服务器是正确的,但是当我键入starttls时,两端都会出现相同的错误:

 events.js:72 throw er; // Unhandled 'error' event ^ Error: 139652888721216:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:766: at SlabBuffer.use (tls.js:232:18) at CleartextStream.read [as _read] (tls.js:450:29) at CleartextStream.Readable.read (_stream_readable.js:320:10) at EncryptedStream.write [as _write] (tls.js:366:25) at doWrite (_stream_writable.js:221:10) at writeOrBuffer (_stream_writable.js:211:5) at EncryptedStream.Writable.write (_stream_writable.js:180:11) at Socket.ondata (stream.js:51:26) at Socket.EventEmitter.emit (events.js:95:17) at Socket.<anonymous> (_stream_readable.js:746:14) 

我完全误解了如何在客户端进行升级吗?

目前,我使用相同的方法将TLS-ness添加到普通stream的每一端。 这种感觉是错误的,因为客户端和服务器都将在协商中扮演同样的angular色。

tlsserver.js:

 r tls = require('tls'); var net = require('net'); var fs = require('fs'); var options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: true, // This is necessary only if the client uses the self-signed certificate. ca: [ fs.readFileSync('client-cert.pem') ], rejectUnauthorized: false }; var server = net.createServer(function(socket) { socket.setEncoding('utf8'); socket.on('data', function(data) { console.log('plain data: ', data); // FIXME: this is not robust, it should be processing the stream into lines if (data.substr(0, 8) === 'starttls') { console.log('server starting TLS'); //socket.write('server starting TLS'); socket.removeAllListeners('data'); options.socket = socket; sec_socket = tls.connect(options, (function() { sec_socket.on('data', function() { console.log('secure data: ', data); }); return callback(null, true); }).bind(this)); } else { console.log('plain data', data); } }); }); server.listen(9999, function() { console.log('server bound'); }); 

client.js:

 var tls = require('tls'); var fs = require('fs'); var net = require('net'); var options = { // These are necessary only if using the client certificate authentication key: fs.readFileSync('client-key.pem'), cert: fs.readFileSync('client-cert.pem'), // This is necessary only if the server uses the self-signed certificate ca: [ fs.readFileSync('server-cert.pem') ], rejectUnauthorized: false }; var socket = new net.Socket(); var sec_socket = undefined; socket.setEncoding('utf8'); socket.on('data', function(data) { console.log('plain data:', data); }); socket.connect(9999, function() { process.stdin.setEncoding('utf8'); process.stdin.on('data', function(data) { if (!sec_socket) { console.log('sending plain:', data); socket.write(data); } else { console.log('sending secure:', data); sec_socket.write(data); } if (data.substr(0, 8) === 'starttls') { console.log('client starting tls'); socket.removeAllListeners('data'); options.socket = socket; sec_socket = tls.connect(options, (function() { sec_socket.on('data', function() { console.log('secure data: ', data); }); return callback(null, true); }).bind(this)); } }); }); 

得到它的工作,感谢马特Seargeant的答案。 我的代码现在看起来像:

server.js:

 var ts = require('./tls_socket'); var fs = require('fs'); var options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: false, // This is necessary only if the client uses the self-signed certificate. ca: [ fs.readFileSync('client-cert.pem') ], rejectUnauthorized: false }; var server = ts.createServer(function(socket) { console.log('connected'); socket.on('data', function(data) { console.log('data', data); if (data.length === 9) { console.log('upgrading to TLS'); socket.upgrade(options, function() { console.log('upgraded to TLS'); }); } }); }); server.listen(9999); 

client.js:

 var ts = require('./tls_socket'); var fs = require('fs'); var crypto = require('crypto'); var options = { // These are necessary only if using the client certificate authentication key: fs.readFileSync('client-key.pem'), cert: fs.readFileSync('client-cert.pem'), // This is necessary only if the server uses the self-signed certificate ca: [ fs.readFileSync('server-cert.pem') ], rejectUnauthorized: false }; var socket = ts.connect(9999, 'localhost', function() { console.log('secured'); }); process.stdin.on('data', function(data) { console.log('sending:', data); socket.write(data); if (data.length === 9) { socket.upgrade(options); } }); 

tls_socket.js:

 "use strict"; /*----------------------------------------------------------------------------------------------*/ /* Obtained and modified from http://js.5sh.net/starttls.js on 8/18/2011. */ /*----------------------------------------------------------------------------------------------*/ var tls = require('tls'); var crypto = require('crypto'); var util = require('util'); var net = require('net'); var stream = require('stream'); var SSL_OP_ALL = require('constants').SSL_OP_ALL; // provides a common socket for attaching // and detaching from either main socket, or crypto socket function pluggableStream(socket) { stream.Stream.call(this); this.readable = this.writable = true; this._timeout = 0; this._keepalive = false; this._writeState = true; this._pending = []; this._pendingCallbacks = []; if (socket) this.attach(socket); } util.inherits(pluggableStream, stream.Stream); pluggableStream.prototype.pause = function () { if (this.targetsocket.pause) { this.targetsocket.pause(); this.readable = false; } } pluggableStream.prototype.resume = function () { if (this.targetsocket.resume) { this.readable = true; this.targetsocket.resume(); } } pluggableStream.prototype.attach = function (socket) { var self = this; self.targetsocket = socket; self.targetsocket.on('data', function (data) { self.emit('data', data); }); self.targetsocket.on('connect', function (a, b) { self.emit('connect', a, b); }); self.targetsocket.on('secureConnection', function (a, b) { self.emit('secureConnection', a, b); self.emit('secure', a, b); }); self.targetsocket.on('secure', function (a, b) { self.emit('secureConnection', a, b); self.emit('secure', a, b); }); self.targetsocket.on('end', function () { self.writable = self.targetsocket.writable; self.emit('end'); }); self.targetsocket.on('close', function (had_error) { self.writable = self.targetsocket.writable; self.emit('close', had_error); }); self.targetsocket.on('drain', function () { self.emit('drain'); }); self.targetsocket.on('error', function (exception) { self.writable = self.targetsocket.writable; self.emit('error', exception); }); self.targetsocket.on('timeout', function () { self.emit('timeout'); }); if (self.targetsocket.remotePort) { self.remotePort = self.targetsocket.remotePort; } if (self.targetsocket.remoteAddress) { self.remoteAddress = self.targetsocket.remoteAddress; } }; pluggableStream.prototype.clean = function (data) { if (this.targetsocket && this.targetsocket.removeAllListeners) { this.targetsocket.removeAllListeners('data'); this.targetsocket.removeAllListeners('secureConnection'); this.targetsocket.removeAllListeners('secure'); this.targetsocket.removeAllListeners('end'); this.targetsocket.removeAllListeners('close'); this.targetsocket.removeAllListeners('error'); this.targetsocket.removeAllListeners('drain'); } this.targetsocket = {}; this.targetsocket.write = function () {}; }; pluggableStream.prototype.write = function (data, encoding, callback) { if (this.targetsocket.write) { return this.targetsocket.write(data, encoding, callback); } return false; }; pluggableStream.prototype.end = function (data, encoding) { if (this.targetsocket.end) { return this.targetsocket.end(data, encoding); } } pluggableStream.prototype.destroySoon = function () { if (this.targetsocket.destroySoon) { return this.targetsocket.destroySoon(); } } pluggableStream.prototype.destroy = function () { if (this.targetsocket.destroy) { return this.targetsocket.destroy(); } } pluggableStream.prototype.setKeepAlive = function (bool) { this._keepalive = bool; return this.targetsocket.setKeepAlive(bool); }; pluggableStream.prototype.setNoDelay = function (/* true||false */) { }; pluggableStream.prototype.setTimeout = function (timeout) { this._timeout = timeout; return this.targetsocket.setTimeout(timeout); }; function pipe(pair, socket) { pair.encrypted.pipe(socket); socket.pipe(pair.encrypted); pair.fd = socket.fd; var cleartext = pair.cleartext; cleartext.socket = socket; cleartext.encrypted = pair.encrypted; cleartext.authorized = false; function onerror(e) { if (cleartext._controlReleased) { cleartext.emit('error', e); } } function onclose() { socket.removeListener('error', onerror); socket.removeListener('close', onclose); } socket.on('error', onerror); socket.on('close', onclose); return cleartext; } function createServer(cb) { var serv = net.createServer(function (cryptoSocket) { var socket = new pluggableStream(cryptoSocket); socket.upgrade = function (options, cb) { console.log("Upgrading to TLS"); socket.clean(); cryptoSocket.removeAllListeners('data'); // Set SSL_OP_ALL for maximum compatibility with broken clients // See http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html if (!options) options = {}; // TODO: bug in Node means we can't do this until it's fixed // options.secureOptions = SSL_OP_ALL; var sslcontext = crypto.createCredentials(options); var pair = tls.createSecurePair(sslcontext, true, true, false); var cleartext = pipe(pair, cryptoSocket); pair.on('error', function(exception) { socket.emit('error', exception); }); pair.on('secure', function() { var verifyError = (pair.ssl || pair._ssl).verifyError(); console.log("TLS secured."); if (verifyError) { cleartext.authorized = false; cleartext.authorizationError = verifyError; } else { cleartext.authorized = true; } var cert = pair.cleartext.getPeerCertificate(); if (pair.cleartext.getCipher) { var cipher = pair.cleartext.getCipher(); } socket.emit('secure'); if (cb) cb(cleartext.authorized, verifyError, cert, cipher); }); cleartext._controlReleased = true; socket.cleartext = cleartext; if (socket._timeout) { cleartext.setTimeout(socket._timeout); } cleartext.setKeepAlive(socket._keepalive); socket.attach(socket.cleartext); }; cb(socket); }); return serv; } if (require('semver').gt(process.version, '0.7.0')) { var _net_connect = function (options) { return net.connect(options); } } else { var _net_connect = function (options) { return net.connect(options.port, options.host); } } function connect(port, host, cb) { var options = {}; if (typeof port === 'object') { options = port; cb = host; } else { options.port = port; options.host = host; } var cryptoSocket = _net_connect(options); var socket = new pluggableStream(cryptoSocket); socket.upgrade = function (options) { socket.clean(); cryptoSocket.removeAllListeners('data'); // Set SSL_OP_ALL for maximum compatibility with broken servers // See http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html if (!options) options = {}; // TODO: bug in Node means we can't do this until it's fixed // options.secureOptions = SSL_OP_ALL; var sslcontext = crypto.createCredentials(options); var pair = tls.createSecurePair(sslcontext, false); socket.pair = pair; var cleartext = pipe(pair, cryptoSocket); pair.on('error', function(exception) { socket.emit('error', exception); }); pair.on('secure', function() { var verifyError = (pair.ssl || pair._ssl).verifyError(); console.log("client TLS secured."); if (verifyError) { cleartext.authorized = false; cleartext.authorizationError = verifyError; } else { cleartext.authorized = true; } if (cb) cb(); socket.emit('secure'); }); cleartext._controlReleased = true; socket.cleartext = cleartext; if (socket._timeout) { cleartext.setTimeout(socket._timeout); } cleartext.setKeepAlive(socket._keepalive); socket.attach(socket.cleartext); console.log("client TLS upgrade in progress, awaiting secured."); }; return (socket); } exports.connect = connect; exports.createConnection = connect; exports.Server = createServer; exports.createServer = createServer; 

tls.connect()不支持不幸的升级服务器。

你必须使用类似Haraka的代码 – 基本上使用SecurePair创build自己的Shim。

在这里看到我们使用的代码: https : //github.com/baudehlo/Haraka/blob/master/tls_socket.js#L171