From b6dda612495d7edb73250649ae53bdf3e7fc223e Mon Sep 17 00:00:00 2001 From: Rhys Jones Date: Sun, 22 Nov 2009 03:58:08 +0100 Subject: [PATCH] Initial TLS support --- doc/api.txt | 50 +++- lib/tcp.js | 12 + src/node_net.cc | 432 +++++++++++++++++++++++++++- src/node_net.h | 63 ++++ test/mjsunit/fixtures/test_ca.pem | 20 ++ test/mjsunit/fixtures/test_cert.pem | 20 ++ test/mjsunit/fixtures/test_key.pem | 15 + test/mjsunit/test-readdir.js | 2 +- test/mjsunit/test-tcp-tls.js | 122 ++++++++ wscript | 19 +- 10 files changed, 743 insertions(+), 12 deletions(-) create mode 100644 test/mjsunit/fixtures/test_ca.pem create mode 100644 test/mjsunit/fixtures/test_cert.pem create mode 100644 test/mjsunit/fixtures/test_key.pem create mode 100644 test/mjsunit/test-tcp-tls.js diff --git a/doc/api.txt b/doc/api.txt index c8880801cc..b3a163cf05 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -741,6 +741,16 @@ options argument for +tcp.Server+ does. The +request_listener+ is a function which is automatically 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)+ :: Begin accepting connections on the specified port and hostname. 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 +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+ @@ -1160,6 +1181,15 @@ Creates a new TCP server. The +connection_listener+ argument is automatically set as a listener for 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)+ :: 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. - +server.close()+:: Stops the server from accepting new connections. This function is 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 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 Use +require("dns")+ to access this module diff --git a/lib/tcp.js b/lib/tcp.js index 88afad1559..a01b04f107 100644 --- a/lib/tcp.js +++ b/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) { var server = new process.tcp.Server(); server.addListener("connection", on_connection); diff --git a/src/node_net.cc b/src/node_net.cc index a39af86228..9966bf4407 100644 --- a/src/node_net.cc +++ b/src/node_net.cc @@ -72,6 +72,13 @@ void Connection::Initialize(v8::Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "readResume", ReadResume); NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout); 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 constructor_template->PrototypeTemplate()->SetAccessor( @@ -139,6 +146,7 @@ Handle Connection::FDGetter(Local property, // reinitialized without destroying the object. void Connection::Init() { resolving_ = false; + secure_ = false; evcom_stream_init(&stream_); stream_.on_connect = Connection::on_connect; stream_.on_read = Connection::on_read; @@ -251,9 +259,6 @@ int Connection::Resolve(eio_req *req) { &client_tcp_hints, &address); req->ptr2 = address; - free(connection->host_); - connection->host_ = NULL; - free(connection->port_); connection->port_ = NULL; @@ -341,6 +346,141 @@ Handle Connection::SetEncoding(const Arguments& args) { String::New("Could not parse encoding. This is a Node bug."))); } +#if EVCOM_HAVE_GNUTLS + +Handle Connection::SetSecure(const Arguments& args) { + HandleScope scope; + + Connection *connection = ObjectWrap::Unwrap(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(*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(*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(*keyString) + , keyString.length() + }; + gnutls_datum_t datum_cert = { reinterpret_cast(*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 Connection::VerifyPeer(const Arguments& args) { + HandleScope scope; + + Connection *connection = ObjectWrap::Unwrap(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 Connection::GetPeerCertificate(const Arguments& args) { + HandleScope scope; + + Connection *connection = ObjectWrap::Unwrap(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 dnString = String::New(name); + free(name); + gnutls_x509_crt_deinit(cert); + return scope.Close(dnString); +} +#endif + Handle Connection::ReadPause(const Arguments& args) { HandleScope scope; @@ -382,6 +522,12 @@ Handle Connection::Close(const Arguments& args) { assert(connection); connection->Close(); + + if (connection->host_ != NULL) { + free(connection->host_); + connection->host_ = NULL; + } + return Undefined(); } @@ -453,6 +599,19 @@ void Connection::OnClose() { }; 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() { @@ -495,6 +654,9 @@ void Server::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor_template, "listen", Listen); 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()); } @@ -550,6 +712,16 @@ Connection* Server::OnConnection(struct sockaddr *addr) { Connection *connection = UnwrapConnection(js_connection); 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(); return connection; @@ -568,7 +740,6 @@ Handle Server::New(const Arguments& args) { Server *server = new Server(); server->Wrap(args.This()); - return args.This(); } @@ -635,6 +806,56 @@ Handle Server::Listen(const Arguments& args) { return Undefined(); } +#if EVCOM_HAVE_GNUTLS + +Handle Server::SetSecure(const Arguments& args) { + Server *server = ObjectWrap::Unwrap(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(*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(*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(*keyString) + , keyString.length() + }; + gnutls_datum_t datum_cert = { reinterpret_cast(*certString) + , certString.length() + }; + r = gnutls_certificate_set_x509_key_mem(server->credentials, + &datum_cert, &datum_key, + GNUTLS_X509_FMT_PEM); + } + + return Undefined(); +} + +#endif + Handle Server::Close(const Arguments& args) { Server *server = ObjectWrap::Unwrap(args.Holder()); assert(server); @@ -644,3 +865,206 @@ Handle Server::Close(const Arguments& args) { } } // 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 + diff --git a/src/node_net.h b/src/node_net.h index 08115c7c6a..cde3f2bae0 100644 --- a/src/node_net.h +++ b/src/node_net.h @@ -7,6 +7,12 @@ #include #include +#if EVCOM_HAVE_GNUTLS +#include +#include +#endif + + namespace node { class Server; @@ -35,6 +41,12 @@ class Connection : public EventEmitter { static v8::Handle FDGetter(v8::Local _, const v8::AccessorInfo& info); + #if EVCOM_HAVE_GNUTLS + static v8::Handle SetSecure(const v8::Arguments& args); + static v8::Handle VerifyPeer(const v8::Arguments& args); + static v8::Handle GetPeerCertificate(const v8::Arguments& args); + #endif + Connection() : EventEmitter() { encoding_ = BINARY; @@ -92,6 +104,10 @@ class Connection : public EventEmitter { enum encoding encoding_; bool resolving_; + bool secure_; + #if EVCOM_HAVE_GNUTLS + gnutls_certificate_credentials_t credentials; + #endif private: @@ -155,12 +171,16 @@ class Server : public EventEmitter { static v8::Handle New(const v8::Arguments& args); static v8::Handle Listen(const v8::Arguments& args); static v8::Handle Close(const v8::Arguments& args); + #if EVCOM_HAVE_GNUTLS + static v8::Handle SetSecure(const v8::Arguments& args); + #endif Server() : EventEmitter() { evcom_server_init(&server_); server_.on_connection = Server::on_connection; server_.on_close = Server::on_close; server_.data = this; + secure_ = false; } virtual ~Server() { @@ -201,7 +221,50 @@ class Server : public EventEmitter { } evcom_server server_; + + #if EVCOM_HAVE_GNUTLS + gnutls_certificate_credentials_t credentials; + #endif + bool secure_; }; } // namespace node #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 diff --git a/test/mjsunit/fixtures/test_ca.pem b/test/mjsunit/fixtures/test_ca.pem new file mode 100644 index 0000000000..a3c1e4a0aa --- /dev/null +++ b/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----- diff --git a/test/mjsunit/fixtures/test_cert.pem b/test/mjsunit/fixtures/test_cert.pem new file mode 100644 index 0000000000..a3c1e4a0aa --- /dev/null +++ b/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----- diff --git a/test/mjsunit/fixtures/test_key.pem b/test/mjsunit/fixtures/test_key.pem new file mode 100644 index 0000000000..48fd93c994 --- /dev/null +++ b/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----- diff --git a/test/mjsunit/test-readdir.js b/test/mjsunit/test-readdir.js index 33b9fe32b7..b866f2aea9 100644 --- a/test/mjsunit/test-readdir.js +++ b/test/mjsunit/test-readdir.js @@ -7,7 +7,7 @@ puts("readdir " + fixturesDir); promise.addCallback(function (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 () { diff --git a/test/mjsunit/test-tcp-tls.js b/test/mjsunit/test-tcp-tls.js new file mode 100644 index 0000000000..1da3e733e8 --- /dev/null +++ b/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); +} diff --git a/wscript b/wscript index 1d1e041239..fa66625361 100644 --- a/wscript +++ b/wscript @@ -119,15 +119,21 @@ def configure(conf): if sys.platform.startswith("freebsd"): 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/libev') 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.env.append_value("CCFLAGS", "-DX_STACKSIZE=%d" % (1024*64)) @@ -258,7 +264,7 @@ def build(bld): evcom.includes = "deps/evcom/ deps/libev/" evcom.name = "evcom" evcom.target = "evcom" - # evcom.uselib = "GNUTLS" + evcom.uselib = "GPGERROR GNUTLS" evcom.install_path = None if bld.env["USE_DEBUG"]: evcom.clone("debug") @@ -337,7 +343,8 @@ def build(bld): """ node.add_objects = 'ev eio evcom http_parser coupling' 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}/bin' node.chmod = 0755