Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: initial LibreSSL support (WIP) #9376

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down
44 changes: 29 additions & 15 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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)
Expand All @@ -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();
Copy link
Member Author

@rvagg rvagg Oct 31, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not quite right, a call to certCbDone() is probably appropriate here somewhere too, my original iteration completely removed certCbDone when LibreSSL but the current incarnation only removes pieces of it so it's still usable.

} else {
try {
self._handle.certCbDone();
} catch (e) {
self.destroy(e);
}
}
});
});
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
11 changes: 7 additions & 4 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3126,10 +3126,13 @@ void SetupProcessObject(Environment* env,
break;
}
}
READONLY_PROPERTY(
versions,
"openssl",
OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i));
Local<String> 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

Expand Down
38 changes: 27 additions & 11 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -680,7 +684,7 @@ void SecureContext::SetCert(const FunctionCallbackInfo<Value>& 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.

Expand All @@ -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() {
Expand Down Expand Up @@ -1153,7 +1157,7 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {


void SecureContext::SetFreeListLength(const FunctionCallbackInfo<Value>& 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;
Expand Down Expand Up @@ -1930,6 +1934,10 @@ void SSLWrap<Base>::RequestOCSP(
template <class Base>
void SSLWrap<Base>::GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& 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);
Expand Down Expand Up @@ -1968,7 +1976,8 @@ void SSLWrap<Base>::GetEphemeralKeyInfo(
EVP_PKEY_free(key);
}

return args.GetReturnValue().Set(info);
args.GetReturnValue().Set(info);
#endif // LIBRESSL_VERSION_NUMBER
}


Expand Down Expand Up @@ -2449,8 +2458,9 @@ void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& 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_);
Expand All @@ -2463,6 +2473,8 @@ void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& 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) {
Expand Down Expand Up @@ -2526,9 +2538,11 @@ void SSLWrap<Base>::SetSNIContext(SecureContext* sc) {

template <class Base>
int SSLWrap<Base>::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_));
Expand Down Expand Up @@ -2841,7 +2855,7 @@ inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
SSL* ssl = static_cast<SSL*>(
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
Expand Down Expand Up @@ -2924,7 +2938,9 @@ void Connection::New(const FunctionCallbackInfo<Value>& args) {

InitNPN(sc);

#ifndef LIBRESSL_VERSION_NUMBER
SSL_set_cert_cb(conn->ssl_, SSLWrap<Connection>::SSLCertCallback, conn);
#endif // LIBRESSL_VERSION_NUMBER

#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
if (is_server) {
Expand Down Expand Up @@ -5976,11 +5992,11 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) {
#endif // !OPENSSL_NO_ENGINE

void GetFipsCrypto(const FunctionCallbackInfo<Value>& 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<Value>& args) {
Expand Down
2 changes: 2 additions & 0 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ void TLSWrap::InitSSL() {

InitNPN(sc_);

#ifndef LIBRESSL_VERSION_NUMBER
SSL_set_cert_cb(ssl_, SSLWrap<TLSWrap>::SSLCertCallback, this);
#endif // LIBRESSL_VERSION_NUMBER

if (is_server()) {
SSL_set_accept_state(ssl_);
Expand Down
5 changes: 4 additions & 1 deletion test/parallel/test-crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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'));
Expand Down
8 changes: 7 additions & 1 deletion test/parallel/test-tls-client-getephemeralkeyinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
9 changes: 8 additions & 1 deletion test/parallel/test-tls-client-mindhsize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
5 changes: 4 additions & 1 deletion test/parallel/test-tls-cnnic-whitelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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'
}
];

Expand Down
12 changes: 12 additions & 0 deletions test/parallel/test-tls-empty-sni-context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/*eslint max-len: ["error", { "ignoreComments": true }]*/

'use strict';

const common = require('../common');
Expand All @@ -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) {
Expand All @@ -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(() => {
Expand All @@ -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));
}));
}));