-
Notifications
You must be signed in to change notification settings - Fork 100
Incomplete file when uploaded over TLS data connection #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,25 +2,7 @@ | |
| // https://github.com/andris9/rai/blob/master/lib/starttls.js | ||
| // (This code is MIT licensed.) | ||
|
|
||
| // | ||
| // Target API: | ||
| // | ||
| // var s = require('net').createStream(25, 'smtp.example.com'); | ||
| // s.on('connect', function() { | ||
| // require('starttls')(s, options, function() { | ||
| // if (!s.authorized) { | ||
| // s.destroy(); | ||
| // return; | ||
| // } | ||
| // | ||
| // s.end("hello world\n"); | ||
| // }); | ||
| // }); | ||
| // | ||
| // | ||
|
|
||
| var tls = require('tls'); | ||
| var crypto = require('crypto'); | ||
|
|
||
| // From Node docs for TLS module. | ||
| var RECOMMENDED_CIPHERS = 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM'; | ||
|
|
@@ -33,111 +15,46 @@ function starttlsClient(socket, options, callback) { | |
| } | ||
|
|
||
| function starttls(socket, options, callback, isServer) { | ||
| var sslcontext; | ||
|
|
||
| var opts = {}; | ||
|
|
||
| Object.keys(options).forEach(function(key) { | ||
| opts[key] = options[key]; | ||
| }); | ||
| if (!opts.ciphers) { | ||
| opts.ciphers = RECOMMENDED_CIPHERS; | ||
| } | ||
| opts.isServer = isServer; | ||
| opts.secureContext = tls.createSecureContext(opts); | ||
|
|
||
| socket.removeAllListeners('data'); | ||
| if (tls.createSecureContext) { | ||
| sslcontext = tls.createSecureContext(opts); | ||
| } else { | ||
| sslcontext = crypto.createCredentials(opts); | ||
| } | ||
| var pair = tls.createSecurePair(sslcontext, isServer); | ||
| var cleartext = pipe(pair, socket); | ||
|
|
||
| var secureSocket = new tls.TLSSocket(socket, opts); | ||
| var erroredOut = false; | ||
| pair.on('secure', function() { | ||
|
|
||
| // NodeJS documentation bug: secure vs secureConnect | ||
| // https://github.com/nodejs/node/issues/10555 | ||
| secureSocket.on('secure', function() { | ||
| if (erroredOut) { | ||
| pair.end(); | ||
| secureSocket.end(); | ||
| return; | ||
| } | ||
|
|
||
| var verifyError = (pair._ssl || pair.ssl).verifyError(); | ||
|
|
||
| if (verifyError) { | ||
| cleartext.authorized = false; | ||
| cleartext.authorizationError = verifyError; | ||
| var authError = secureSocket.ssl.verifyError(); | ||
| if (authError) { | ||
| secureSocket.authorized = false; | ||
| secureSocket.authorizationError = authError; | ||
| } else { | ||
| cleartext.authorized = true; | ||
| secureSocket.authorized = true; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its not authorized yet. You have connected to a valid certificate, but you don't know if its a certificate for the host you want to connect to! It See https://github.com/sam-github/node/blob/39f39189ee6d4ef3c6d20033721ae515a744267f/doc/api/tls.md#event-secure
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I can do that as I don't know the hostname of client. This is server side of FTP connection, so client is connecting to us. All we can know about the client is perhaps its IP address and even that one does not have to be correct (if client is behind NAT or so). So making sure that the certificate presented by the client is valid is probably the best we can do here. It looks that neither the previous version of the code called checkServerIdentity(). Since it's not a regression I would prefer to leave the code as it is.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this code even special for the case where clients do present their own certificates, or do we have to assume the client authenticated with username and password? In the latter case, as far as I understand it, there would be no client certificate at all that we could verify.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. By skipping server identity check we can make people to believe that the connection is fully secure while it is not. IMHO it is really a corner case as I doubt that any one runs FTP server configured to validate client certificates but nevertheless we can either:
@mk-pmb: What would be the preferred solution in your opinion? I'm open to other suggestions as well. thanks
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I skimmed the README again to see what behavior people might expect from our config documentation. Took me a while to find the TLS options, we should add some keyword-y hints like "Configure your server certificate and private key here." The best I could find was
The validation event should provide:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yea, you are right. checkServerIdentity is used only on client side of TLS connection. So my proposal regarding checkServerIdentity was not correct. I looked at what general TLS server implementation in nodejs does and it is surprising: https://github.com/nodejs/node/blob/master/lib/_tls_wrap.js#L814 . It does not bother to verify hostname to match a certificate at all. Valid certificate used by client is sufficient. I got confused by Sam's initial comment about missing checkServerIdentity call: Sam thought that we deal with client side of the connection, but that's wrong. So if we want to provide more sophisticated validation of client certificates, it is possible but we should pick a different name for such method than checkServerIdentity (i.e. checkClientIdentity :-) ) and it would get called only if client's certificate was successfully verified. Regardless how it is named implementation of this new option sounds exactly like feature which could be implemented in future as extension of what we have now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, sorry, I thought it was client side code.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should think of what exactly the client's cert shall certify. The remote IP or its hostname, probably not. I could think of a username disguised as a hostname, or email address, my CA software could do both. Those would make sense to verify.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that it is not very clear what should be subject to verification. We should keep in mind that when TLS handshake happens on control connection we don't even know client's login credentials. I'm sure it will be much easier to add this feature when there is a need for it, rather than if one has to guess what might be needed without any particular use case. I have updated my PR to include documentation changes clarifying how tlsOptions should be used and inserted a couple of TODOs to source code - hints on possible future improvements. Thanks!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, so we'd have to save which name the cert claimed to certify (if we believe it) so we can remember it when a USER or PASS command arrives. |
||
| } | ||
|
|
||
| callback(null, cleartext); | ||
| callback(null, secureSocket); | ||
| }); | ||
| pair.once('error', function(err) { | ||
| secureSocket.once('error', function(err) { | ||
| if (!erroredOut) { | ||
| erroredOut = true; | ||
| callback(err); | ||
| } | ||
| }); | ||
|
|
||
| cleartext._controlReleased = true; | ||
| pair; | ||
| } | ||
|
|
||
| function forwardEvents(events, emitterSource, emitterDestination) { | ||
| var map = []; | ||
|
|
||
| for (var i = 0, len = events.length; i < len; i++) { | ||
| var name = events[i]; | ||
|
|
||
| var handler = forwardEvent.bind(emitterDestination, name); | ||
|
|
||
| map.push(name); | ||
| emitterSource.on(name, handler); | ||
| } | ||
|
|
||
| return map; | ||
| } | ||
|
|
||
| function forwardEvent() { | ||
| this.emit.apply(this, arguments); | ||
| } | ||
|
|
||
| function removeEvents(map, emitterSource) { | ||
| for (var i = 0, len = map.length; i < len; i++) { | ||
| emitterSource.removeAllListeners(map[i]); | ||
| } | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
|
|
||
| var map = forwardEvents(['timeout', 'end', 'close', 'drain', 'error'], socket, cleartext); | ||
|
|
||
| function onclose() { | ||
| socket.removeListener('error', onerror); | ||
| socket.removeListener('close', onclose); | ||
| removeEvents(map, socket); | ||
| } | ||
|
|
||
| socket.on('error', onerror); | ||
| socket.on('close', onclose); | ||
|
|
||
| return cleartext; | ||
| } | ||
|
|
||
| exports.starttlsServer = starttlsServer; | ||
| exports.starttlsClient = starttlsClient; | ||
| exports.RECOMMENDED_CIPHERS = RECOMMENDED_CIPHERS; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-(. I will document the API.