2013-12-19 54 views
1

我試圖將STARTTLS升級添加到現有協議(當前工作在純文本中)。在NodeJS中使用協議實現STARTTLS

作爲一個開始,我使用了一個簡單的基於行的回顯服務器(這是一個可怕的kludge,沒有錯誤處理或將數據包處理成行 - 但它通常只是在控制檯發送一行一行時間到標準輸入)。

我想我的服務器是正確的,但兩端具有相同的錯誤退出當我輸入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添加到普通流的每一端。這種感覺是錯誤的,因爲客戶端和服務器都將試圖在協商中發揮相同的作用。

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; 
+0

也許我應該使用SecurePair http://nodejs.org/api/tls.html#tls_tls_createsecurepair_credentials_isserver_requestcert_rejectunauthorized而不是'在客戶端tls.connect',或兩端。我會在午餐後測試。 – fadedbee

回答

1

tls.connect()沒有按」 t不幸地支持服務器進行升級。

你必須使用類似Haraka的代碼 - 基本上使用SecurePair創建自己的墊片。

看到這裏的代碼,我們使用:https://github.com/baudehlo/Haraka/blob/master/tls_socket.js#L171

+0

我正確地認爲客戶端和服務器代碼都需要升級嗎?而且請求是在明文應用協議中傳遞的? – fadedbee

+0

我破解了你的tls_socket.js文件,以至於它不需要Haraka的其餘部分,並且它第一次運行!我會將我的代碼粘貼到原始問題中,以防其他人遇到TLS問題。 – fadedbee