closes #926.
var http = require('http');
var inherits = require('util').inherits;
+var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);
+
+ if (NPN_ENABLED && !opts.NPNProtocols) {
+ opts.NPNProtocols = ['http/1.1', 'http/1.0'];
+ }
+
tls.Server.call(this, opts, http._connectionListener);
this.httpAllowHalfOpen = false;
Agent.prototype._getConnection = function(host, port, cb) {
+ if (NPN_ENABLED && !this.options.NPNProtocols) {
+ this.options.NPNProtocols = ['http/1.1', 'http/1.0'];
+ }
+
var s = tls.connect(port, host, this.options, function() {
// do other checks here?
if (cb) cb();
var END_OF_FILE = 42;
var assert = require('assert').ok;
+var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
+
var debug;
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
debug = function(a) { console.error('TLS:', a); };
var Connection = null;
try {
Connection = process.binding('crypto').Connection;
+ exports.NPN_ENABLED = NPN_ENABLED;
} catch (e) {
throw new Error('node.js not compiled with openssl crypto support.');
}
+// Convert protocols array into valid OpenSSL protocols list
+// ("\x06spdy/2\x08http/1.1\x08http/1.0")
+function convertNPNProtocols(NPNProtocols, out) {
+ // If NPNProtocols is Array - translate it into buffer
+ if (Array.isArray(NPNProtocols)) {
+ var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
+ return p + 1 + Buffer.byteLength(c);
+ }, 0));
+
+ NPNProtocols.reduce(function(offset, c) {
+ var clen = Buffer.byteLength(c);
+ buff[offset] = clen;
+ buff.write(c, offset + 1);
+
+ return offset + 1 + clen;
+ }, 0);
+
+ NPNProtocols = buff;
+ }
+
+ // If it's already a Buffer - store it
+ if (Buffer.isBuffer(NPNProtocols)) {
+ out.NPNProtocols = NPNProtocols;
+ }
+};
// Base class of both CleartextStream and EncryptedStream
function CryptoStream(pair) {
* Provides a pair of streams to do encrypted communication.
*/
-function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) {
+function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
+ NPNProtocols) {
if (!(this instanceof SecurePair)) {
return new SecurePair(credentials,
isServer,
requestCert,
- rejectUnauthorized);
+ rejectUnauthorized,
+ NPNProtocols);
}
var self = this;
this._requestCert,
this._rejectUnauthorized);
+ if (NPN_ENABLED && NPNProtocols) {
+ this._ssl.setNPNProtocols(NPNProtocols);
+ this.npnProtocol = null;
+ }
/* Acts as a r/w stream to the cleartext side of the stream. */
this.cleartext = new CleartextStream(this);
SecurePair.prototype._maybeInitFinished = function() {
if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
+ if (NPN_ENABLED) {
+ this.npnProtocol = this._ssl.getNegotiatedProtocol();
+ }
+
this._secureEstablished = true;
debug('secure established');
this.emit('secure');
var pair = new SecurePair(creds,
true,
self.requestCert,
- self.rejectUnauthorized);
+ self.rejectUnauthorized,
+ self.NPNProtocols);
var cleartext = pipe(pair, socket);
cleartext._controlReleased = false;
pair.on('secure', function() {
pair.cleartext.authorized = false;
+ pair.cleartext.npnProtocol = pair.npnProtocol;
if (!self.requestCert) {
cleartext._controlReleased = true;
self.emit('secureConnection', pair.cleartext, pair.encrypted);
if (options.ciphers) this.ciphers = options.ciphers;
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.secureOptions) this.secureOptions = options.secureOptions;
+ if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
};
var sslcontext = crypto.createCredentials(options);
//sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
- var pair = new SecurePair(sslcontext, false);
+ convertNPNProtocols(options.NPNProtocols, this);
+ var pair = new SecurePair(sslcontext, false, true, false,
+ this.NPNProtocols);
var cleartext = pipe(pair, socket);
pair.on('secure', function() {
var verifyError = pair._ssl.verifyError();
+ cleartext.npnProtocol = pair.npnProtocol;
+
if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
NODE_DEFINE_CONSTANT(target, SSL_OP_CRYPTOPRO_TLSEXT_BUG);
#endif
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+#define NPN_ENABLED 1
+ NODE_DEFINE_CONSTANT(target, NPN_ENABLED);
+#endif
}
} // namespace node
NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", Connection::ReceivedShutdown);
NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);
+#ifdef OPENSSL_NPN_NEGOTIATED
+ NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", Connection::GetNegotiatedProto);
+ NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", Connection::SetNPNProtocols);
+#endif
+
target->Set(String::NewSymbol("Connection"), t->GetFunction());
}
return 1;
}
+#ifdef OPENSSL_NPN_NEGOTIATED
+
+int Connection::AdvertiseNextProtoCallback_(SSL *s,
+ const unsigned char **data,
+ unsigned int *len,
+ void *arg) {
+
+ Connection *p = static_cast<Connection*>(SSL_get_app_data(s));
+
+ if (p->npnProtos_.IsEmpty()) {
+ // No initialization - no NPN protocols
+ *data = reinterpret_cast<const unsigned char*>("");
+ *len = 0;
+ } else {
+ *data = reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));
+ *len = Buffer::Length(p->npnProtos_);
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+int Connection::SelectNextProtoCallback_(SSL *s,
+ unsigned char **out, unsigned char *outlen,
+ const unsigned char* in,
+ unsigned int inlen, void *arg) {
+ Connection *p = static_cast<Connection*> SSL_get_app_data(s);
+
+ // Release old protocol handler if present
+ if (!p->selectedNPNProto_.IsEmpty()) {
+ p->selectedNPNProto_.Dispose();
+ }
+
+ if (p->npnProtos_.IsEmpty()) {
+ // We should at least select one protocol
+ // If server is using NPN
+ *out = reinterpret_cast<unsigned char*>(const_cast<char*>("http/1.1"));
+ *outlen = 8;
+
+ // set status unsupported
+ p->selectedNPNProto_ = Persistent<Value>::New(False());
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ const unsigned char* npnProtos =
+ reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_));
+
+ int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos,
+ Buffer::Length(p->npnProtos_));
+
+ switch (status) {
+ case OPENSSL_NPN_UNSUPPORTED:
+ p->selectedNPNProto_ = Persistent<Value>::New(Null());
+ break;
+ case OPENSSL_NPN_NEGOTIATED:
+ p->selectedNPNProto_ = Persistent<Value>::New(String::New(
+ reinterpret_cast<const char*>(*out), *outlen
+ ));
+ break;
+ case OPENSSL_NPN_NO_OVERLAP:
+ p->selectedNPNProto_ = Persistent<Value>::New(False());
+ break;
+ default:
+ break;
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
Handle<Value> Connection::New(const Arguments& args) {
HandleScope scope;
p->ssl_ = SSL_new(sc->ctx_);
p->bio_read_ = BIO_new(BIO_s_mem());
p->bio_write_ = BIO_new(BIO_s_mem());
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+ SSL_set_app_data(p->ssl_, p);
+ if (is_server) {
+ // Server should advertise NPN protocols
+ SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
+ AdvertiseNextProtoCallback_,
+ NULL);
+ } else {
+ // Client should select protocol from advertised
+ // If server supports NPN
+ SSL_CTX_set_next_proto_select_cb(sc->ctx_,
+ SelectNextProtoCallback_,
+ NULL);
+ }
+#endif
+
SSL_set_bio(p->ssl_, p->bio_read_, p->bio_write_);
#ifdef SSL_MODE_RELEASE_BUFFERS
return True();
}
+#ifdef OPENSSL_NPN_NEGOTIATED
+Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) {
+ HandleScope scope;
+
+ Connection *ss = Connection::Unwrap(args);
+
+ if (ss->is_server_) {
+ const unsigned char *npn_proto;
+ unsigned int npn_proto_len;
+
+ SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);
+
+ if (!npn_proto) {
+ return False();
+ }
+
+ return String::New((const char*) npn_proto, npn_proto_len);
+ } else {
+ return ss->selectedNPNProto_;
+ }
+}
+
+Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
+ HandleScope scope;
+
+ Connection *ss = Connection::Unwrap(args);
+
+ if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
+ return ThrowException(Exception::Error(String::New(
+ "Must give a Buffer as first argument")));
+ }
+
+ // Release old handle
+ if (!ss->npnProtos_.IsEmpty()) {
+ ss->npnProtos_.Dispose();
+ }
+ ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject());
+
+ return True();
+};
+#endif
+
static void HexEncode(unsigned char *md_value,
int md_len,
#define SRC_NODE_CRYPTO_H_
#include <node.h>
+
#include <node_object_wrap.h>
#include <v8.h>
#include <openssl/x509.h>
#include <openssl/hmac.h>
+#ifdef OPENSSL_NPN_NEGOTIATED
+#include <node_buffer.h>
+#endif
+
#define EVP_F_EVP_DECRYPTFINAL 101
public:
static void Initialize(v8::Handle<v8::Object> target);
+#ifdef OPENSSL_NPN_NEGOTIATED
+ v8::Persistent<v8::Object> npnProtos_;
+ v8::Persistent<v8::Value> selectedNPNProto_;
+#endif
+
protected:
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> EncIn(const v8::Arguments& args);
static v8::Handle<v8::Value> Start(const v8::Arguments& args);
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
+#ifdef OPENSSL_NPN_NEGOTIATED
+ // NPN
+ static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
+ static v8::Handle<v8::Value> SetNPNProtocols(const v8::Arguments& args);
+ static int AdvertiseNextProtoCallback_(SSL *s,
+ const unsigned char **data,
+ unsigned int *len,
+ void *arg);
+ static int SelectNextProtoCallback_(SSL *s,
+ unsigned char **out, unsigned char *outlen,
+ const unsigned char* in,
+ unsigned int inlen, void *arg);
+#endif
+
int HandleBIOError(BIO *bio, const char* func, int rv);
int HandleSSLError(const char* func, int rv);
BIO *bio_read_;
BIO *bio_write_;
SSL *ssl_;
+
bool is_server_; /* coverity[member_decl] */
};
--- /dev/null
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
+
+if (!process.versions.openssl || !NPN_ENABLED) {
+ console.error("Skipping because node compiled without OpenSSL or " +
+ "with old OpenSSL version.");
+ process.exit(0);
+}
+
+var common = require('../common'),
+ assert = require('assert'),
+ fs = require('fs'),
+ tls = require('tls');
+
+function filenamePEM(n) {
+ return require('path').join(common.fixturesDir, 'keys', n + ".pem");
+}
+
+function loadPEM(n) {
+ return fs.readFileSync(filenamePEM(n));
+}
+
+var serverOptions = {
+ key: loadPEM('agent2-key'),
+ cert: loadPEM('agent2-cert'),
+ crl: loadPEM('ca2-crl'),
+ NPNProtocols: ['a', 'b', 'c']
+};
+
+var clientsOptions = [{
+ key: serverOptions.key,
+ cert: serverOptions.cert,
+ crl: serverOptions.crl,
+ NPNProtocols: ['a', 'b', 'c']
+},{
+ key: serverOptions.key,
+ cert: serverOptions.cert,
+ crl: serverOptions.crl,
+ NPNProtocols: ['c', 'b', 'e']
+},{
+ key: serverOptions.key,
+ cert: serverOptions.cert,
+ crl: serverOptions.crl,
+ NPNProtocols: ['first-priority-unsupported', 'x', 'y']
+}];
+
+var serverPort = common.PORT;
+
+var serverResults = [],
+ clientsResults = [];
+
+var server = tls.createServer(serverOptions, function(c) {
+ serverResults.push(c.npnProtocol);
+});
+server.listen(serverPort, startTest);
+
+function startTest() {
+ function connectClient(options, callback) {
+ var client = tls.connect(serverPort, 'localhost', options, function() {
+ clientsResults.push(client.npnProtocol);
+ client.destroy();
+
+ callback();
+ });
+ };
+
+ connectClient(clientsOptions[0], function() {
+ connectClient(clientsOptions[1], function() {
+ connectClient(clientsOptions[2], function() {
+ server.close();
+ });
+ });
+ });
+};
+
+process.on('exit', function() {
+ assert.equal(serverResults[0], clientsResults[0]);
+ assert.equal(serverResults[1], clientsResults[1]);
+ assert.equal(serverResults[2], 'first-priority-unsupported');
+ assert.equal(clientsResults[2], false);
+});