2013-12-19 3 views
1

기존 프로토콜 (현재 일반 텍스트로 작동)에 STARTTLS 업그레이드를 추가하려고합니다.NodeJS의 프로토콜에서 STARTTLS 구현

처음에는 단순한 라인 기반 반향 서버를 사용하고 있습니다 (오류 처리 또는 패킷을 라인에 처리하지 않는 끔찍한 괴롭힘 -하지만 대개 콘솔이 line-at-a - stdin까지).

나는 내 서버가 옳다고 생각하지만, 내가 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

아마도 나는 오히려 클라이언트에서 tls.connect' '보다 나에 SecurePair http://nodejs.org/api/tls.html#tls_tls_createsecurepair_credentials_isserver_requestcert_rejectunauthorized 사용한다 양쪽 끝. 점심 먹은 후에 시험하겠습니다. – fadedbee

답변

1

tls.connect() 아무튼 내 코드는 지금처럼 보이는 불행하게도 업그레이드를 수행하는 서버를 지원하지 마십시오.

당신은 하라카 (Haraka)와 비슷한 코드를 사용해야합니다. 기본적으로 SecurePair를 사용하여 자신의 심을 만듭니다. 가

코드 여기를 참조하십시오

우리가 사용 https://github.com/baudehlo/Haraka/blob/master/tls_socket.js#L171

+0

클라이언트와 서버 코드를 모두 업그레이드해야한다고 생각합니까? 요청은 일반 텍스트 응용 프로그램 프로토콜로 전달됩니까? – fadedbee

+0

나는 당신의 tls_socket.js 파일을 해킹하여 나머지 Haraka는 필요 없으며 처음 실행되었습니다! 다른 사람이 TLS에 문제가있는 경우 원래 질문에 코드를 붙여 넣습니다. – fadedbee