From 341b8f701525bedb049aba15a2d2be10d9d62a64 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sun, 30 Oct 2016 23:39:01 +1100 Subject: [PATCH] crypto: initial LibreSSL support --- lib/_tls_common.js | 8 +++- lib/_tls_wrap.js | 44 ++++++++++++------- node.gyp | 2 +- src/node.cc | 11 +++-- src/node_crypto.cc | 38 +++++++++++----- src/tls_wrap.cc | 2 + test/parallel/test-crypto.js | 5 ++- .../test-tls-client-getephemeralkeyinfo.js | 8 +++- test/parallel/test-tls-client-mindhsize.js | 9 +++- test/parallel/test-tls-cnnic-whitelist.js | 5 ++- test/parallel/test-tls-empty-sni-context.js | 12 +++++ 11 files changed, 108 insertions(+), 36 deletions(-) diff --git a/lib/_tls_common.js b/lib/_tls_common.js index 56baf7bde8c922..dcd7143013d8d5 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -10,6 +10,9 @@ var crypto = null; const binding = process.binding('crypto'); const NativeSecureContext = binding.SecureContext; +const isLibreSSL = process.versions.openssl && + /LibreSSL$/.test(process.versions.openssl); + function SecureContext(secureProtocol, secureOptions, context) { if (!(this instanceof SecureContext)) { @@ -138,7 +141,10 @@ exports.createSecureContext = function createSecureContext(options, context) { // freelist.) if (options.singleUse) { c.singleUse = true; - c.context.setFreeListLength(0); + if (!isLibreSSL) { + c.context.setFreeListLength(0); + } // else TODO check for possible leak + // https://github.com/nodejs/node/issues/1522 } return c; diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index ebd36519cf1525..a5c64d8e7606a3 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -16,6 +16,8 @@ const Timer = process.binding('timer_wrap').Timer; const tls_wrap = process.binding('tls_wrap'); const TCP = process.binding('tcp_wrap').TCP; const Pipe = process.binding('pipe_wrap').Pipe; +const isLibreSSL = process.versions.openssl && + /LibreSSL$/.test(process.versions.openssl); function onhandshakestart() { debug('onhandshakestart'); @@ -155,14 +157,20 @@ function onclienthello(hello) { if (err) return self.destroy(err); - self._handle.endParser(); + if (isLibreSSL) { + oncertcb(hello, self, session && session.servername || hello.servername); + } else { + self._handle.endParser(); + } }); } -function oncertcb(info) { - var self = this; - var servername = info.servername; +function oncertcb(info, self, servername) { + if (!self) + self = this; + if (!servername) + servername = info.servername; loadSNI(self, servername, function(err, ctx) { if (err) @@ -174,10 +182,14 @@ function oncertcb(info) { if (!self._handle) return self.destroy(new Error('Socket is closed')); - try { - self._handle.certCbDone(); - } catch (e) { - self.destroy(e); + if (isLibreSSL) { + self._handle.endParser(); + } else { + try { + self._handle.certCbDone(); + } catch (e) { + self.destroy(e); + } } }); }); @@ -1072,13 +1084,15 @@ exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { socket.on('secure', function() { // Check the size of DHE parameter above minimum requirement // specified in options. - var ekeyinfo = socket.getEphemeralKeyInfo(); - if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) { - var err = new Error('DH parameter size ' + ekeyinfo.size + - ' is less than ' + options.minDHSize); - socket.emit('error', err); - socket.destroy(); - return; + if (!isLibreSSL) { + var ekeyinfo = socket.getEphemeralKeyInfo(); + if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) { + var err = new Error('DH parameter size ' + ekeyinfo.size + + ' is less than ' + options.minDHSize); + socket.emit('error', err); + socket.destroy(); + return; + } } var verifyError = socket._handle.verifyError(); diff --git a/node.gyp b/node.gyp index 673a1d10effaa8..9273eafb58e38f 100644 --- a/node.gyp +++ b/node.gyp @@ -381,7 +381,7 @@ 'conditions': [ # -force_load or --whole-archive are not applicable for # the static library - [ 'node_target_type!="static_library"', { + [ 'node_target_type!="static_library" and node_shared_openssl=="false"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '-Wl,-force_load,<(PRODUCT_DIR)/<(OPENSSL_PRODUCT)', diff --git a/src/node.cc b/src/node.cc index 525f28c1148e40..f22b68a5deefce 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3126,10 +3126,13 @@ void SetupProcessObject(Environment* env, break; } } - READONLY_PROPERTY( - versions, - "openssl", - OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i)); + Local sslversion = + OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i); +# ifdef LIBRESSL_VERSION_NUMBER + sslversion = String::Concat(sslversion, + OneByteString(env->isolate(), "-LibreSSL")); +# endif + READONLY_PROPERTY(versions, "openssl", sslversion); } #endif diff --git a/src/node_crypto.cc b/src/node_crypto.cc index f959f0a33c46a9..f18117a41c2036 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -523,8 +523,12 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, for (int i = 0; i < sk_X509_num(extra_certs); i++) { X509* ca = sk_X509_value(extra_certs, i); +#ifdef LIBRESSL_VERSION_NUMBER + r = SSL_CTX_add_extra_chain_cert(ctx, ca); +#else // NOTE: Increments reference count on `ca` r = SSL_CTX_add1_chain_cert(ctx, ca); +#endif // LIBRESSL_VERSION_NUMBER if (!r) { ret = 0; @@ -680,7 +684,7 @@ void SecureContext::SetCert(const FunctionCallbackInfo& args) { } -#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) +#if (OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)) || defined(LIBRESSL_VERSION_NUMBER) // This section contains OpenSSL 1.1.0 functions reimplemented for OpenSSL // 1.0.2 so that the following code can be written without lots of #if lines. @@ -693,7 +697,7 @@ static int X509_up_ref(X509* cert) { CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509); return 1; } -#endif // OPENSSL_VERSION_NUMBER < 0x10100000L && !OPENSSL_IS_BORINGSSL +#endif // (OPENSSL_VERSION_NUMBER < 0x10100000L && !OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) static X509_STORE* NewRootCertStore() { @@ -1153,7 +1157,7 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { void SecureContext::SetFreeListLength(const FunctionCallbackInfo& args) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) // |freelist_max_len| was removed in OpenSSL 1.1.0. In that version OpenSSL // mallocs and frees buffers directly, without the use of a freelist. SecureContext* wrap; @@ -1930,6 +1934,10 @@ void SSLWrap::RequestOCSP( template void SSLWrap::GetEphemeralKeyInfo( const v8::FunctionCallbackInfo& args) { +#ifdef LIBRESSL_VERSION_NUMBER + Environment* env = Environment::GetCurrent(args); + env->ThrowError("getEphemeralKeyInfo() not supported when using LibreSSL"); +#else Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = Environment::GetCurrent(args); @@ -1968,7 +1976,8 @@ void SSLWrap::GetEphemeralKeyInfo( EVP_PKEY_free(key); } - return args.GetReturnValue().Set(info); + args.GetReturnValue().Set(info); +#endif // LIBRESSL_VERSION_NUMBER } @@ -2449,8 +2458,9 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { w->sni_context_.Reset(); w->sni_context_.Reset(env->isolate(), ctx); - int rv; + int rv = 1; +#ifndef LIBRESSL_VERSION_NUMBER // NOTE: reference count is not increased by this API methods X509* x509 = SSL_CTX_get0_certificate(sc->ctx_); EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_); @@ -2463,6 +2473,8 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { rv = SSL_use_PrivateKey(w->ssl_, pkey); if (rv && chain != nullptr) rv = SSL_set1_chain(w->ssl_, chain); +#endif // LIBRESSL_VERSION_NUMBER + if (rv) rv = w->SetCACerts(sc); if (!rv) { @@ -2526,9 +2538,11 @@ void SSLWrap::SetSNIContext(SecureContext* sc) { template int SSLWrap::SetCACerts(SecureContext* sc) { +#ifndef LIBRESSL_VERSION_NUMBER int err = SSL_set1_verify_cert_store(ssl_, SSL_CTX_get_cert_store(sc->ctx_)); if (err != 1) return err; +#endif // LIBRESSL_VERSION_NUMBER STACK_OF(X509_NAME)* list = SSL_dup_CA_list( SSL_CTX_get_client_CA_list(sc->ctx_)); @@ -2841,7 +2855,7 @@ inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { SSL* ssl = static_cast( X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - if (SSL_is_server(ssl)) + if (ssl->server) return 1; // Client needs to check if the server cert is listed in the @@ -2924,7 +2938,9 @@ void Connection::New(const FunctionCallbackInfo& args) { InitNPN(sc); +#ifndef LIBRESSL_VERSION_NUMBER SSL_set_cert_cb(conn->ssl_, SSLWrap::SSLCertCallback, conn); +#endif // LIBRESSL_VERSION_NUMBER #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (is_server) { @@ -5976,11 +5992,11 @@ void SetEngine(const FunctionCallbackInfo& args) { #endif // !OPENSSL_NO_ENGINE void GetFipsCrypto(const FunctionCallbackInfo& args) { - if (FIPS_mode()) { - args.GetReturnValue().Set(1); - } else { - args.GetReturnValue().Set(0); - } +#ifdef NODE_FIPS_MODE + args.GetReturnValue().Set(FIPS_mode()); +#else + args.GetReturnValue().Set(0); +#endif } void SetFipsCrypto(const FunctionCallbackInfo& args) { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index d56128fec6c5ce..4af2913e17b4de 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -142,7 +142,9 @@ void TLSWrap::InitSSL() { InitNPN(sc_); +#ifndef LIBRESSL_VERSION_NUMBER SSL_set_cert_cb(ssl_, SSLWrap::SSLCertCallback, this); +#endif // LIBRESSL_VERSION_NUMBER if (is_server()) { SSL_set_accept_state(ssl_); diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index 2e94397c8f22f9..ed05cf501ce3cc 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -10,6 +10,7 @@ const assert = require('assert'); const crypto = require('crypto'); const fs = require('fs'); const tls = require('tls'); +const isLibreSSL = /LibreSSL$/.test(process.versions.openssl); crypto.DEFAULT_ENCODING = 'buffer'; @@ -82,9 +83,11 @@ assert(tlsCiphers.every((value) => /^[^A-Z]+$/.test(value))); validateList(tlsCiphers); // Assert that we have sha and sha1 but not SHA and SHA1. +<<<<<<< 1824bbbff1341e253a891a804651b6338f8008e4 assert.notStrictEqual(0, crypto.getHashes().length); assert(crypto.getHashes().includes('sha1')); -assert(crypto.getHashes().includes('sha')); +if (!isLibreSSL) + assert(crypto.getHashes().includes('sha')); assert(!crypto.getHashes().includes('SHA1')); assert(!crypto.getHashes().includes('SHA')); assert(crypto.getHashes().includes('RSA-SHA1')); diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js index 7d68046de9baae..2b699a842b0d2b 100644 --- a/test/parallel/test-tls-client-getephemeralkeyinfo.js +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -6,8 +6,14 @@ if (!common.hasCrypto) { common.skip('missing crypto'); process.exit(); } -const tls = require('tls'); +var isLibreSSL = /LibreSSL$/.test(process.versions.openssl); +if (isLibreSSL) { + common.skip('LibreSSL does not support getEphemeralKeyInfo()'); + process.exit(); +} + +const tls = require('tls'); const fs = require('fs'); const key = fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'); const cert = fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem'); diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index a2b480f51e1326..b77a92a45ac676 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -6,8 +6,15 @@ if (!common.hasCrypto) { common.skip('missing crypto'); process.exit(); } -const tls = require('tls'); +const isLibreSSL = /LibreSSL$/.test(process.versions.openssl); + +if (isLibreSSL) { + common.skip('LibreSSL does not support DH key size limiting'); + process.exit(); +} + +const tls = require('tls'); const fs = require('fs'); const key = fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'); const cert = fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem'); diff --git a/test/parallel/test-tls-cnnic-whitelist.js b/test/parallel/test-tls-cnnic-whitelist.js index c2b9c0849296bc..8cd42276838062 100644 --- a/test/parallel/test-tls-cnnic-whitelist.js +++ b/test/parallel/test-tls-cnnic-whitelist.js @@ -10,6 +10,8 @@ const assert = require('assert'); const tls = require('tls'); const fs = require('fs'); const path = require('path'); +const finished = 0; +const isLibreSSL = /LibreSSL$/.test(process.versions.openssl); function filenamePEM(n) { return path.join(common.fixturesDir, 'keys', n + '.pem'); @@ -52,7 +54,8 @@ const testCases = [ port: undefined, rejectUnauthorized: true }, - errorCode: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' + errorCode: isLibreSSL ? 'CERT_UNTRUSTED' : + 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' } ]; diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index e68378fb4eac50..7fffd6f05a58a1 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -1,3 +1,5 @@ +/*eslint max-len: ["error", { "ignoreComments": true }]*/ + 'use strict'; const common = require('../common'); @@ -7,6 +9,12 @@ if (!process.features.tls_sni) { return; } +var isLibreSSL = /LibreSSL$/.test(process.versions.openssl); +if (isLibreSSL) { + common.skip('Test not yet supported with LibreSSL'); + process.exit(); +} + const assert = require('assert'); if (!common.hasCrypto) { @@ -24,6 +32,8 @@ const options = { const server = tls.createServer(options, (c) => { common.fail('Should not be called'); }).on('tlsClientError', common.mustCall((err, c) => { + //TODO: LibreSSL gives: + // 140735295963904:error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher:s3_srvr.c:1038: assert(/SSL_use_certificate:passed a null parameter/i.test(err.message)); server.close(); })).listen(0, common.mustCall(() => { @@ -34,6 +44,8 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { + //TODO: LibreSSL gives: + // 140735295963904:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:441: assert(/socket hang up/.test(err.message)); })); }));