-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Added TLS-PSK support. #1162
Added TLS-PSK support. #1162
Changes from 1 commit
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 |
|---|---|---|
|
|
@@ -5,12 +5,12 @@ Use `require('tls')` to access this module. | |
| The `tls` module uses OpenSSL to provide Transport Layer Security and/or | ||
| Secure Socket Layer: encrypted stream communication. | ||
|
|
||
| TLS/SSL is a public/private key infrastructure. Each client and each | ||
| Traditional TLS/SSL is a public/private key infrastructure. Each client and each | ||
| server must have a private key. A private key is created like this | ||
|
|
||
| openssl genrsa -out ryans-key.pem 1024 | ||
|
|
||
| All severs and some clients need to have a certificate. Certificates are public | ||
| All servers and some clients need to have a certificate. Certificates are public | ||
| keys signed by a Certificate Authority or self-signed. The first step to | ||
| getting a certificate is to create a "Certificate Signing Request" (CSR) | ||
| file. This is done with: | ||
|
|
@@ -26,29 +26,56 @@ Alternatively you can send the CSR to a Certificate Authority for signing. | |
| (TODO: docs on creating a CA, for now interested users should just look at | ||
| `test/fixtures/keys/Makefile` in the Node source code) | ||
|
|
||
| If node is compiled with OpenSSL v1.0.0 or later then TLS-PSK (RFC 4279) support is | ||
| available as an alternative to normal certificate-based authentication. PSK uses | ||
| a pre-shared key instead of certificates to authenticate a TLS connection, providing | ||
| mutual authentication (normal cert-based TLS is usually only one-way authentication). | ||
| PSK and certificate auth are not mutually exclusive; one server can accommodate both, | ||
| with the variety used determined by the normal cipher negotiation step. | ||
| Note that PSK is only a good choice where means exist to securely share a key with | ||
| every connecting machine, so it does not replace PKI for the majority of TLS uses. | ||
|
|
||
| ### s = tls.connect(port, [host], [options], callback) | ||
|
|
||
| Creates a new client connection to the given `port` and `host`. (If `host` | ||
| defaults to `localhost`.) `options` should be an object which specifies | ||
|
|
||
| - `key`: A string or `Buffer` containing the private key of the server in | ||
| PEM format. (Required) | ||
| - `key`: A string or `Buffer` containing the client's private key in PEM format. | ||
|
|
||
| - `cert`: A string or `Buffer` containing the certificate key of the server in | ||
| PEM format. | ||
| - `cert`: A string or `Buffer` containing the client's certificate in PEM format. | ||
|
|
||
| - `ca`: An array of strings or `Buffer`s of trusted certificates. If this is | ||
| omitted several well known "root" CAs will be used, like VeriSign. | ||
| These are used to authorize connections. | ||
|
|
||
| - `pskIdentity`: A string containing the TLS-PSK identity to use when connecting. | ||
|
|
||
| - `pskKey`: A `Buffer` containing the binary pre-shared key corresponding | ||
| to the `pskIdentity`. | ||
|
|
||
| - `pskCallback`: A callback that may be provided if you wish to select the identity | ||
| and key based on the "hint" provided by the server. The callback receives the hint | ||
| as its sole argument and must return an object with `pskIdentity` and `pskKey` | ||
| attributes as described above. This option is ignored if `pskIdentity` and `pskKey` | ||
| are provided outright. | ||
|
|
||
| - `ciphers`: An OpenSSL-style cipher priority list (eg `RC4-SHA:AES128-SHA:AES256-SHA`). | ||
| Optional, but note that the cipher negotiation between client and server determines | ||
| how the TLS handshake will proceed; for PSK both client and server must be configured | ||
| to use PSK ciphers. For ease of use, if PSK identity options are provided but `ciphers` | ||
| is not, it will be configured with a default set of PSK ciphers. | ||
|
|
||
| `tls.connect()` returns a cleartext `CryptoStream` object. | ||
|
|
||
| After the TLS/SSL handshake the `callback` is called. The `callback` will be | ||
| called no matter if the server's certificate was authorized or not. It is up | ||
| to the user to test `s.authorized` to see if the server certificate was | ||
| signed by one of the specified CAs. If `s.authorized === false` then the error | ||
| can be found in `s.authorizationError`. | ||
| After the TLS/SSL handshake the `callback` is called. If using normal certificate-based | ||
| TLS, The `callback` will be called even if the server's certificate was not authorized. | ||
| It is up to the user to test `s.authorized` to see if the server certificate was signed | ||
| by one of the specified CAs. If `s.authorized === false` then the error can be found in | ||
| `s.authorizationError`. If using PSK, the handshake will only complete if the pre-shared | ||
| keys match; `s.authorized` will be `true` and `s.pskIdentity` will be clientss identity | ||
| string. If the keys do not match then the handshake will fail. The callback will not be | ||
| called but the cleartext stream will emit an `error` event. | ||
|
|
||
|
||
|
|
||
|
|
||
| ### STARTTLS | ||
|
|
@@ -98,10 +125,10 @@ This is a constructor for the `tls.Server` class. The options object | |
| has these possibilities: | ||
|
|
||
| - `key`: A string or `Buffer` containing the private key of the server in | ||
| PEM format. (Required) | ||
| PEM format. (Required, unless using PSK) | ||
|
|
||
| - `cert`: A string or `Buffer` containing the certificate key of the server in | ||
| PEM format. (Required) | ||
| PEM format. (Required, unless using PSK) | ||
|
|
||
| - `ca`: An array of strings or `Buffer`s of trusted certificates. If this is | ||
| omitted several well known "root" CAs will be used, like VeriSign. | ||
|
|
@@ -115,6 +142,20 @@ has these possibilities: | |
| which is not authorized with the list of supplied CAs. This option only | ||
| has an effect if `requestCert` is `true`. Default: `false`. | ||
|
|
||
| - `pskCallback`: A function that receives a TLS-PSK identity string sent by | ||
| the connecting client and should synchronously return a `Buffer` containing | ||
| that user's key, or null if the user isn't recognized. (Required if using PSK) | ||
|
|
||
| - `pskHint`: Optional "hint" string sent to each connecting client to help the | ||
| client determine which identity to use. By default, no hint is sent. See RFC | ||
| 4279 for details. | ||
|
|
||
| - `ciphers`: An OpenSSL-style cipher priority list (eg `RC4-SHA:AES128-SHA:AES256-SHA`). | ||
| Optional, but note that the cipher negotiation between client and server determines | ||
| how the TLS handshake will proceed; for PSK both client and server must be configured | ||
| to use PSK ciphers. For ease of use, if a PSK callback is provided but `ciphers` | ||
| is not, it will be configured with a default set of PSK ciphers. | ||
|
|
||
|
|
||
| #### Event: 'secureConnection' | ||
|
|
||
|
|
@@ -129,7 +170,7 @@ client has verified by one of the supplied certificate authorities for the | |
| server. If `cleartextStream.authorized` is false, then | ||
| `cleartextStream.authorizationError` is set to describe how authorization | ||
| failed. Implied but worth mentioning: depending on the settings of the TLS | ||
| server, you unauthorized connections may be accepted. | ||
| server, your unauthorized connections may be accepted. | ||
|
|
||
|
|
||
| #### server.listen(port, [host], [callback]) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,8 @@ if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) { | |
| debug = function() { }; | ||
| } | ||
|
|
||
| // ciphers to use if a server or client is configured for TLS-PSK but does not specify a cipher list | ||
| var DefaultPSKCiphers = 'PSK-AES256-CBC-SHA:PSK-3DES-EDE-CBC-SHA:PSK-AES128-CBC-SHA:PSK-RC4-SHA'; | ||
|
||
|
|
||
| var Connection = null; | ||
| try { | ||
|
|
@@ -69,7 +71,7 @@ function convertNPNProtocols(NPNProtocols, out) { | |
| if (Buffer.isBuffer(NPNProtocols)) { | ||
| out.NPNProtocols = NPNProtocols; | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // Base class of both CleartextStream and EncryptedStream | ||
| function CryptoStream(pair) { | ||
|
|
@@ -478,15 +480,17 @@ EncryptedStream.prototype._pusher = function(pool, offset, length) { | |
| */ | ||
|
|
||
| function SecurePair(credentials, isServer, requestCert, rejectUnauthorized, | ||
| NPNProtocols) { | ||
| NPNProtocols, pskCallback) { | ||
| if (!(this instanceof SecurePair)) { | ||
| return new SecurePair(credentials, | ||
| isServer, | ||
| requestCert, | ||
| rejectUnauthorized, | ||
| NPNProtocols); | ||
| NPNProtocols, | ||
| pskCallback); | ||
| } | ||
|
|
||
|
|
||
| var self = this; | ||
|
|
||
| events.EventEmitter.call(this); | ||
|
|
@@ -523,6 +527,10 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized, | |
| this.npnProtocol = null; | ||
| } | ||
|
|
||
| if (pskCallback) { | ||
| this.ssl.setPskClientCallback(pskCallback); | ||
| } | ||
|
|
||
| /* Acts as a r/w stream to the cleartext side of the stream. */ | ||
| this.cleartext = new CleartextStream(this); | ||
|
|
||
|
|
@@ -541,11 +549,14 @@ util.inherits(SecurePair, events.EventEmitter); | |
| exports.createSecurePair = function(credentials, | ||
| isServer, | ||
| requestCert, | ||
| rejectUnauthorized) { | ||
| rejectUnauthorized, | ||
| pskCallback) { | ||
| var pair = new SecurePair(credentials, | ||
| isServer, | ||
| requestCert, | ||
| rejectUnauthorized); | ||
| rejectUnauthorized, | ||
| null, | ||
| pskCallback); | ||
| return pair; | ||
| }; | ||
|
|
||
|
|
@@ -675,7 +686,7 @@ SecurePair.prototype.error = function() { | |
| } | ||
| }; | ||
|
|
||
| // TODO: support anonymous (nocert) and PSK | ||
| // TODO: support anonymous (nocert) | ||
|
|
||
|
|
||
| // AUTHENTICATION MODES | ||
|
|
@@ -774,11 +785,11 @@ function Server(/* [options], listener */) { | |
| ciphers: self.ciphers, | ||
| secureProtocol: self.secureProtocol, | ||
| secureOptions: self.secureOptions, | ||
| crl: self.crl | ||
| crl: self.crl, | ||
| pskServerCallback: self.pskCallback, | ||
| pskServerHint: self.pskHint | ||
| }); | ||
|
|
||
| sharedCreds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); | ||
|
|
||
| // constructor call | ||
| net.Server.call(this, function(socket) { | ||
| var creds = crypto.createCredentials(null, sharedCreds.context); | ||
|
|
@@ -795,25 +806,35 @@ function Server(/* [options], listener */) { | |
| pair.on('secure', function() { | ||
| pair.cleartext.authorized = false; | ||
| pair.cleartext.npnProtocol = pair.npnProtocol; | ||
| if (!self.requestCert) { | ||
|
|
||
| // if using PSK, then handshake completion implies authorization | ||
| if (pair.ssl.pskIdentity) { | ||
| pair.cleartext.pskIdentity = pair.ssl.pskIdentity; | ||
| pair.cleartext.authorized = true; | ||
| cleartext._controlReleased = true; | ||
| self.emit('secureConnection', pair.cleartext, pair.encrypted); | ||
| } else { | ||
| var verifyError = pair.ssl.verifyError(); | ||
| if (verifyError) { | ||
| pair.cleartext.authorizationError = verifyError; | ||
|
|
||
| if (self.rejectUnauthorized) { | ||
| socket.destroy(); | ||
| pair.destroy(); | ||
| } | ||
| else { | ||
| // otherwise, might need to check for cert verification errors | ||
| if (!self.requestCert) { | ||
| cleartext._controlReleased = true; | ||
| self.emit('secureConnection', pair.cleartext, pair.encrypted); | ||
| } else { | ||
| var verifyError = pair.ssl.verifyError(); | ||
| if (verifyError) { | ||
| pair.cleartext.authorizationError = verifyError; | ||
| if (self.rejectUnauthorized) { | ||
| socket.destroy(); | ||
| pair.destroy(); | ||
| } else { | ||
| cleartext._controlReleased = true; | ||
| self.emit('secureConnection', pair.cleartext, pair.encrypted); | ||
| } | ||
| } else { | ||
| pair.cleartext.authorized = true; | ||
| cleartext._controlReleased = true; | ||
| self.emit('secureConnection', pair.cleartext, pair.encrypted); | ||
| } | ||
| } else { | ||
| pair.cleartext.authorized = true; | ||
| cleartext._controlReleased = true; | ||
| self.emit('secureConnection', pair.cleartext, pair.encrypted); | ||
| } | ||
| } | ||
| }); | ||
|
|
@@ -847,6 +868,12 @@ Server.prototype.setOptions = function(options) { | |
| this.rejectUnauthorized = false; | ||
| } | ||
|
|
||
| if (options.pskCallback && !options.ciphers) { | ||
| // set default PSK ciphers if it looks like we're trying to use | ||
| // PSK but no preference has been specified | ||
| options.ciphers = DefaultPSKCiphers; | ||
| } | ||
|
|
||
| if (options.key) this.key = options.key; | ||
| if (options.cert) this.cert = options.cert; | ||
| if (options.ca) this.ca = options.ca; | ||
|
|
@@ -856,6 +883,8 @@ Server.prototype.setOptions = function(options) { | |
| if (options.secureProtocol) this.secureProtocol = options.secureProtocol; | ||
| if (options.secureOptions) this.secureOptions = options.secureOptions; | ||
| if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this); | ||
| if (options.pskCallback) this.pskCallback = options.pskCallback; | ||
| if (options.pskHint) this.pskHint = options.pskHint; | ||
| }; | ||
|
|
||
|
|
||
|
|
@@ -893,31 +922,66 @@ exports.connect = function(port /* host, options, cb */) { | |
| } | ||
| } | ||
|
|
||
| // if a PSK identity/key pair was provided, translate it into the | ||
| // callback expected by SecurePair | ||
| if (options.pskIdentity && options.pskKey) { | ||
| options.pskClientCallback = function(hint) { | ||
| return { | ||
| identity: options.pskIdentity, | ||
| key: options.pskKey | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| if (options.pskClientCallback && !options.ciphers) { | ||
| // set default PSK ciphers if it looks like we're trying to use | ||
| // PSK but no preference has been specified | ||
| options.ciphers = DefaultPSKCiphers; | ||
| } | ||
|
|
||
| var socket = new net.Stream(); | ||
|
|
||
| var sslcontext = crypto.createCredentials(options); | ||
| //sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); | ||
|
|
||
| convertNPNProtocols(options.NPNProtocols, this); | ||
| var pair = new SecurePair(sslcontext, false, true, false, | ||
| this.NPNProtocols); | ||
| this.NPNProtocols, options.pskClientCallback); | ||
|
|
||
| var cleartext = pipe(pair, socket); | ||
|
|
||
| // This handler runs if the socket closes before the TLS handshake completes, | ||
| // either because the remote host aborted a normal TLS handshake for some reason | ||
| // or, in the case of TLS-PSK, if the identity and/or secret is invalid. | ||
| function onSocketClose() { | ||
| cleartext.emit('error', new Error('remote host closed the connection')); | ||
| } | ||
| socket.on('close', onSocketClose); | ||
|
||
|
|
||
| socket.connect(port, host); | ||
|
|
||
| pair.on('secure', function() { | ||
| var verifyError = pair.ssl.verifyError(); | ||
|
|
||
| // the handshake is complete, so remove the close listener; | ||
| // a close hereafter is just a close, not an error | ||
| socket.removeListener('close', onSocketClose); | ||
| cleartext.npnProtocol = pair.npnProtocol; | ||
|
|
||
| if (verifyError) { | ||
| cleartext.authorized = false; | ||
| cleartext.authorizationError = verifyError; | ||
| } else { | ||
| if (pair.ssl.pskIdentity) { | ||
| // if there is a PSK identity, then we're authorized, since PSK inherently | ||
| // provides mutual authentication | ||
| cleartext.pskIdentity = pair.ssl.pskIdentity; | ||
| cleartext.authorized = true; | ||
| } else { | ||
| // otherwise check the ssl verifyError and make it | ||
| // available for the client code to inspect | ||
| var verifyError = pair.ssl.verifyError(); | ||
| if (verifyError) { | ||
| cleartext.authorized = false; | ||
| cleartext.authorizationError = verifyError; | ||
| } else { | ||
| cleartext.authorized = true; | ||
| } | ||
| } | ||
|
|
||
| // call the callback, indicating that the handshake is complete | ||
| if (cb) cb(); | ||
| }); | ||
|
|
||
|
|
||
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.
This statement seems moderately misleading considering node.js does support client certificates? I understand what is trying to be stated, maybe it just needs a different wording about the most common use cases.
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.
Good point. I was trying to note that in the vast majority of deployments TLS doesn't authenticate the client, but of course it could. I'll amend this.
(btw, sorry for the delay in responding. I was away for several days.)