Browse Source

Initial TLS support

v0.7.4-release
Rhys Jones 15 years ago
committed by Ryan Dahl
parent
commit
b6dda61249
  1. 50
      doc/api.txt
  2. 12
      lib/tcp.js
  3. 432
      src/node_net.cc
  4. 63
      src/node_net.h
  5. 20
      test/mjsunit/fixtures/test_ca.pem
  6. 20
      test/mjsunit/fixtures/test_cert.pem
  7. 15
      test/mjsunit/fixtures/test_key.pem
  8. 2
      test/mjsunit/test-readdir.js
  9. 122
      test/mjsunit/test-tcp-tls.js
  10. 19
      wscript

50
doc/api.txt

@ -741,6 +741,16 @@ options argument for +tcp.Server+ does.
The +request_listener+ is a function which is automatically The +request_listener+ is a function which is automatically
added to the +"request"+ event. added to the +"request"+ event.
+server.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ ::
Enable TLS for all incoming connections, with the specified credentials.
+
format_type currently has to be "X509_PEM", and each of the ca, crl, key and
cert parameters are in the format of PEM strings.
+
The ca_certs is a string that holds a number of CA certificates for use in accepting
client connections that authenticate themselves with a client certificate.
The private_key is a PEM string of the unencrypted key for the server.
+server.listen(port, hostname)+ :: +server.listen(port, hostname)+ ::
Begin accepting connections on the specified port and hostname. Begin accepting connections on the specified port and hostname.
If the hostname is omitted, the server will accept connections If the hostname is omitted, the server will accept connections
@ -927,6 +937,17 @@ the response. (This sounds convoluted but it provides a chance
for the user to stream a body to the server with for the user to stream a body to the server with
+request.sendBody()+.) +request.sendBody()+.)
+client.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ ::
Enable TLS for the client connection, with the specified credentials.
+
format_type currently has to be "X509_PEM", and each of the ca, crl, key and
cert parameters are in the format of PEM strings, and optional.
+
The ca_certs is a string that holds a number of CA certificates for use in deciding the
authenticity of the remote server. The private_key is a PEM string of the unencrypted
key for the client, which together with the certificate allows the client to authenticate
itself to the server.
==== +http.ClientRequest+ ==== +http.ClientRequest+
@ -1160,6 +1181,15 @@ Creates a new TCP server.
The +connection_listener+ argument is automatically set as a listener for The +connection_listener+ argument is automatically set as a listener for
the +"connection"+ event. the +"connection"+ event.
+server.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ ::
Enable TLS for all incoming connections, with the specified credentials.
+
format_type currently has to be "X509_PEM", and each of the ca, crl, key and
cert parameters are in the format of PEM strings.
+
The ca_certs is a string that holds a number of CA certificates for use in accepting
client connections that authenticate themselves with a client certificate.
The private_key is a PEM string of the unencrypted key for the server.
+server.listen(port, host=null, backlog=128)+ :: +server.listen(port, host=null, backlog=128)+ ::
Tells the server to listen for TCP connections to +port+ and +host+. Tells the server to listen for TCP connections to +port+ and +host+.
@ -1173,7 +1203,6 @@ connections for the server may grow.
+ +
This function is synchronous. This function is synchronous.
+server.close()+:: +server.close()+::
Stops the server from accepting new connections. This function is Stops the server from accepting new connections. This function is
asynchronous, the server is finally closed when the server emits a +"close"+ asynchronous, the server is finally closed when the server emits a +"close"+
@ -1279,6 +1308,25 @@ Disables the Nagle algorithm. By default TCP connections use the Nagle
algorithm, they buffer data before sending it off. Setting +noDelay+ will algorithm, they buffer data before sending it off. Setting +noDelay+ will
immediately fire off data each time +connection.send()+ is called. immediately fire off data each time +connection.send()+ is called.
+connection.verifyPeer()+::
Returns an integer indicating the trusted status of the peer in a TLS
connection.
+
Returns 1 if the peer's certificate is issued by one of the trusted CAs,
the certificate has not been revoked, is in the issued date range,
and if the peer is the server, matches the hostname.
+
Returns 0 if no certificate was presented by the peer, or negative result
if the verification fails (with a given reason code). This function is synchronous.
+connection.getPeerCertificate(format)+::
For a TLS connection, returns the peer's certificate information, as defined
by the given format.
+
A format of "DNstring" gives a single string with the combined Distinguished
Name (DN) from the certificate, as comma delimited name=value pairs as defined
in RFC2253. This function is synchronous.
=== DNS module === DNS module
Use +require("dns")+ to access this module Use +require("dns")+ to access this module

12
lib/tcp.js

