From fb3a9cd0d8e8cf5f3120012a28643230b31c1f83 Mon Sep 17 00:00:00 2001 From: Rhys Jones Date: Sat, 3 Apr 2010 00:11:53 +0100 Subject: [PATCH] Initial openssl support for net2 --- lib/net.js | 176 +++++++- src/node.cc | 14 +- src/node_crypto.cc | 971 +++++++++++++++++++++++++++++++++++++++++++++ src/node_crypto.h | 73 ++++ wscript | 14 +- 5 files changed, 1232 insertions(+), 16 deletions(-) create mode 100644 src/node_crypto.cc create mode 100644 src/node_crypto.h diff --git a/lib/net.js b/lib/net.js index dd415f7d23fdd1..7ef363a7d44f40 100644 --- a/lib/net.js +++ b/lib/net.js @@ -9,7 +9,6 @@ var debugLevel = process.env['NODE_DEBUG'] ? 1 : 0; function debug () { if (debugLevel > 0) sys.error.apply(this, arguments); } - var binding = process.binding('net'); // Note about Buffer interface: @@ -45,6 +44,14 @@ var EINPROGRESS = binding.EINPROGRESS; var ENOENT = binding.ENOENT; var END_OF_FILE = 42; +// Do we have openssl crypto? +try { + var SecureContext = process.binding('crypto').SecureContext; + var SecureStream = process.binding('crypto').SecureStream; + var crypto = true; +} catch (e) { + var crypto = false; +} // IDLE TIMEOUTS // @@ -246,6 +253,16 @@ function allocNewPool () { pool.used = 0; } +var securePool = null; +function allocNewSecurePool () { + securePool = new Buffer(40*1024); +} +var emptyBuffer = null; +function allocEmptyBuffer () { + emptyBuffer = new Buffer(1); + emptyBuffer.sent = 0; + emptyBuffer.length = 0; +} function _doFlush () { var socket = this.socket; @@ -271,20 +288,46 @@ function initStream (self) { //debug('pool.used ' + pool.used); var bytesRead; + var secureBytesRead; try { - bytesRead = read(self.fd, + if (self.secure) { + if (!securePool) allocNewSecurePool(); + secureBytesRead = read(self.fd, securePool, 0, securePool.length); + self.secureStream.readInject(securePool, 0, secureBytesRead); + bytesRead = self.secureStream.readExtract(pool, pool.used, pool.length - pool.used); + if(!self.secureEstablished) { + if (self.secureStream.isInitFinished()) { + self.secureEstablished = true; + if (self._events && self._events['secure']) self.emit('secure'); + } + } + if (secureBytesRead === null && !self.server) { + // Client needs to write as part of handshake + this._writeWatcher.start(); + } + } else { + bytesRead = read(self.fd, pool, pool.used, - pool.length - pool.used); + pool.length - pool.used); + } } catch (e) { - self.forceClose(e); + if (this.forceClose) this.forceClose(e); return; } //debug('bytesRead ' + bytesRead + '\n'); - if (bytesRead === 0) { + if (self.secure && bytesRead == 0 && secureBytesRead >0){ + // Deal with SSL handshake + if (self.server) { + self._checkForSecureHandshake(); + } else { + if (self.secureEstablised) self.flush(); + else self._checkForSecureHandshake(); + } + } else if (bytesRead === 0) { self.readable = false; self._readWatcher.stop(); @@ -341,10 +384,36 @@ function initStream (self) { self.writable = false; } +function Credentials(method) { + if (!crypto) { + throw new Error('node.js not compiled with openssl crypto support.'); + } + this.context = new SecureContext(); + if (method) this.context.init(method); + else this.context.init(); +} + +exports.createCredentials = function(cred) { + var c = new Credentials(cred.method); + if (cred.key) c.context.setKey(cred.key); + if (cred.cert) c.context.setCert(cred.cert); + if (cred.ca) { + if ( (typeof(cred.ca) == 'object') && cred.ca.length ) { + for(var i=0; i= 0) { this.open(fd); @@ -353,6 +422,55 @@ function Stream (fd) { sys.inherits(Stream, events.EventEmitter); exports.Stream = Stream; +Stream.prototype.setSecure = function(credentials) { + if (!crypto) { + throw new Error('node.js not compiled with openssl crypto support.'); + } + this.secure = true; + this.secureEstablished = false; + // If no credentials given, create a new one for just this Stream + if (!credentials) { + this.credentials = new Credentials(); + } else { + this.credentials = credentials; + } + this.secureStream = new SecureStream(this.credentials.context, this.server?1:0); + + if (!this.server) { + // If client, trigger handshake + this._checkForSecureHandshake(); + } + + +} + +Stream.prototype.verifyPeer = function() { + if (!this.secure) { + throw new Error('Stream is not a secure stream.'); + } + return this.secureStream.verifyPeer(this.credentials.context); +} + +Stream.prototype._checkForSecureHandshake = function() { + // Do an empty write to see if we need to write out as part of handshake + if (!emptyBuffer) allocEmptyBuffer(); + this.write(emptyBuffer); +} + +Stream.prototype.getPeerCertificate = function(credentials) { + if (!this.secure) { + throw new Error('Stream is not a secure stream.'); + } + return this.secureStream.getPeerCertificate(); +} + +Stream.prototype.getCipher = function() { + if (!this.secure) { + throw new Error('Stream is not a secure stream.'); + } + return this.secureStream.getCurrentCipher(); +} + Stream.prototype.open = function (fd) { initStream(this); @@ -411,6 +529,15 @@ Stream.prototype.write = function (data, encoding) { }; +Stream.prototype._shutdownSecure = function () { + this.secureStream.shutdown(); + if (!securePool) allocNewSecurePool(); + var secureLen = this.secureStream.writeExtract(securePool, 0, securePool.length); + try { + var secureBytesWritten = write(this.fd, securePool, 0, secureLen); + } catch (e) {} +} + // Directly writes the data to socket. // // Steps: @@ -420,14 +547,15 @@ Stream.prototype.write = function (data, encoding) { // 3. Slice out remaining // 4. Unshift remaining onto _writeQueue. Return false. Stream.prototype._writeOut = function (data, encoding) { - if (!this.writable) throw new Error('Stream is not writable'); + if (!this.writable) { + if (this.secure) return false; + else throw new Error('Stream is not writable'); + } var buffer, off, len; var bytesWritten, charsWritten; - var queuedData = false; - if (typeof data != 'string') { // 'data' is a buffer, ignore 'encoding' buffer = data; @@ -458,7 +586,7 @@ Stream.prototype._writeOut = function (data, encoding) { assert(charsWritten <= data.length); } - assert(bytesWritten > 0); + if (encoding) assert(bytesWritten > 0); buffer = pool; len = bytesWritten; @@ -478,11 +606,26 @@ Stream.prototype._writeOut = function (data, encoding) { } } - - // Send the buffer. - try { - bytesWritten = write(this.fd, buffer, off, len); + if (this.secure) { + if (!buffer) return false; + bytesWritten = this.secureStream.writeInject(buffer, off, len); + if (!securePool) allocNewSecurePool(); + var secureLen = this.secureStream.writeExtract(securePool, 0, securePool.length); + if (secureLen==-1) { + // Check our read again for secure handshake + this._readWatcher.callback(); + secureBytesWritten = 0; + } else { + var secureBytesWritten = write(this.fd, securePool, 0, secureLen); + } + if(!this.secureEstablished && this.secureStream.isInitFinished()) { + this.secureEstablished = true; + if (this._events && this._events['secure']) this.emit('secure'); + } + } else { + bytesWritten = write(this.fd, buffer, off, len); + } } catch (e) { this.forceClose(e); return false; @@ -675,6 +818,10 @@ Stream.prototype.forceClose = function (exception) { timeout.unenroll(this); + if (this.secure) { + this.secureStream.close(); + } + // FIXME Bug when this.fd == 0 if (typeof this.fd == 'number') { close(this.fd); @@ -691,6 +838,9 @@ Stream.prototype._shutdown = function () { if (this.writable) { this.writable = false; + if (this.secure) { + this._shutdownSecure(); + } try { shutdown(this.fd, 'write') } catch (e) { diff --git a/src/node.cc b/src/node.cc index 452c111b11f28a..c3a8dd54a24333 100644 --- a/src/node.cc +++ b/src/node.cc @@ -32,6 +32,9 @@ #include #include #include +#ifdef HAVE_OPENSSL +#include +#endif #include @@ -1179,7 +1182,16 @@ static Handle Binding(const Arguments& args) { Buffer::Initialize(exports); binding_cache->Set(module, exports); } - + #ifdef HAVE_OPENSSL + } else if (!strcmp(*module_v, "crypto")) { + if (binding_cache->Has(module)) { + exports = binding_cache->Get(module)->ToObject(); + } else { + exports = Object::New(); + InitCrypto(exports); + binding_cache->Set(module, exports); + } + #endif } else if (!strcmp(*module_v, "natives")) { if (binding_cache->Has(module)) { exports = binding_cache->Get(module)->ToObject(); diff --git a/src/node_crypto.cc b/src/node_crypto.cc new file mode 100644 index 00000000000000..0056fe6e97b0b5 --- /dev/null +++ b/src/node_crypto.cc @@ -0,0 +1,971 @@ +#include +#include + +#include +#include + +#include +#include + +#include + +namespace node { + +using namespace v8; + +static Persistent errno_symbol; +static Persistent syscall_symbol; +static Persistent subject_symbol; +static Persistent issuer_symbol; +static Persistent valid_from_symbol; +static Persistent valid_to_symbol; +static Persistent name_symbol; +static Persistent version_symbol; + +static int x509_verify_error; + +static inline const char *errno_string(int errorno) { +#define ERRNO_CASE(e) case e: return #e; + switch (errorno) { + +#ifdef EACCES + ERRNO_CASE(EACCES); +#endif + +#ifdef EADDRINUSE + ERRNO_CASE(EADDRINUSE); +#endif + +#ifdef EADDRNOTAVAIL + ERRNO_CASE(EADDRNOTAVAIL); +#endif + +#ifdef EAFNOSUPPORT + ERRNO_CASE(EAFNOSUPPORT); +#endif + +#ifdef EAGAIN + ERRNO_CASE(EAGAIN); +#else +# ifdef EWOULDBLOCK + ERRNO_CASE(EWOULDBLOCK); +# endif +#endif + +#ifdef EALREADY + ERRNO_CASE(EALREADY); +#endif + +#ifdef EBADF + ERRNO_CASE(EBADF); +#endif + +#ifdef EBADMSG + ERRNO_CASE(EBADMSG); +#endif + +#ifdef EBUSY + ERRNO_CASE(EBUSY); +#endif + +#ifdef ECANCELED + ERRNO_CASE(ECANCELED); +#endif + +#ifdef ECHILD + ERRNO_CASE(ECHILD); +#endif + +#ifdef ECONNABORTED + ERRNO_CASE(ECONNABORTED); +#endif + +#ifdef ECONNREFUSED + ERRNO_CASE(ECONNREFUSED); +#endif + +#ifdef ECONNRESET + ERRNO_CASE(ECONNRESET); +#endif + +#ifdef EDEADLK + ERRNO_CASE(EDEADLK); +#endif + +#ifdef EDESTADDRREQ + ERRNO_CASE(EDESTADDRREQ); +#endif + +#ifdef EDOM + ERRNO_CASE(EDOM); +#endif + +#ifdef EDQUOT + ERRNO_CASE(EDQUOT); +#endif + +#ifdef EEXIST + ERRNO_CASE(EEXIST); +#endif + +#ifdef EFAULT + ERRNO_CASE(EFAULT); +#endif + +#ifdef EFBIG + ERRNO_CASE(EFBIG); +#endif + +#ifdef EHOSTUNREACH + ERRNO_CASE(EHOSTUNREACH); +#endif + +#ifdef EIDRM + ERRNO_CASE(EIDRM); +#endif + +#ifdef EILSEQ + ERRNO_CASE(EILSEQ); +#endif + +#ifdef EINPROGRESS + ERRNO_CASE(EINPROGRESS); +#endif + +#ifdef EINTR + ERRNO_CASE(EINTR); +#endif + +#ifdef EINVAL + ERRNO_CASE(EINVAL); +#endif + +#ifdef EIO + ERRNO_CASE(EIO); +#endif + +#ifdef EISCONN + ERRNO_CASE(EISCONN); +#endif + +#ifdef EISDIR + ERRNO_CASE(EISDIR); +#endif + +#ifdef ELOOP + ERRNO_CASE(ELOOP); +#endif + +#ifdef EMFILE + ERRNO_CASE(EMFILE); +#endif + +#ifdef EMLINK + ERRNO_CASE(EMLINK); +#endif + +#ifdef EMSGSIZE + ERRNO_CASE(EMSGSIZE); +#endif + +#ifdef EMULTIHOP + ERRNO_CASE(EMULTIHOP); +#endif + +#ifdef ENAMETOOLONG + ERRNO_CASE(ENAMETOOLONG); +#endif + +#ifdef ENETDOWN + ERRNO_CASE(ENETDOWN); +#endif + +#ifdef ENETRESET + ERRNO_CASE(ENETRESET); +#endif + +#ifdef ENETUNREACH + ERRNO_CASE(ENETUNREACH); +#endif + +#ifdef ENFILE + ERRNO_CASE(ENFILE); +#endif + +#ifdef ENOBUFS + ERRNO_CASE(ENOBUFS); +#endif + +#ifdef ENODATA + ERRNO_CASE(ENODATA); +#endif + +#ifdef ENODEV + ERRNO_CASE(ENODEV); +#endif + +#ifdef ENOENT + ERRNO_CASE(ENOENT); +#endif + +#ifdef ENOEXEC + ERRNO_CASE(ENOEXEC); +#endif + +#ifdef ENOLCK + ERRNO_CASE(ENOLCK); +#endif + +#ifdef ENOLINK + ERRNO_CASE(ENOLINK); +#endif + +#ifdef ENOMEM + ERRNO_CASE(ENOMEM); +#endif + +#ifdef ENOMSG + ERRNO_CASE(ENOMSG); +#endif + +#ifdef ENOPROTOOPT + ERRNO_CASE(ENOPROTOOPT); +#endif + +#ifdef ENOSPC + ERRNO_CASE(ENOSPC); +#endif + +#ifdef ENOSR + ERRNO_CASE(ENOSR); +#endif + +#ifdef ENOSTR + ERRNO_CASE(ENOSTR); +#endif + +#ifdef ENOSYS + ERRNO_CASE(ENOSYS); +#endif + +#ifdef ENOTCONN + ERRNO_CASE(ENOTCONN); +#endif + +#ifdef ENOTDIR + ERRNO_CASE(ENOTDIR); +#endif + +#ifdef ENOTEMPTY + ERRNO_CASE(ENOTEMPTY); +#endif + +#ifdef ENOTSOCK + ERRNO_CASE(ENOTSOCK); +#endif + +#ifdef ENOTSUP + ERRNO_CASE(ENOTSUP); +#else +# ifdef EOPNOTSUPP + ERRNO_CASE(EOPNOTSUPP); +# endif +#endif + +#ifdef ENOTTY + ERRNO_CASE(ENOTTY); +#endif + +#ifdef ENXIO + ERRNO_CASE(ENXIO); +#endif + + +#ifdef EOVERFLOW + ERRNO_CASE(EOVERFLOW); +#endif + +#ifdef EPERM + ERRNO_CASE(EPERM); +#endif + +#ifdef EPIPE + ERRNO_CASE(EPIPE); +#endif + +#ifdef EPROTO + ERRNO_CASE(EPROTO); +#endif + +#ifdef EPROTONOSUPPORT + ERRNO_CASE(EPROTONOSUPPORT); +#endif + +#ifdef EPROTOTYPE + ERRNO_CASE(EPROTOTYPE); +#endif + +#ifdef ERANGE + ERRNO_CASE(ERANGE); +#endif + +#ifdef EROFS + ERRNO_CASE(EROFS); +#endif + +#ifdef ESPIPE + ERRNO_CASE(ESPIPE); +#endif + +#ifdef ESRCH + ERRNO_CASE(ESRCH); +#endif + +#ifdef ESTALE + ERRNO_CASE(ESTALE); +#endif + +#ifdef ETIME + ERRNO_CASE(ETIME); +#endif + +#ifdef ETIMEDOUT + ERRNO_CASE(ETIMEDOUT); +#endif + +#ifdef ETXTBSY + ERRNO_CASE(ETXTBSY); +#endif + +#ifdef EXDEV + ERRNO_CASE(EXDEV); +#endif + + default: return ""; + } +} + + + + +static int verify_callback(int ok, X509_STORE_CTX *ctx) { + x509_verify_error = ctx->error; + return(ok); +} + +static inline Local ErrnoException(int errorno, + const char *syscall, + const char *msg = "") { + Local estring = String::NewSymbol(errno_string(errorno)); + if (!msg[0]) msg = strerror(errorno); + Local message = String::NewSymbol(msg); + + Local cons1 = String::Concat(estring, String::NewSymbol(", ")); + Local cons2 = String::Concat(cons1, message); + + Local e = Exception::Error(cons2); + + Local obj = e->ToObject(); + obj->Set(errno_symbol, Integer::New(errorno)); + obj->Set(syscall_symbol, String::NewSymbol(syscall)); + return e; +} + +void SecureContext::Initialize(Handle target) { + HandleScope scope; + + Local t = FunctionTemplate::New(SecureContext::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(String::NewSymbol("SecureContext")); + + NODE_SET_PROTOTYPE_METHOD(t, "init", SecureContext::Init); + NODE_SET_PROTOTYPE_METHOD(t, "setKey", SecureContext::SetKey); + NODE_SET_PROTOTYPE_METHOD(t, "setCert", SecureContext::SetCert); + NODE_SET_PROTOTYPE_METHOD(t, "addCACert", SecureContext::AddCACert); + NODE_SET_PROTOTYPE_METHOD(t, "setCiphers", SecureContext::SetCiphers); + NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close); + + target->Set(String::NewSymbol("SecureContext"), t->GetFunction()); +} + + +Handle SecureContext::New(const Arguments& args) { + HandleScope scope; + SecureContext *p = new SecureContext(); + p->Wrap(args.Holder()); + return args.This(); +} + +Handle SecureContext::Init(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + SSL_METHOD *method = SSLv23_method(); + + if (args.Length() == 1) { + if (!args[0]->IsString()) + return ThrowException(Exception::TypeError( + String::New("Bad parameter"))); + + String::Utf8Value sslmethod(args[0]->ToString()); + if (strcmp(*sslmethod, "SSLv2_method") == 0) + method = SSLv2_method(); + if (strcmp(*sslmethod, "SSLv2_server_method") == 0) + method = SSLv2_server_method(); + if (strcmp(*sslmethod, "SSLv2_client_method") == 0) + method = SSLv2_client_method(); + if (strcmp(*sslmethod, "SSLv3_method") == 0) + method = SSLv3_method(); + if (strcmp(*sslmethod, "SSLv3_server_method") == 0) + method = SSLv3_server_method(); + if (strcmp(*sslmethod, "SSLv3_client_method") == 0) + method = SSLv3_client_method(); + if (strcmp(*sslmethod, "SSLv23_method") == 0) + method = SSLv23_method(); + if (strcmp(*sslmethod, "SSLv23_server_method") == 0) + method = SSLv23_server_method(); + if (strcmp(*sslmethod, "SSLv23_client_method") == 0) + method = SSLv23_client_method(); + if (strcmp(*sslmethod, "TLSv1_method") == 0) + method = TLSv1_method(); + if (strcmp(*sslmethod, "TLSv1_server_method") == 0) + method = TLSv1_server_method(); + if (strcmp(*sslmethod, "TLSv1_client_method") == 0) + method = TLSv1_client_method(); + } + + + + sc->pCtx = SSL_CTX_new(method); + // Enable session caching? + SSL_CTX_set_session_cache_mode(sc->pCtx, SSL_SESS_CACHE_SERVER); + // SSL_CTX_set_session_cache_mode(sc->pCtx,SSL_SESS_CACHE_OFF); + + sc->caStore = X509_STORE_new(); + return True(); +} + +Handle SecureContext::SetKey(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() != 1 || + !args[0]->IsString()) { + return ThrowException(Exception::TypeError( + String::New("Bad parameter"))); + } + String::Utf8Value keyPem(args[0]->ToString()); + + BIO *bp = NULL; + EVP_PKEY* pkey; + bp = BIO_new(BIO_s_mem()); + if (!BIO_write(bp, *keyPem, strlen(*keyPem))) + return False(); + + pkey = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL); + if (pkey == NULL) + return False(); + + SSL_CTX_use_PrivateKey(sc->pCtx, pkey); + BIO_free(bp); + + return True(); +} + +Handle SecureContext::SetCert(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() != 1 || + !args[0]->IsString()) { + return ThrowException(Exception::TypeError( + String::New("Bad parameter"))); + } + String::Utf8Value certPem(args[0]->ToString()); + + BIO *bp = NULL; + X509 * x509; + bp = BIO_new(BIO_s_mem()); + if (!BIO_write(bp, *certPem, strlen(*certPem))) + return False(); + + x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL); + if (x509 == NULL) + return False(); + + SSL_CTX_use_certificate(sc->pCtx, x509); + BIO_free(bp); + X509_free(x509); + + return True(); +} + +Handle SecureContext::AddCACert(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() != 1 || + !args[0]->IsString()) { + return ThrowException(Exception::TypeError( + String::New("Bad parameter"))); + } + String::Utf8Value certPem(args[0]->ToString()); + + BIO *bp = NULL; + X509 *x509; + bp = BIO_new(BIO_s_mem()); + if (!BIO_write(bp, *certPem, strlen(*certPem))) + return False(); + + x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL); + if (x509 == NULL) + return False(); + + X509_STORE_add_cert(sc->caStore, x509); + + BIO_free(bp); + X509_free(x509); + + return True(); +} + + +Handle SecureContext::SetCiphers(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() != 1 || + !args[0]->IsString()) { + return ThrowException(Exception::TypeError( + String::New("Bad parameter"))); + } + String::Utf8Value ciphers(args[0]->ToString()); + SSL_CTX_set_cipher_list(sc->pCtx, *ciphers); + + return True(); +} + + +Handle SecureContext::Close(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (sc->pCtx != NULL) { + SSL_CTX_free(sc->pCtx); + return True(); + } + return False(); +} + + + +void SecureStream::Initialize(Handle target) { + HandleScope scope; + + Local t = FunctionTemplate::New(SecureStream::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(String::NewSymbol("SecureStream")); + + NODE_SET_PROTOTYPE_METHOD(t, "readInject", + SecureStream::ReadInject); + NODE_SET_PROTOTYPE_METHOD(t, "readExtract", + SecureStream::ReadExtract); + NODE_SET_PROTOTYPE_METHOD(t, "writeInject", + SecureStream::WriteInject); + NODE_SET_PROTOTYPE_METHOD(t, "writeExtract", + SecureStream::WriteExtract); + NODE_SET_PROTOTYPE_METHOD(t, "writeCanExtract", + SecureStream::WriteCanExtract); + NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", + SecureStream::GetPeerCertificate); + NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", + SecureStream::IsInitFinished); + NODE_SET_PROTOTYPE_METHOD(t, "verifyPeer", + SecureStream::VerifyPeer); + NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", + SecureStream::GetCurrentCipher); + NODE_SET_PROTOTYPE_METHOD(t, "shutdown", + SecureStream::Shutdown); + NODE_SET_PROTOTYPE_METHOD(t, "close", + SecureStream::Close); + + target->Set(String::NewSymbol("SecureStream"), t->GetFunction()); +} + + +Handle SecureStream::New(const Arguments& args) { + HandleScope scope; + SecureStream *p = new SecureStream(); + p->Wrap(args.Holder()); + + if (args.Length() != 2 || + !args[0]->IsObject() || + !args[1]->IsNumber()) { + return ThrowException(Exception::Error(String::New("Bad arguments."))); + } + SecureContext *sc = ObjectWrap::Unwrap(args[0]->ToObject()); + int isServer = args[1]->Int32Value(); + + p->pSSL = SSL_new(sc->pCtx); + p->pbioRead = BIO_new(BIO_s_mem()); + p->pbioWrite = BIO_new(BIO_s_mem()); + SSL_set_bio(p->pSSL, p->pbioRead, p->pbioWrite); + p->server = isServer>0; + if (p->server) { + SSL_set_accept_state(p->pSSL); + } else { + SSL_set_connect_state(p->pSSL); + } + + return args.This(); +} + + +Handle SecureStream::ReadInject(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() < 3) { + return ThrowException(Exception::TypeError( + String::New("Takes 3 parameters"))); + } + + if (!Buffer::HasInstance(args[0])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject()); + + size_t off = args[1]->Int32Value(); + if (off >= buffer->length()) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[2]->Int32Value(); + if (off + len > buffer->length()) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + int bytes_written = BIO_write(ss->pbioRead, (char*)buffer->data() + off, len); + + if (bytes_written < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "read")); + } + + return scope.Close(Integer::New(bytes_written)); +} + +Handle SecureStream::ReadExtract(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() < 3) { + return ThrowException(Exception::TypeError( + String::New("Takes 3 parameters"))); + } + + if (!Buffer::HasInstance(args[0])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject()); + + size_t off = args[1]->Int32Value(); + if (off >= buffer->length()) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[2]->Int32Value(); + if (off + len > buffer->length()) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + int bytes_read; + + if (!SSL_is_init_finished(ss->pSSL)) { + if (ss->server) { + bytes_read = SSL_accept(ss->pSSL); + } else { + bytes_read = SSL_connect(ss->pSSL); + } + if (bytes_read < 0) { + int err; + if ((err = SSL_get_error(ss->pSSL, bytes_read)) == SSL_ERROR_WANT_READ) { + return scope.Close(Integer::New(0)); + } + } + return scope.Close(Integer::New(0)); + } + + bytes_read = SSL_read(ss->pSSL, (char*)buffer->data() + off, len); + if (bytes_read < 0) { + int err = SSL_get_error(ss->pSSL, bytes_read); + if (err == SSL_ERROR_WANT_READ) { + return scope.Close(Integer::New(0)); + } + // SSL read error + return scope.Close(Integer::New(-2)); + } + + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EINTR) return Null(); + return ThrowException(ErrnoException(errno, "read")); + } + + return scope.Close(Integer::New(bytes_read)); +} + +Handle SecureStream::WriteCanExtract(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + int bytes_pending = BIO_pending(ss->pbioWrite); + return scope.Close(Integer::New(bytes_pending)); +} + +Handle SecureStream::WriteExtract(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() < 3) { + return ThrowException(Exception::TypeError( + String::New("Takes 3 parameters"))); + } + + if (!Buffer::HasInstance(args[0])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject()); + + size_t off = args[1]->Int32Value(); + if (off >= buffer->length()) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[2]->Int32Value(); + if (off + len > buffer->length()) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + int bytes_read = BIO_read(ss->pbioWrite, (char*)buffer->data() + off, len); + + return scope.Close(Integer::New(bytes_read)); +} + + +Handle SecureStream::WriteInject(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() < 3) { + return ThrowException(Exception::TypeError( + String::New("Takes 3 parameters"))); + } + + if (!Buffer::HasInstance(args[0])) { + return ThrowException(Exception::TypeError( + String::New("Second argument should be a buffer"))); + } + + Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject()); + + size_t off = args[1]->Int32Value(); + if (off >= buffer->length()) { + return ThrowException(Exception::Error( + String::New("Offset is out of bounds"))); + } + + size_t len = args[2]->Int32Value(); + if (off + len > buffer->length()) { + return ThrowException(Exception::Error( + String::New("Length is extends beyond buffer"))); + } + + if (!SSL_is_init_finished(ss->pSSL)) { + int s; + if (ss->server) { + s = SSL_accept(ss->pSSL); + } else { + s = SSL_connect(ss->pSSL); + } + return scope.Close(Integer::New(0)); + } + int bytes_written = SSL_write(ss->pSSL, (char*)buffer->data() + off, len); + + return scope.Close(Integer::New(bytes_written)); +} + + +Handle SecureStream::GetPeerCertificate(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (ss->pSSL == NULL) return Undefined(); + Local info = Object::New(); + X509* peer_cert = SSL_get_peer_certificate(ss->pSSL); + if (peer_cert != NULL) { + char* subject = X509_NAME_oneline(X509_get_subject_name(peer_cert), 0, 0); + if (subject != NULL) { + info->Set(subject_symbol, String::New(subject)); + OPENSSL_free(subject); + } + char* issuer = X509_NAME_oneline(X509_get_issuer_name(peer_cert), 0, 0); + if (subject != NULL) { + info->Set(issuer_symbol, String::New(issuer)); + OPENSSL_free(issuer); + } + char buf[256]; + BIO* bio = BIO_new(BIO_s_mem()); + ASN1_TIME_print(bio, X509_get_notBefore(peer_cert)); + memset(buf, 0, sizeof(buf)); + BIO_read(bio, buf, sizeof(buf) - 1); + info->Set(valid_from_symbol, String::New(buf)); + ASN1_TIME_print(bio, X509_get_notAfter(peer_cert)); + memset(buf, 0, sizeof(buf)); + BIO_read(bio, buf, sizeof(buf) - 1); + BIO_free(bio); + info->Set(valid_to_symbol, String::New(buf)); + + X509_free(peer_cert); + } + return scope.Close(info); +} + +Handle SecureStream::Shutdown(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (ss->pSSL == NULL) return False(); + if (SSL_shutdown(ss->pSSL) == 1) { + return True(); + } + return False(); +} + +Handle SecureStream::IsInitFinished(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (ss->pSSL == NULL) return False(); + if (SSL_is_init_finished(ss->pSSL)) { + return True(); + } + return False(); +} + + +Handle SecureStream::VerifyPeer(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + SecureContext *sc = ObjectWrap::Unwrap(args[0]->ToObject()); + + if (ss->pSSL == NULL) return False(); + if (sc->caStore == NULL) return False(); + + X509 *cert = SSL_get_peer_certificate(ss->pSSL); + STACK_OF(X509) *certChain = SSL_get_peer_cert_chain(ss->pSSL); + X509_STORE_set_verify_cb_func(sc->caStore, verify_callback); + X509_STORE_CTX *storeCtx = X509_STORE_CTX_new(); + X509_STORE_CTX_init(storeCtx, sc->caStore, cert, certChain); + + x509_verify_error = 0; + // OS X Bug in openssl : x509_verify_cert is always true? + // This is why we have our global. + X509_verify_cert(storeCtx); + + X509_STORE_CTX_free(storeCtx); + + // Can also check for: + // X509_V_ERR_CERT_HAS_EXPIRED + // X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT + // X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN + // X509_V_ERR_INVALID_CA + // X509_V_ERR_PATH_LENGTH_EXCEEDED + // X509_V_ERR_INVALID_PURPOSE + // X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT + + // printf("%s\n", X509_verify_cert_error_string(x509_verify_error)); + + if (!x509_verify_error) return True(); + return False(); +} + +Handle SecureStream::GetCurrentCipher(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + SSL_CIPHER *c; + + if ( ss->pSSL == NULL ) return Undefined(); + c = SSL_get_current_cipher(ss->pSSL); + if ( c == NULL ) return Undefined(); + Local info = Object::New(); + const char *cipher_name = SSL_CIPHER_get_name(c); + info->Set(name_symbol, String::New(cipher_name)); + const char *cipher_version = SSL_CIPHER_get_version(c); + info->Set(version_symbol, String::New(cipher_version)); + return scope.Close(info); +} + +Handle SecureStream::Close(const Arguments& args) { + HandleScope scope; + + SecureStream *ss = ObjectWrap::Unwrap(args.Holder()); + + if (ss->pSSL != NULL) { + SSL_free(ss->pSSL); + ss->pSSL = NULL; + } + return True(); +} + + + +void InitCrypto(Handle target) { + HandleScope scope; + + SSL_library_init(); + OpenSSL_add_ssl_algorithms(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + SecureContext::Initialize(target); + SecureStream::Initialize(target); + + subject_symbol = NODE_PSYMBOL("subject"); + issuer_symbol = NODE_PSYMBOL("issuer"); + valid_from_symbol = NODE_PSYMBOL("valid_from"); + valid_to_symbol = NODE_PSYMBOL("valid_to"); + name_symbol = NODE_PSYMBOL("name"); + version_symbol = NODE_PSYMBOL("version"); +} + + + +} // namespace node + diff --git a/src/node_crypto.h b/src/node_crypto.h new file mode 100644 index 00000000000000..4032f6d73bd35b --- /dev/null +++ b/src/node_crypto.h @@ -0,0 +1,73 @@ +#ifndef SRC_NODE_CRYPTO_H_ +#define SRC_NODE_CRYPTO_H_ + +#include +#include +#include + +#include +#include + +namespace node { + +class SecureContext : ObjectWrap { + public: + static void Initialize(v8::Handle target); + + SSL_CTX *pCtx; + X509_STORE *caStore; + + protected: + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Init(const v8::Arguments& args); + static v8::Handle SetKey(const v8::Arguments& args); + static v8::Handle SetCert(const v8::Arguments& args); + static v8::Handle AddCACert(const v8::Arguments& args); + static v8::Handle SetCiphers(const v8::Arguments& args); + static v8::Handle Close(const v8::Arguments& args); + + SecureContext() : ObjectWrap() { + } + + ~SecureContext() { + // Free up + } + + private: +}; + +class SecureStream : ObjectWrap { + public: + static void Initialize(v8::Handle target); + + protected: + static v8::Handle New(const v8::Arguments& args); + static v8::Handle ReadInject(const v8::Arguments& args); + static v8::Handle ReadExtract(const v8::Arguments& args); + static v8::Handle WriteCanExtract(const v8::Arguments& args); + static v8::Handle WriteExtract(const v8::Arguments& args); + static v8::Handle WriteInject(const v8::Arguments& args); + static v8::Handle GetPeerCertificate(const v8::Arguments& args); + static v8::Handle IsInitFinished(const v8::Arguments& args); + static v8::Handle VerifyPeer(const v8::Arguments& args); + static v8::Handle GetCurrentCipher(const v8::Arguments& args); + static v8::Handle Shutdown(const v8::Arguments& args); + static v8::Handle Close(const v8::Arguments& args); + + SecureStream() : ObjectWrap() { + } + + ~SecureStream() { + } + + private: + BIO *pbioRead; + BIO *pbioWrite; + SSL *pSSL; + bool server; +}; + +void InitCrypto(v8::Handle target); +} + +#endif // SRC_NODE_CRYPTO_H_ diff --git a/wscript b/wscript index aa1be93036358b..92da580afb919e 100644 --- a/wscript +++ b/wscript @@ -134,6 +134,13 @@ def configure(conf): if sys.platform.startswith("freebsd"): conf.fatal("Install the libexecinfo port from /usr/ports/devel/libexecinfo.") + if conf.check_cfg(package='openssl', + args='--cflags --libs', + #libpath=['/usr/lib', '/usr/local/lib'], + uselib_store='OPENSSL'): + conf.env["USE_OPENSSL"] = True + conf.env.append_value("CXXFLAGS", "-DHAVE_OPENSSL=1") + if conf.check_cfg(package='gnutls', args='--cflags --libs', atleast_version='2.5.0', @@ -415,6 +422,9 @@ def build(bld): src/node_timer.cc src/node_idle_watcher.cc """ + if bld.env["USE_OPENSSL"]: + node.source += "src/node_crypto.cc" + if not bld.env["USE_SYSTEM"]: node.includes = """ src/ @@ -428,7 +438,7 @@ def build(bld): """ node.add_objects = 'ev eio evcom http_parser coupling' node.uselib_local = '' - node.uselib = 'GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL' + node.uselib = 'OPENSSL GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL' else: node.includes = """ src/ @@ -439,7 +449,7 @@ def build(bld): """ node.add_objects = 'eio evcom http_parser coupling' node.uselib_local = 'eio' - node.uselib = 'EV GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL' + node.uselib = 'EV OPENSSL GNUTLS GPGERROR UDNS V8 EXECINFO DL KVM SOCKET NSL' node.install_path = '${PREFIX}/lib' node.install_path = '${PREFIX}/bin'