mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
Initial TLS support
This commit is contained in:
50
doc/api.txt
50
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
|
||||
|
||||
12
lib/tcp.js
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) {
|
||||
var server = new process.tcp.Server();
|
||||
server.addListener("connection", on_connection);
|
||||
|
||||
432
src/node_net.cc
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, "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<Value> Connection::FDGetter(Local<String> 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<Value> Connection::SetEncoding(const Arguments& args) {
|
||||
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) {
|
||||
HandleScope scope;
|
||||
|
||||
@@ -382,6 +522,12 @@ Handle<Value> 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<Object> 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<Value> Server::New(const Arguments& args) {
|
||||
|
||||
Server *server = new Server();
|
||||
server->Wrap(args.This());
|
||||
|
||||
return args.This();
|
||||
}
|
||||
|
||||
@@ -635,6 +806,56 @@ Handle<Value> Server::Listen(const Arguments& args) {
|
||||
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) {
|
||||
Server *server = ObjectWrap::Unwrap<Server>(args.Holder());
|
||||
assert(server);
|
||||
@@ -644,3 +865,206 @@ Handle<Value> 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
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
#include <v8.h>
|
||||
#include <evcom.h>
|
||||
|
||||
#if EVCOM_HAVE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace node {
|
||||
|
||||
class Server;
|
||||
@@ -35,6 +41,12 @@ class Connection : public EventEmitter {
|
||||
static v8::Handle<v8::Value> FDGetter(v8::Local<v8::String> _,
|
||||
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() {
|
||||
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<v8::Value> New(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Listen(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() {
|
||||
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
|
||||
|
||||
20
test/mjsunit/fixtures/test_ca.pem
Normal file
20
test/mjsunit/fixtures/test_ca.pem
Normal file
@@ -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
Normal file
20
test/mjsunit/fixtures/test_cert.pem
Normal file
@@ -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
Normal file
15
test/mjsunit/fixtures/test_key.pem
Normal file
@@ -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-----
|
||||
@@ -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 () {
|
||||
|
||||
122
test/mjsunit/test-tcp-tls.js
Normal file
122
test/mjsunit/test-tcp-tls.js
Normal file
@@ -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
19
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
|
||||
|
||||
Reference in New Issue
Block a user