@ -1,3 +1,15 @@
var TLS_STATUS_CODES = {
1 : 'JS_GNUTLS_CERT_VALIDATED',
0 : 'JS_GNUTLS_CERT_UNDEFINED',
}
TLS_STATUS_CODES[-100] = 'JS_GNUTLS_CERT_SIGNER_NOT_FOUND';
TLS_STATUS_CODES[-101] = 'JS_GNUTLS_CERT_SIGNER_NOT_CA';
TLS_STATUS_CODES[-102] = 'JS_GNUTLS_CERT_INVALID';
TLS_STATUS_CODES[-103] = 'JS_GNUTLS_CERT_NOT_ACTIVATED';
TLS_STATUS_CODES[-104] = 'JS_GNUTLS_CERT_EXPIRED';
TLS_STATUS_CODES[-105] = 'JS_GNUTLS_CERT_REVOKED';
TLS_STATUS_CODES[-106] = 'JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME';
exports.createServer = function (on_connection, options) { exports.createServer = function (on_connection, options) {
var server = new process.tcp.Server(); var server = new process.tcp.Server();
server.addListener("connection", on_connection); server.addListener("connection", on_connection);

432
src/node_net.cc

@ -72,6 +72,13 @@ void Connection::Initialize(v8::Handle<v8::Object> target) {
NODE_SET_PROTOTYPE_METHOD(constructor_template, "readResume", ReadResume); NODE_SET_PROTOTYPE_METHOD(constructor_template, "readResume", ReadResume);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout); NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setNoDelay", SetNoDelay); NODE_SET_PROTOTYPE_METHOD(constructor_template, "setNoDelay", SetNoDelay);
#if EVCOM_HAVE_GNUTLS
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setSecure", SetSecure);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "verifyPeer", VerifyPeer);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "getPeerCertificate",
GetPeerCertificate);
gnutls_global_init();
#endif
// Getter for connection.readyState // Getter for connection.readyState
constructor_template->PrototypeTemplate()->SetAccessor( constructor_template->PrototypeTemplate()->SetAccessor(
@ -139,6 +146,7 @@ Handle<Value> Connection::FDGetter(Local<String> property,
// reinitialized without destroying the object. // reinitialized without destroying the object.
void Connection::Init() { void Connection::Init() {
resolving_ = false; resolving_ = false;
secure_ = false;
evcom_stream_init(&stream_); evcom_stream_init(&stream_);
stream_.on_connect = Connection::on_connect; stream_.on_connect = Connection::on_connect;
stream_.on_read = Connection::on_read; stream_.on_read = Connection::on_read;
@ -251,9 +259,6 @@ int Connection::Resolve(eio_req *req) {
&client_tcp_hints, &address); &client_tcp_hints, &address);
req->ptr2 = address; req->ptr2 = address;
free(connection->host_);
connection->host_ = NULL;
free(connection->port_); free(connection->port_);
connection->port_ = NULL; connection->port_ = NULL;
@ -341,6 +346,141 @@ Handle<Value> Connection::SetEncoding(const Arguments& args) {
String::New("Could not parse encoding. This is a Node bug."))); String::New("Could not parse encoding. This is a Node bug.")));
} }
#if EVCOM_HAVE_GNUTLS
Handle<Value> Connection::SetSecure(const Arguments& args) {
HandleScope scope;
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This());
assert(connection);
int r;
connection->secure_ = true;
// Create credentials
gnutls_certificate_allocate_credentials(&connection->credentials);
if (args[1]->IsString()) {
String::Utf8Value caString(args[1]->ToString());
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*caString)
, caString.length()
};
r = gnutls_certificate_set_x509_trust_mem(connection->credentials,
&datum, GNUTLS_X509_FMT_PEM);
}
if (args[2]->IsString()) {
String::Utf8Value crlString(args[2]->ToString());
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*crlString)
, crlString.length()
};
r = gnutls_certificate_set_x509_crl_mem(connection->credentials,
&datum, GNUTLS_X509_FMT_PEM);
}
if (args[3]->IsString() && args[4]->IsString()) {
String::Utf8Value keyString(args[3]->ToString());
String::Utf8Value certString(args[4]->ToString());
gnutls_datum_t datum_key = { reinterpret_cast<unsigned char*>(*keyString)
, keyString.length()
};
gnutls_datum_t datum_cert = { reinterpret_cast<unsigned char*>(*certString)
, certString.length()
};
r = gnutls_certificate_set_x509_key_mem(connection->credentials,
&datum_cert, &datum_key,
GNUTLS_X509_FMT_PEM);
}
// Create the session object
init_tls_session(&connection->stream_,
connection->credentials,
GNUTLS_CLIENT);
return Undefined();
}
Handle<Value> Connection::VerifyPeer(const Arguments& args) {
HandleScope scope;
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This());
assert(connection);
const gnutls_datum_t * cert_chain;
uint cert_chain_length;
gnutls_x509_crl_t *crl_list;
uint crl_list_size;
gnutls_x509_crt_t *ca_list;
uint ca_list_size;
int r;
if (!connection->secure_) {
return Undefined();
}
cert_chain = gnutls_certificate_get_peers(connection->stream_.session,
&cert_chain_length);
gnutls_certificate_get_x509_crls(connection->credentials,
&crl_list,
&crl_list_size);
gnutls_certificate_get_x509_cas(connection->credentials,
&ca_list,
&ca_list_size);
r = verify_certificate_chain(connection->stream_.session,
connection->host_,
cert_chain,
cert_chain_length,
crl_list,
crl_list_size,
ca_list,
ca_list_size);
return scope.Close(Integer::New(r));
}
Handle<Value> Connection::GetPeerCertificate(const Arguments& args) {
HandleScope scope;
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This());
assert(connection);
if (!connection->secure_) {
return Undefined();
}
const gnutls_datum_t * cert_chain;
uint cert_chain_length;
char *name;
size_t name_size;
gnutls_x509_crt_t cert;
cert_chain = gnutls_certificate_get_peers(connection->stream_.session,
&cert_chain_length);
if ( (cert_chain_length == 0) || (cert_chain == NULL) ) {
return Undefined();
}
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, &cert_chain[0], GNUTLS_X509_FMT_DER);
gnutls_x509_crt_get_dn(cert, NULL, &name_size);
name = (char *)malloc(name_size);
gnutls_x509_crt_get_dn(cert, name, &name_size);
Local<String> dnString = String::New(name);
free(name);
gnutls_x509_crt_deinit(cert);
return scope.Close(dnString);
}
#endif
Handle<Value> Connection::ReadPause(const Arguments& args) { Handle<Value> Connection::ReadPause(const Arguments& args) {
HandleScope scope; HandleScope scope;
@ -382,6 +522,12 @@ Handle<Value> Connection::Close(const Arguments& args) {
assert(connection); assert(connection);
connection->Close(); connection->Close();
if (connection->host_ != NULL) {
free(connection->host_);
connection->host_ = NULL;
}
return Undefined(); return Undefined();
} }
@ -453,6 +599,19 @@ void Connection::OnClose() {
}; };
Emit("close", 2, argv); Emit("close", 2, argv);
#if EVCOM_HAVE_GNUTLS
if (secure_) {
if (stream_.session) {
gnutls_deinit(stream_.session);
stream_.session = NULL;
}
if (!stream_.server && credentials) {
gnutls_certificate_free_credentials(credentials);
credentials = NULL;
}
}
#endif
} }
void Connection::OnConnect() { void Connection::OnConnect() {
@ -495,6 +654,9 @@ void Server::Initialize(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(constructor_template, "listen", Listen); NODE_SET_PROTOTYPE_METHOD(constructor_template, "listen", Listen);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close); NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close);
#if EVCOM_HAVE_GNUTLS
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setSecure", SetSecure);
#endif
target->Set(String::NewSymbol("Server"), constructor_template->GetFunction()); target->Set(String::NewSymbol("Server"), constructor_template->GetFunction());
} }
@ -550,6 +712,16 @@ Connection* Server::OnConnection(struct sockaddr *addr) {
Connection *connection = UnwrapConnection(js_connection); Connection *connection = UnwrapConnection(js_connection);
if (!connection) return NULL; if (!connection) return NULL;
#if EVCOM_HAVE_GNUTLS
if (secure_) {
connection->secure_ = true;
connection->credentials = credentials;
init_tls_session(&connection->stream_,
connection->credentials,
GNUTLS_SERVER);
}
#endif
connection->Attach(); connection->Attach();
return connection; return connection;
@ -568,7 +740,6 @@ Handle<Value> Server::New(const Arguments& args) {
Server *server = new Server(); Server *server = new Server();
server->Wrap(args.This()); server->Wrap(args.This());
return args.This(); return args.This();
} }
@ -635,6 +806,56 @@ Handle<Value> Server::Listen(const Arguments& args) {
return Undefined(); return Undefined();
} }
#if EVCOM_HAVE_GNUTLS
Handle<Value> Server::SetSecure(const Arguments& args) {
Server *server = ObjectWrap::Unwrap<Server>(args.Holder());
assert(server);
int r;
server->secure_ = true;
gnutls_certificate_allocate_credentials(&server->credentials);
if (args[1]->IsString()) {
String::Utf8Value caString(args[1]->ToString());
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*caString)
, caString.length()
};
r = gnutls_certificate_set_x509_trust_mem(server->credentials,
&datum, GNUTLS_X509_FMT_PEM);
}
if (args[2]->IsString()) {
String::Utf8Value crlString(args[2]->ToString());
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*crlString)
, crlString.length()
};
r = gnutls_certificate_set_x509_crl_mem(server->credentials,
&datum, GNUTLS_X509_FMT_PEM);
}
if (args[3]->IsString() && args[4]->IsString()) {
String::Utf8Value keyString(args[3]->ToString());
String::Utf8Value certString(args[4]->ToString());
gnutls_datum_t datum_key = { reinterpret_cast<unsigned char*>(*keyString)
, keyString.length()
};
gnutls_datum_t datum_cert = { reinterpret_cast<unsigned char*>(*certString)
, certString.length()
};
r = gnutls_certificate_set_x509_key_mem(server->credentials,
&datum_cert, &datum_key,
GNUTLS_X509_FMT_PEM);
}
return Undefined();
}
#endif
Handle<Value> Server::Close(const Arguments& args) { Handle<Value> Server::Close(const Arguments& args) {
Server *server = ObjectWrap::Unwrap<Server>(args.Holder()); Server *server = ObjectWrap::Unwrap<Server>(args.Holder());
assert(server); assert(server);
@ -644,3 +865,206 @@ Handle<Value> Server::Close(const Arguments& args) {
} }
} // namespace node } // namespace node
#if EVCOM_HAVE_GNUTLS
void init_tls_session(evcom_stream* stream_,
gnutls_certificate_credentials_t credentials,
gnutls_connection_end_t session_type) {
gnutls_init(&stream_->session,
session_type);
if (session_type == GNUTLS_SERVER) {
gnutls_certificate_server_set_request(stream_->session,
GNUTLS_CERT_REQUEST);
}
gnutls_set_default_priority(stream_->session);
const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
const int proto_type_priority[] = { GNUTLS_TLS1_0,
GNUTLS_TLS1_1,
GNUTLS_SSL3,
0};
gnutls_certificate_type_set_priority(stream_->session,
cert_type_priority);
gnutls_protocol_set_priority(stream_->session,
proto_type_priority);
gnutls_credentials_set(stream_->session,
GNUTLS_CRD_CERTIFICATE,
credentials);
evcom_stream_set_secure_session(stream_,
stream_->session);
}
/* This function will try to verify the peer's certificate chain, and
* also check if the hostname matches, and the activation, expiration dates.
*/
int verify_certificate_chain(gnutls_session_t session,
const char *hostname,
const gnutls_datum_t * cert_chain,
int cert_chain_length,
gnutls_x509_crl_t *crl_list,
int crl_list_size,
gnutls_x509_crt_t *ca_list,
int ca_list_size) {
int r = 0;
int i;
int ss = 0;
gnutls_x509_crt_t *cert;
if ((cert_chain_length == 0) || (cert_chain == NULL)) {
return JS_GNUTLS_CERT_UNDEFINED;
}
cert = (gnutls_x509_crt_t *)malloc(sizeof(*cert) * cert_chain_length);
/* Import all the certificates in the chain to
* native certificate format.
*/
for (i = 0; i < cert_chain_length; i++) {
gnutls_x509_crt_init(&cert[i]);
gnutls_x509_crt_import(cert[i], &cert_chain[i], GNUTLS_X509_FMT_DER);
}
/* If the last certificate in the chain is self signed ignore it.
* That is because we want to check against our trusted certificate
* list.
*/
if (gnutls_x509_crt_check_issuer(cert[cert_chain_length - 1],
cert[cert_chain_length - 1]) > 0
&& cert_chain_length > 0) {
cert_chain_length--;
ss = 1;
}
/* Now verify the certificates against their issuers
* in the chain.
*/
for (i = 1; i < cert_chain_length; i++) {
r = verify_cert2(cert[i - 1], cert[i], crl_list, crl_list_size);
if (r < 0) goto out;
}
/* Here we must verify the last certificate in the chain against
* our trusted CA list.
*/
if (cert_chain_length>0) {
r = verify_last_cert(cert[cert_chain_length - 1], ca_list, ca_list_size,
crl_list, crl_list_size);
if (r < 0) goto out;
} else {
r = verify_last_cert(cert[0], ca_list, ca_list_size,
crl_list, crl_list_size);
if (r < 0) goto out;
}
/* Check if the name in the first certificate matches our destination!
*/
if (hostname != NULL) {
if (!gnutls_x509_crt_check_hostname(cert[0], hostname)) {
r = JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME;
}
}
out:
for (i = 0; i < cert_chain_length+ss; i++) {
gnutls_x509_crt_deinit(cert[i]);
}
return r;
}
/* Verifies a certificate against an other certificate
* which is supposed to be it's issuer. Also checks the
* crl_list if the certificate is revoked.
*/
int verify_cert2(gnutls_x509_crt_t crt,
gnutls_x509_crt_t issuer,
gnutls_x509_crl_t * crl_list,
int crl_list_size) {
unsigned int output;
int ret;
time_t now = time(0);
gnutls_x509_crt_verify(crt, &issuer, 1, 0, &output);
if (output & GNUTLS_CERT_INVALID) {
if (output & GNUTLS_CERT_SIGNER_NOT_FOUND) {
return JS_GNUTLS_CERT_SIGNER_NOT_FOUND;
}
if (output & GNUTLS_CERT_SIGNER_NOT_CA) {
return JS_GNUTLS_CERT_SIGNER_NOT_CA;
}
return JS_GNUTLS_CERT_SIGNER_NOT_CA;
}
/* Now check the expiration dates.
*/
if (gnutls_x509_crt_get_activation_time(crt) > now) {
return JS_GNUTLS_CERT_NOT_ACTIVATED;
}
if (gnutls_x509_crt_get_expiration_time(crt) < now) {
return JS_GNUTLS_CERT_EXPIRED;
}
/* Check if the certificate is revoked.
*/
ret = gnutls_x509_crt_check_revocation(crt, crl_list, crl_list_size);
if (ret == 1) {
return JS_GNUTLS_CERT_REVOKED;
}
return JS_GNUTLS_CERT_VALIDATED;
}
/* Verifies a certificate against our trusted CA list.
* Also checks the crl_list if the certificate is revoked.
*/
int verify_last_cert(gnutls_x509_crt_t crt,
gnutls_x509_crt_t * ca_list,
int ca_list_size,
gnutls_x509_crl_t * crl_list,
int crl_list_size) {
unsigned int output;
int ret;
time_t now = time(0);
gnutls_x509_crt_verify(crt, ca_list, ca_list_size,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &output);
if (output & GNUTLS_CERT_INVALID) {
if (output & GNUTLS_CERT_SIGNER_NOT_CA) {
return JS_GNUTLS_CERT_SIGNER_NOT_CA;
}
return JS_GNUTLS_CERT_INVALID;
}
/* Now check the expiration dates.
*/
if (gnutls_x509_crt_get_activation_time(crt) > now) {
return JS_GNUTLS_CERT_NOT_ACTIVATED;
}
if (gnutls_x509_crt_get_expiration_time(crt) < now) {
return JS_GNUTLS_CERT_EXPIRED;
}
/* Check if the certificate is revoked.
*/
ret = gnutls_x509_crt_check_revocation(crt, crl_list, crl_list_size);
if (ret == 1) {
return JS_GNUTLS_CERT_REVOKED;
}
return JS_GNUTLS_CERT_VALIDATED;
}
#endif // EVCOM_HAVE_GNUTLS

