Repository navigation
SECLEVEL set via ciphers option is applied too late in tls.createSecureContext #36655
Description
- Version: v14.9.0
- Platform:
Linux box 5.8.5-arch1-1 #1 SMP PREEMPT Thu, 27 Aug 2020 18:53:02 +0000 x86_64 GNU/Linux - Subsystem: tls
What steps will reproduce the bug?
Creating secure context with a certificate who's key bits are too small for the default openssl SECLEVEL causes the operation to throw a key "too small error" even if the SECLEVEL is overridden to an acceptable value via the ciphers option. For example the following code throws on my system (note requires node-forge to generate the certificate, install it via npm):
const util = require("util");
const tls = require("tls");
const forge = require("node-forge");
async function test() {
// Generate a TLS certificate with too few bits
let keypair = forge.pki.rsa.generateKeyPair(512);
let cert = forge.pki.createCertificate();
cert.publicKey = keypair.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date(cert.validity.notBefore);
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1);
let attrs = [{ name: "commonName", value: "localhost" }];
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.sign(keypair.privateKey);
let certPem = forge.pki.certificateToPem(cert);
let keyPem = forge.pki.privateKeyToPem(keypair.privateKey);
let serverOpts = {
key: keyPem,
cert: certPem,
ciphers: "DEFAULT@SECLEVEL=0",
}
let clientOpts = {
ciphers: "DEFAULT@SECLEVEL=0",
ca: certPem,
};
// Create TLS server, this is the operation that fails.
let server = tls.createServer(serverOpts, socket => {
socket.on("data", (chunk) => {
console.log("server pong");
socket.end("pong");
});
});
server.unref();
await util.promisify(server.listen.bind(server))();
// Try connecting to the server
let addr = `https://localhost:${server.address().port}/`;
let socket = tls.connect(server.address().port, "localhost", clientOpts, () => {
socket.on("data", (chunk) => { });
console.log("client ping");
socket.write("ping");
});
}
test().catch(console.log);With the following error:
Error: error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small
at Object.createSecureContext (_tls_common.js:129:17)
at Server.setSecureContext (_tls_wrap.js:1323:27)
at new Server (_tls_wrap.js:1181:8)
at Object.createServer (_tls_wrap.js:1224:10)
at test (bug.js:33:22)
at Object.<anonymous> (bug.js:51:1)
at Module._compile (internal/modules/cjs/loader.js:1075:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
at Module.load (internal/modules/cjs/loader.js:940:32)
at Function.Module._load (internal/modules/cjs/loader.js:781:14) {
library: 'SSL routines',
function: 'SSL_CTX_use_certificate',
reason: 'ee key too small',
code: 'ERR_SSL_EE_KEY_TOO_SMALL'
}
(if this does not reproduce the issue it might be possible that the system default SECLEVEL is 0, in this case it's possible to reproduce the issue by adding the following to a .cnf file
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
CipherString = DEFAULT:@SECLEVEL=1
And then pointing node to it via the --openssl-config option.)
How often does it reproduce? Is there a required condition?
Whenever the default/configured SECLEVEL for openssl is greater than the one requested via the ciphers and this level is more strict than the certificate used requires.
What is the expected behavior?
Setting SECLEVEL via the ciphers option as Documented in the Ciphers List Format should allow a weaker certificate to be used than the system default SECLEVEL allows.
What do you see instead?
Node.js tries to add the certificate to the secure context before the ciphers option is process, which causes the default SECLEVEL to be used when evaluating the certificate. I know this to be the case as I tested reordering the certificate being added to the security context by using the following monkey patch:
const origCreateSecureContext = tls.createSecureContext;
tls.createSecureContext = function(options) {
if (!options.cert || !options.key) {
return origCreateSecureContext(options);
}
let lessOptions = { ...options };
delete lessOptions.key;
delete lessOptions.cert;
let ctx = origCreateSecureContext(lessOptions);
ctx.context.setCert(options.cert);
ctx.context.setKey(options.key, undefined);
return ctx;
};If this is added to the reproduction code above it no longer throws a key too small error.
Additional information
Being able to set the security level in order to use certificates weaken than the default security setting allows is useful for automated testing. A viable workaround is to set the level through an openssl config file like shown above, but it's a bit of a sledgehammer approach. It looks like there's a SSL_CTX_set_security_level function to directly set the level instead of doing through the ciphers option, which might be a better alternative to setting it via the ciphers option.
Note that the reverse situation also does not work correctly: if SECLEVEL is increased in the ciphers option the certificate is still accepted, but handshakes with the server fails with Error: Client network socket disconnected before secure TLS connection was established.
Activity
targos commentedon Dec 28, 2020
@nodejs/crypto
trentm commentedon Apr 27, 2021
FWIW, I'm hitting:
in tests using the newly released "node:16" docker image because the default SECLEVEL has increased since the "node:14" docker image:
IIUC, without this bug I would have been able to workaround with using
ciphers: "DEFAULT@SECLEVEL=0"in my test code. I have other workarounds available for my testing. I mention this mainly because that SECLEVEL change in the "node:16" docker image might make this bug more prevalent for node reports.@Hornwitser Thanks very much for this bug report.
kumarrishav commentedon Feb 7, 2023
Facing the same issue.
this workaround did the job
kumarrishav commentedon Feb 24, 2023
Do we have a fix for this? Else, it needs to do that workaround patch fix for tls.createSecureContext
kumarrishav commentedon Sep 7, 2023
with node.js 18, When node.js app is a server, it throws the following error (when a client makes a connection) when I try load the cert later
It seems like the server is unable to find the right cipher and unable to finish serverHello
I get the same error mentioned below in node 16 as well if I do the above (i.e load cert after creating securecontext with ciphers). why there is error if i load the cert later
i.e
Error
kumarrishav commentedon Sep 8, 2023
any pointer here @Hornwitser ?
Hornwitser commentedon Sep 9, 2023
I don't know Node.js TLS internals, and they might change and break code relying on them with any update, so I wouldn't try making it work like that if I were you. The original post describes how to set up an OpenSSL config file with SECLEVEL set in it, pass that in with
--openssl-configwhen starting node and it should work.kumarrishav commentedon Sep 11, 2023
@Hornwitser I tried this
node --openssl-config=openssl.cnf index.jsbut still seeingUNCAUGHTEXCEPTION Error: error:0A00018E:SSL routines::ca md too weak. could it be because ciphers are being overridden in node.js tls call in the application and hence these config are not being honored??where
openssl.cnfistried this too
richardlau commentedon Sep 11, 2023
Node.js won't read
openssl_confby default since #43124 -- it will instead look fornodejs_conf. There's a command line option if you'd like Node.js to readopenssl_conf.kumarrishav commentedon Sep 11, 2023
Still the same result @richardlau
node --openssl-shared-config --openssl-config=openssl.cnf index.jsTried this too
node --openssl-config=openssl.cnf index.jskumarrishav commentedon Oct 13, 2023
@Hornwitser @richardlau any idea why does this not work
#49549 (comment)
richardlau commentedon Oct 13, 2023
I do not.
bnoordhuis commentedon Oct 13, 2023
I suspect this is an ordering issue that can be fixed with the patch below. Feel free to take it and turn it into a pull request.
fix the ordering issue nodejs#36655 (comment)
kumarrishav commentedon Oct 14, 2023
@bnoordhuis Thanks for the guidance. Yes, it seems that was the issue. I made the suggested change and built a binary from the source. It seems working now.
raised the PR here: #50186
tls: fix order of setting cipher before setting cert and key
tls: fix order of setting cipher before setting cert and key
tls: fix order of setting cipher before setting cert and key
tls: fix order of setting cipher before setting cert and key