63
src/node_net.h

@ -7,6 +7,12 @@
#include <v8.h> #include <v8.h>
#include <evcom.h> #include <evcom.h>
#if EVCOM_HAVE_GNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#endif
namespace node { namespace node {
class Server; class Server;
@ -35,6 +41,12 @@ class Connection : public EventEmitter {
static v8::Handle<v8::Value> FDGetter(v8::Local<v8::String> _, static v8::Handle<v8::Value> FDGetter(v8::Local<v8::String> _,
const v8::AccessorInfo& info); const v8::AccessorInfo& info);
#if EVCOM_HAVE_GNUTLS
static v8::Handle<v8::Value> SetSecure(const v8::Arguments& args);
static v8::Handle<v8::Value> VerifyPeer(const v8::Arguments& args);
static v8::Handle<v8::Value> GetPeerCertificate(const v8::Arguments& args);
#endif
Connection() : EventEmitter() { Connection() : EventEmitter() {
encoding_ = BINARY; encoding_ = BINARY;
@ -92,6 +104,10 @@ class Connection : public EventEmitter {
enum encoding encoding_; enum encoding encoding_;
bool resolving_; bool resolving_;
bool secure_;
#if EVCOM_HAVE_GNUTLS
gnutls_certificate_credentials_t credentials;
#endif
private: private:
@ -155,12 +171,16 @@ class Server : public EventEmitter {
static v8::Handle<v8::Value> New(const v8::Arguments& args); static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> Listen(const v8::Arguments& args); static v8::Handle<v8::Value> Listen(const v8::Arguments& args);
static v8::Handle<v8::Value> Close(const v8::Arguments& args); static v8::Handle<v8::Value> Close(const v8::Arguments& args);
#if EVCOM_HAVE_GNUTLS
static v8::Handle<v8::Value> SetSecure(const v8::Arguments& args);
#endif
Server() : EventEmitter() { Server() : EventEmitter() {
evcom_server_init(&server_); evcom_server_init(&server_);
server_.on_connection = Server::on_connection; server_.on_connection = Server::on_connection;
server_.on_close = Server::on_close; server_.on_close = Server::on_close;
server_.data = this; server_.data = this;
secure_ = false;
} }
virtual ~Server() { virtual ~Server() {
@ -201,7 +221,50 @@ class Server : public EventEmitter {
} }
evcom_server server_; evcom_server server_;
#if EVCOM_HAVE_GNUTLS
gnutls_certificate_credentials_t credentials;
#endif
bool secure_;
}; };
} // namespace node } // namespace node
#endif // SRC_NET_H_ #endif // SRC_NET_H_
#if EVCOM_HAVE_GNUTLS
void init_tls_session(evcom_stream* stream_,
gnutls_certificate_credentials_t credentials,
gnutls_connection_end_t session_type);
int verify_certificate_chain(gnutls_session_t session,
const char *hostname,
const gnutls_datum_t * cert_chain,
int cert_chain_length,
gnutls_x509_crl_t *crl_list,
int crl_list_size,
gnutls_x509_crt_t *ca_list,
int ca_list_size);
int verify_cert2(gnutls_x509_crt_t crt,
gnutls_x509_crt_t issuer,
gnutls_x509_crl_t * crl_list,
int crl_list_size);
int verify_last_cert(gnutls_x509_crt_t crt,
gnutls_x509_crt_t * ca_list,
int ca_list_size,
gnutls_x509_crl_t * crl_list,
int crl_list_size);
#define JS_GNUTLS_CERT_VALIDATED 1
#define JS_GNUTLS_CERT_UNDEFINED 0
#define JS_GNUTLS_CERT_SIGNER_NOT_FOUND -100
#define JS_GNUTLS_CERT_SIGNER_NOT_CA -101
#define JS_GNUTLS_CERT_INVALID -102
#define JS_GNUTLS_CERT_NOT_ACTIVATED -103
#define JS_GNUTLS_CERT_EXPIRED -104
#define JS_GNUTLS_CERT_REVOKED -105
#define JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME -106
#endif

20
test/mjsunit/fixtures/test_ca.pem

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDXDCCAsWgAwIBAgIJAKL0UG+mRkSPMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
BAYTAlVLMRQwEgYDVQQIEwtBY2tuYWNrIEx0ZDETMBEGA1UEBxMKUmh5cyBKb25l
czEQMA4GA1UEChMHbm9kZS5qczEdMBsGA1UECxMUVGVzdCBUTFMgQ2VydGlmaWNh
dGUxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0wOTExMTEwOTUyMjJaFw0yOTExMDYw
OTUyMjJaMH0xCzAJBgNVBAYTAlVLMRQwEgYDVQQIEwtBY2tuYWNrIEx0ZDETMBEG
A1UEBxMKUmh5cyBKb25lczEQMA4GA1UEChMHbm9kZS5qczEdMBsGA1UECxMUVGVz
dCBUTFMgQ2VydGlmaWNhdGUxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEA8d8Hc6atq78Jt1HLp9agA/wpQfsFvkYUdZ1YsdvO
kL2janjwHQgMMCy/Njal3FUEW0OLPebKZUJ8L44JBXSlVxU4zyiiSOWld8EkTetR
AVT3WKQq3ud+cnxv7g8rGRQp1UHZwmdbZ1wEfAYq8QjYx6m1ciMgRo7DaDQhD29k
d+UCAwEAAaOB4zCB4DAdBgNVHQ4EFgQUL9miTJn+HKNuTmx/oMWlZP9cd4QwgbAG
A1UdIwSBqDCBpYAUL9miTJn+HKNuTmx/oMWlZP9cd4ShgYGkfzB9MQswCQYDVQQG
EwJVSzEUMBIGA1UECBMLQWNrbmFjayBMdGQxEzARBgNVBAcTClJoeXMgSm9uZXMx
EDAOBgNVBAoTB25vZGUuanMxHTAbBgNVBAsTFFRlc3QgVExTIENlcnRpZmljYXRl
MRIwEAYDVQQDEwlsb2NhbGhvc3SCCQCi9FBvpkZEjzAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4GBADRXXA2xSUK5W1i3oLYWW6NEDVWkTQ9RveplyeS9MOkP
e7yPcpz0+O0ZDDrxR9chAiZ7fmdBBX1Tr+pIuCrG/Ud49SBqeS5aMJGVwiSd7o1n
dhU2Sz3Q60DwJEL1VenQHiVYlWWtqXBThe9ggqRPnCfsCRTP8qifKkjk45zWPcpN
-----END CERTIFICATE-----

20
test/mjsunit/fixtures/test_cert.pem

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDXDCCAsWgAwIBAgIJAKL0UG+mRkSPMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
BAYTAlVLMRQwEgYDVQQIEwtBY2tuYWNrIEx0ZDETMBEGA1UEBxMKUmh5cyBKb25l
czEQMA4GA1UEChMHbm9kZS5qczEdMBsGA1UECxMUVGVzdCBUTFMgQ2VydGlmaWNh
dGUxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0wOTExMTEwOTUyMjJaFw0yOTExMDYw
OTUyMjJaMH0xCzAJBgNVBAYTAlVLMRQwEgYDVQQIEwtBY2tuYWNrIEx0ZDETMBEG
A1UEBxMKUmh5cyBKb25lczEQMA4GA1UEChMHbm9kZS5qczEdMBsGA1UECxMUVGVz
dCBUTFMgQ2VydGlmaWNhdGUxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEA8d8Hc6atq78Jt1HLp9agA/wpQfsFvkYUdZ1YsdvO
kL2janjwHQgMMCy/Njal3FUEW0OLPebKZUJ8L44JBXSlVxU4zyiiSOWld8EkTetR
AVT3WKQq3ud+cnxv7g8rGRQp1UHZwmdbZ1wEfAYq8QjYx6m1ciMgRo7DaDQhD29k
d+UCAwEAAaOB4zCB4DAdBgNVHQ4EFgQUL9miTJn+HKNuTmx/oMWlZP9cd4QwgbAG
A1UdIwSBqDCBpYAUL9miTJn+HKNuTmx/oMWlZP9cd4ShgYGkfzB9MQswCQYDVQQG
EwJVSzEUMBIGA1UECBMLQWNrbmFjayBMdGQxEzARBgNVBAcTClJoeXMgSm9uZXMx
EDAOBgNVBAoTB25vZGUuanMxHTAbBgNVBAsTFFRlc3QgVExTIENlcnRpZmljYXRl
MRIwEAYDVQQDEwlsb2NhbGhvc3SCCQCi9FBvpkZEjzAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4GBADRXXA2xSUK5W1i3oLYWW6NEDVWkTQ9RveplyeS9MOkP
e7yPcpz0+O0ZDDrxR9chAiZ7fmdBBX1Tr+pIuCrG/Ud49SBqeS5aMJGVwiSd7o1n
dhU2Sz3Q60DwJEL1VenQHiVYlWWtqXBThe9ggqRPnCfsCRTP8qifKkjk45zWPcpN
-----END CERTIFICATE-----

15
test/mjsunit/fixtures/test_key.pem

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDx3wdzpq2rvwm3Ucun1qAD/ClB+wW+RhR1nVix286QvaNqePAd
CAwwLL82NqXcVQRbQ4s95splQnwvjgkFdKVXFTjPKKJI5aV3wSRN61EBVPdYpCre
535yfG/uDysZFCnVQdnCZ1tnXAR8BirxCNjHqbVyIyBGjsNoNCEPb2R35QIDAQAB
AoGBAJNem9C4ftrFNGtQ2DB0Udz7uDuucepkErUy4MbFsc947GfENjDKJXr42Kx0
kYx09ImS1vUpeKpH3xiuhwqe7tm4FsCBg4TYqQle14oxxm7TNeBwwGC3OB7hiokb
aAjbPZ1hAuNs6ms3Ybvvj6Lmxzx42m8O5DXCG2/f+KMvaNUhAkEA/ekrOsWkNoW9
2n3m+msdVuxeek4B87EoTOtzCXb1dybIZUVv4J48VAiM43hhZHWZck2boD/hhwjC
M5NWd4oY6QJBAPPcgBVNdNZSZ8hR4ogI4nzwWrQhl9MRbqqtfOn2TK/tjMv10ALg
lPmn3SaPSNRPKD2hoLbFuHFERlcS79pbCZ0CQQChX3PuIna/gDitiJ8oQLOg7xEM
wk9TRiDK4kl2lnhjhe6PDpaQN4E4F0cTuwqLAoLHtrNWIcOAQvzKMrYdu1MhAkBm
Et3qDMnjDAs05lGT72QeN90/mPAcASf5eTTYGahv21cb6IBxM+AnwAPpqAAsHhYR
9h13Y7uYbaOjvuF23LRhAkBoI9eaSMn+l81WXOVUHnzh3ZwB4GuTyxMXXNOhuiFd
0z4LKAMh99Z4xQmqSoEkXsfM4KPpfhYjF/bwIcP5gOei
-----END RSA PRIVATE KEY-----

2
test/mjsunit/test-readdir.js

@ -7,7 +7,7 @@ puts("readdir " + fixturesDir);
promise.addCallback(function (files) { promise.addCallback(function (files) {
p(files); p(files);
assertArrayEquals(["a.js", "b", "multipart.js", "x.txt"], files.sort()); assertArrayEquals(["a.js", "b", "multipart.js", "test_ca.pem", "test_cert.pem", "test_key.pem", "x.txt"], files.sort());
}); });
promise.addErrback(function () { promise.addErrback(function () {

122
test/mjsunit/test-tcp-tls.js

@ -0,0 +1,122 @@
process.mixin(require("./common"));
tcp = require("tcp");
posix=require("posix");
var tests_run = 0;
function tlsTest (port, host, caPem, keyPem, certPem) {
var N = 50;
var count = 0;
var sent_final_ping = false;
var server = tcp.createServer(function (socket) {
assertTrue(socket.remoteAddress !== null);
assertTrue(socket.remoteAddress !== undefined);
if (host === "127.0.0.1")
assertEquals(socket.remoteAddress, "127.0.0.1");
else if (host == null)
assertEquals(socket.remoteAddress, "127.0.0.1");
socket.setEncoding("utf8");
socket.setNoDelay();
socket.timeout = 0;
socket.addListener("receive", function (data) {
var verified = socket.verifyPeer();
var peerDN = socket.getPeerCertificate("DNstring");
assertEquals(verified, 1);
assertEquals(peerDN, "C=UK,ST=Acknack Ltd,L=Rhys Jones,O=node.js,"
+ "OU=Test TLS Certificate,CN=localhost");
puts("server got: " + JSON.stringify(data));
assertEquals("open", socket.readyState);
assertTrue(count <= N);
if (/PING/.exec(data)) {
socket.send("PONG");
}
});
socket.addListener("eof", function () {
assertEquals("writeOnly", socket.readyState);
socket.close();
});
socket.addListener("close", function (had_error) {
assertFalse(had_error);
assertEquals("closed", socket.readyState);
socket.server.close();
});
});
server.setSecure('X509_PEM', caPem, 0, keyPem, certPem);
server.listen(port, host);
var client = tcp.createConnection(port, host);
client.setEncoding("utf8");
client.setSecure('X509_PEM', caPem, 0, keyPem, caPem);
client.addListener("connect", function () {
assertEquals("open", client.readyState);
var verified = client.verifyPeer();
var peerDN = client.getPeerCertificate("DNstring");
assertEquals(verified, 1);
assertEquals(peerDN, "C=UK,ST=Acknack Ltd,L=Rhys Jones,O=node.js,"
+ "OU=Test TLS Certificate,CN=localhost");
client.send("PING");
});
client.addListener("receive", function (data) {
assertEquals("PONG", data);
count += 1;
puts("client got PONG");
if (sent_final_ping) {
assertEquals("readOnly", client.readyState);
return;
} else {
assertEquals("open", client.readyState);
}
if (count < N) {
client.send("PING");
} else {
sent_final_ping = true;
client.send("PING");
client.close();
}
});
client.addListener("close", function () {
assertEquals(N+1, count);
assertTrue(sent_final_ping);
tests_run += 1;
});
}
var have_tls;
try {
var dummy_server = tcp.createServer();
dummy_server.setSecure();
have_tls=true;
} catch (e) {
have_tls=false;
}
if (have_tls) {
var caPem = posix.cat(fixturesDir+"/test_ca.pem").wait();
var certPem = posix.cat(fixturesDir+"/test_cert.pem").wait();
var keyPem = posix.cat(fixturesDir+"/test_key.pem").wait();
/* All are run at once, so run on different ports */
tlsTest(20443, "localhost", caPem, keyPem, certPem);
tlsTest(21443, null, caPem, keyPem, certPem);
process.addListener("exit", function () {
assertEquals(2, tests_run);
});
} else {
puts("Not compiled with TLS support.");
process.exit(1);
}

19
wscript

@ -119,15 +119,21 @@ def configure(conf):
if sys.platform.startswith("freebsd"): if sys.platform.startswith("freebsd"):
fatal("Install the libexecinfo port from /usr/ports/devel/libexecinfo.") fatal("Install the libexecinfo port from /usr/ports/devel/libexecinfo.")
if conf.check_cfg(package='gnutls',
args='--cflags --libs',
#libpath=['/usr/lib', '/usr/local/lib'],
uselib_store='GNUTLS'):
if conf.check(lib='gpg-error',
#libpath=['/usr/lib', '/usr/local/lib'],
uselib_store='GPGERROR'):
conf.env.append_value("CCFLAGS", "-DEVCOM_HAVE_GNUTLS=1")
conf.env.append_value("CXXFLAGS", "-DEVCOM_HAVE_GNUTLS=1")
conf.sub_config('deps/libeio') conf.sub_config('deps/libeio')
conf.sub_config('deps/libev') conf.sub_config('deps/libev')
conf_subproject(conf, 'deps/udns', './configure') conf_subproject(conf, 'deps/udns', './configure')
# Not using TLS yet
# if conf.check_cfg(package='gnutls', args='--cflags --libs', uselib_store="GNUTLS"):
# conf.define("HAVE_GNUTLS", 1)
conf.define("HAVE_CONFIG_H", 1) conf.define("HAVE_CONFIG_H", 1)
conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (1024*64)) conf.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (1024*64))
@ -258,7 +264,7 @@ def build(bld):
evcom.includes = "deps/evcom/ deps/libev/" evcom.includes = "deps/evcom/ deps/libev/"
evcom.name = "evcom" evcom.name = "evcom"
evcom.target = "evcom" evcom.target = "evcom"
# evcom.uselib = "GNUTLS" evcom.uselib = "GPGERROR GNUTLS"
evcom.install_path = None evcom.install_path = None
if bld.env["USE_DEBUG"]: if bld.env["USE_DEBUG"]:
evcom.clone("debug") evcom.clone("debug")
@ -337,7 +343,8 @@ def build(bld):
""" """
node.add_objects = 'ev eio evcom http_parser coupling' node.add_objects = 'ev eio evcom http_parser coupling'
node.uselib_local = '' node.uselib_local = ''
node.uselib = 'UDNS V8 EXECINFO DL' node.uselib = 'UDNS V8 EXECINFO DL GPGERROR GNUTLS'
node.install_path = '${PREFIX}/lib' node.install_path = '${PREFIX}/lib'
node.install_path = '${PREFIX}/bin' node.install_path = '${PREFIX}/bin'
node.chmod = 0755 node.chmod = 0755

Loading…
Cancel
Save