curl cannot handshake https server created with Nodejs v14.16.0 tls.createSecureContext - node.js

I have set up a HTTPS server (nodejs v14.16.0) and certificates from letsEncrypt (which work in the current version of the app that uses https.createServer). Unfortunately, curl cannot connect successfully to my HTTPS server. I get the following error
routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
Here's a minimum reproducible version of my server
const https = require('https')
const tls = require('tls');
const fs = require('fs');
const constants = require('constants');
tls.DEFAULT_ECDH_CURVE = "auto"
require('dotenv').config()
const certOpts = {
key: `${process.env.KEY_PATH}/privkey.pem`,
cert: `${process.env.KEY_PATH}/cert.pem`,
ca: `${process.env.KEY_PATH}/chain.pem`,
};
/**
*
* #param {Record<string, string>} filePathMap
* #returns {Record<string, Buffer>}
*/
function getBuffersFromFilePathMap(filePathMap) {
const bufferMap = {}
for (const path in filePathMap) {
if (Object.hasOwnProperty.call(filePathMap, path)) {
bufferMap[path] = fs.readFileSync(filePathMap[path]);
}
}
return bufferMap;
}
const buffers = getBuffersFromFilePathMap(certOpts);
function createContext() {
return tls.createSecureContext({
...buffers,
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1,
maxVersion:'TLSv1.2'
});
}
const server = https.createServer({ secureContext: createContext() }, (req, res) => {
// eslint-disable-next-line no-console
console.log('connect');
res.writeHead(200);
res.write('Hello World!\n');
res.end('Goodbye World!\n');
});
server.listen(9999, () => console.log('Server up: ', server.address()));
This is the output of curl --version
curl 7.61.1 (x86_64-redhat-linux-gnu) libcurl/7.61.1 OpenSSL/1.0.2k zlib/1.2.8 libidn2/2.3.0 libpsl/0.6.2 (+libicu/50.1.2) libssh2/1.4.2 nghttp2/1.31.1
Release-Date: 2018-09-05
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy PSL
From what I've gathered, ssl3 may not be supported .
Output from curl
$ curl -k https://localhost:9999
curl: (35) error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
Curl traces
== Info: Rebuilt URL to: https://localhost:9999/
== Info: Trying 127.0.0.1...
== Info: TCP_NODELAY set
== Info: Connected to localhost (127.0.0.1) port 9999 (#0)
== Info: ALPN, offering h2
== Info: ALPN, offering http/1.1
== Info: Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:#STRENGTH
== Info: successfully set certificate verify locations:
== Info: CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
== Info: TLSv1.2 (OUT), TLS header, Certificate Status (22):
=> Send SSL data, 5 bytes (0x5)
0000: .....
== Info: TLSv1.2 (OUT), TLS handshake, Client hello (1):
=> Send SSL data, 512 bytes (0x200)
0000: .......*.c....}.5.H>^..e\;)}.zEW.....d....0.,.(.$.............k.
0040: j.i.h.9.8.7.6.........2...*.&.......=.5.../.+.'.#.............g.
0080: #.?.>.3.2.1.0.........E.D.C.B.1.-.).%.......<./...A.............
00c0: ............3.........localhost......................... .......
0100: ..............................3t.........h2.http/1.1............
0140: ................................................................
0180: ................................................................
01c0: ................................................................
== Info: TLSv1.2 (IN), TLS header, Unknown (21):
<= Recv SSL data, 5 bytes (0x5)
0000: .....
== Info: TLSv1.2 (IN), TLS alert, handshake failure (552):
<= Recv SSL data, 2 bytes (0x2)
0000: .(
== Info: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

Okay, it appears that even though the documentation for tls.createSecureContext says the result is "usable as an argument to several tls APIs, such as tls.createServer" it actually isn't. It is accepted by server.addContext (for a virtual host or more exactly an SNI-value handler) tls.connect (for client) tls.createSecurePair (deprecated) and new TLSSocket (low-level), but createServer only takes the same options as createSecureContext not an actual SecureContext. Since you didn't supply the needed key&cert in a usable form, and OpenSSL by default disables anonymous ciphersuites (which most clients don't offer anyway), all handshakes fail with no_shared_cipher. Try:
const server = https.createServer(
{...buffers,
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1,
maxVersion:'TLSv1.2' },
(req, res) => stuff )
BTW the req,res=> function is invoked when you get a request not a connection as such; a proper client will only connect when it needs to do a request, but it is possible to connect without doing a request, and conversely fine to do multiple requests on one connection.

Related

Unable to finish TLS handshake for my express webserver

Here's my webserver, most parts not included in favor of being concise.
server.js
const https = require('https');
const app = require('express')();
const port = 1000;
const options = {
key : getKey(),
cert: getCert(),
ciphers: getCiphers(),
passphrase: "abcd",
rejectUnauthorized: true,
requestCert: true
};
const server = https.createServer(options, app);
server.on('clientError', function (err) {
console.log('received client error');
console.log({err});
})
server.on('connection', function (err) {
console.log('client connected');
})
server.listen(port);
To test my server, I run a connection commands with openssl
openssl s_client -connect localhost:1000 \
-servername localhost \
-CAfile etc/root-cert/ca.cert.pem \
-cert etc/certs/client.cert.pem \
-key etc/private/client.key.pem
This returns the following output, which indicates that the TLS connection was successful (as far as I'm aware):
CONNECTED(00000005)
depth=2 C = US, ST = California, O = Hackysack, CN = Hackysack Root CA
verify return:1
depth=1 C = US, ST = California, O = Hackysack, CN = Hackysack Intermediate CA
verify return:1
depth=0 C = US, ST = California, O = Hackysack, CN = localhost
verify return:1
write W BLOCK
---
Certificate chain
0 s:/C=US/ST=California/O=Hackysack/CN=localhost
i:/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
1 s:/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
i:/C=US/ST=California/O=Hackysack/CN=Hackysack Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
[Long cert file here]
-----END CERTIFICATE-----
subject=/C=US/ST=California/O=Hackysack/CN=localhost
issuer=/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 3400 bytes and written 2061 bytes
---
New, TLSv1/SSLv3, Cipher is AEAD-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.3
Cipher : AEAD-AES128-GCM-SHA256
Session-ID:
Session-ID-ctx:
Master-Key:
Start Time: 1674855496
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
read:errno=0
However, in my server terminal logs I see that I receive a clientError when trying to make this call via HTTPS request.
{
err: Error: socket hang up
at connResetException (internal/errors.js:639:14)
at TLSSocket.onSocketClose (_tls_wrap.js:1063:23)
at TLSSocket.emit (events.js:412:35)
at net.js:686:12
at Socket.done (_tls_wrap.js:564:7)
at Object.onceWrapper (events.js:520:26)
at Socket.emit (events.js:400:28)
at TCP.<anonymous> (net.js:686:12) {
code: 'ECONNRESET'
}
}
Here's my client request:
const https = require('https');
const options = {
hostname: 'localhost',
port: 1000,
method: "POST",
path: "/test",
cert: getClientCert(),
key: getClientKey(),
passphrase: "abcd",
ciphers: getCiphers()
ca: getCA(),
}
const request = https.request(options);
I noticed this error only started happening when I included requestCert: true when I created the server. I think something must be going wrong with my client side for this to be the case. The main problem with debugging this is that I'm not receiving a good error when it fails the handshake.
How can I better debug this issue?

Websockets on Node-server hosted in Azure fails

I have a barebone simple example that works locally but not in Azure. My original question was a bit specific to an cors-error, that i have now worked around, and therefor edited away that part. But i still cant manage to make WS work on azure. And i need help.;
var server = http.Server(app);
const wss = new WebSocket.Server({server});
server.listen(8070, () => {
console.log("Listening on " + port)
});
wss.on('connection', (socket) => {
console.log('a user connected');
socket.on('message', (msg) => {
console.log(msg)
socket.send(JSON.stringify({hey:'you'}))
})
});
client connection-string:
SOCKET_ENDPOINT = 'wss://******.azurewebsites.net/';
I've turned on websockets in my app-configuration.
EDIT:
So i have now turned every stone. Even switched library from socket.io to ws. Same problem. I came a little further i think when trying different things, now i get following error "Firefox can’t establish a connection to the server at wss://*******.azurewebsites.net:8070/."
Edit2:
The http-upgrade is fine
Here's the error
Edit 3: Curl command:
* TCP_NODELAY set
* Connected to *******.azurewebsites.net (20.40.202.6) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: CN=*.azurewebsites.net
* start date: Sep 28 19:00:01 2020 GMT
* expire date: Sep 28 19:00:01 2021 GMT
* subjectAltName: host "********.azurewebsites.net" matched cert's "*.azurewebsites.net"
* issuer: C=US; O=Microsoft Corporation; CN=Microsoft RSA TLS CA 01
* SSL certificate verify ok.
> GET /socket.io/?EIO=4 HTTP/1.1
> Host: *********.azurewebsites.net
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Server: Kestrel
< WWW-Authenticate: Bearer realm="********.azurewebsites.net" authorization_uri="https://login.windows.net/<tenant>/oauth2/authorize" resource_id="<resource>"
< Date: Tue, 22 Jun 2021 05:56:15 GMT
< Content-Length: 0
<
* Connection #0 to host *******.azurewebsites.net left intact
I am guessing that you host your app in Azure AppService?
Make sure you have websocket option turned on in platform:
AppService resource -> Settings -> General settings tab -> Platform settings section

Cloud Run returns 400 Bad Request with Custom Domain

I have previously deployed a lot of services to Cloud Run but this is my first time deploying a NodeJs server. It got successfully deployed and is also working perfectly. The problem arises when I map a custom domain to it. It works with the generated URL but not with the mapped URL. Whenever I hit the mapped URL, it returns 400 Bad Request
My NodeJS code:
const app = require('express')();
const cors = require('cors');
const server = require('http').createServer(app);
// routes
const home = require('./src/routes');
app.use(cors());
app.use('/', home);
const PORT = process.env.PORT || 5000;
server.listen(PORT, '0.0.0.0', () => console.log(`Server listening on port ${PORT}`));
Dockerfile
FROM node:16
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm#5+)
COPY package*.json ./
# If you are building your code for production
RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 5000
CMD [ "node", "server.js" ]
Curl
curl -v https://<subdomain.domain.com>
* Trying 17x.xxx.xxx.xxx:443...
* TCP_NODELAY set
* Connected to <subdomain.domain.com> (17x.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=<subdomain.domain.com>
* start date: May 29 15:31:03 2021 GMT
* expire date: Aug 27 16:31:03 2021 GMT
* subjectAltName: host "<subdomain.domain.com>" matched cert's "<subdomain.domain.com>"
* issuer: C=US; O=Google Trust Services LLC; CN=GTS CA 1D4
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x565480c42e10)
> GET / HTTP/2
> Host: <subdomain.domain.com>
> user-agent: curl/7.68.0
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 400
< content-type: text/html
< x-content-type-options: nosniff
< referrer-policy: same-origin
< x-cloud-trace-context: d0038e2821f61d26f77c95ead4f75d96
< date: Mon, 31 May 2021 19:54:12 GMT
< server: Google Frontend
< content-length: 143
<
<!doctype html>
<html lang="en">
<head>
<title>Bad Request (400)</title>
</head>
<body>
<h1>Bad Request (400)</h1><p></p>
</body>
</html>
* Connection #0 to host <subdomain.domain.com> left intact

axios SSL error with Node 12 : SSL routines:ssl_choose_client_version:unsupported protocol

I’m running into an issue with axios and Node 12. As I’m not sure this error is only related to axios, I followed the advice to ask on SO rather than opening a bug on axios’ GitHub.
Here is the code I’m trying to run :
const axios = require('axios')
axios({
method: 'get',
url: 'https://www.colisprive.com/moncolis/pages/detailColis.aspx?numColis=12345',
responseType: 'text'
}).then((response) => {
console.log(response)
})
This code fails on Node 12 with following error :
Error: write EPROTO 140121214769024:error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol:../deps/openssl/openssl/ssl/statem/statem_lib.c:1929:
at WriteWrap.onWriteComplete [as oncomplete] (internal/stream_base_commons.js:87:16)
Same code ran against Node 11 doesn’t throw any error.
When I curl -v I got this :
* Trying 91.208.224.32:443...
* TCP_NODELAY set
* Connected to www.colisprive.com (91.208.224.32) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: none
CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: serialNumber=391029345; jurisdictionC=FR; businessCategory=Private Organization; C=FR; postalCode=13290; ST=Bouches-du-Rh�ne; L=AIX EN PROVENCE; street=1330 AV J R G GAUTIER DE LA LAUZIERE; street=ZI MILLES EUROPARC PICHAURY; O=COLIS PRIVE SAS; OU=0002 391029345; CN=www.colisprive.com
* start date: Sep 3 00:00:00 2018 GMT
* expire date: Sep 2 23:59:59 2020 GMT
* subjectAltName: host "www.colisprive.com" matched cert's "www.colisprive.com"
* issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
* SSL certificate verify ok.
> GET /moncolis/pages/detailColis.aspx?numColis=12345 HTTP/1.1
> Host: www.colisprive.com
> User-Agent: curl/7.65.3
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
< Location: /moncolis/Default.aspx?numColis=12345&cp=
< Server: Microsoft-IIS/7.5
< Set-Cookie: ASP.NET_SessionId=eln3cq143d35lfj5tpqkkwcg; path=/; HttpOnly
< X-Powered-By: Colis Priv�
< Date: Fri, 24 Jan 2020 13:48:35 GMT
< Content-Length: 162
<
<html><head><title>Object moved</title></head><body>
<h2>Object moved to here.</h2>
</body></html>
* Connection #0 to host www.colisprive.com left intact
As you can see, it gives a 302 Found with a Location header pointing to another endpoint. I agree it should answer a 301 Moved to indicate document has moved, but this is not the case and it is handled as expected by axios on Node 11 (fetching endpoint under Location header).
I saw that Node 12 now includes TLS 1.3 as default, so this could be related to that…
Also, there is an unknown character in X-Powered-By header.
I tried to :
reproduce this issue with an express server always replying 302 Found with same headers : works as expected
fetch another .aspx web page with axios : works as expected
The problem is not just with axios but with got as well.
Node.js 12's default TLS settings are stricter now. The site doesn't handle TLS v1.2. Node 12 by default need 1.2.
You can change this via a command line flag (--tls-min-v1.0) when running your app.
something like this
node --tls-min-v1.0 app.js

chai Error: unable to verify the first certificate

stack: nodejs4, chai,jdom. The objective is to write a test that interrogates the HTML of a URL in a web application which is only accessible over SSL in the corporate intranet.
I am writing a mocha test using jsdom and I get the SSL certificate error:
{ Error: unable to verify the first certificate
at Error (native)
at TLSSocket.<anonymous> (_tls_wrap.js:1060:38)
at emitNone (events.js:86:13)
at TLSSocket.emit (events.js:185:7)
at TLSSocket._finishInit (_tls_wrap.js:584:8)
at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:416:38)
code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' }
I have the following test code:
var chai = require('chai');
var jsdom = require('jsdom');
var expect = chai.expect;
https = require('https');
fs = require('fs');
var cas = require('ssl-root-cas/latest').inject().addFile('test/ssl/key.pem').addFile('test/ssl/server.crt');
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
https.globalAgent.options.ca = cas;
jsdom.env(
"https://www.example.com",
['https://www.example.com/jquery-1.10.2.js'],
function(err, window) {
if(err){
console.log(err);
} else {
console.log("contents of the research project input box is:", window.$("#field_cell_1840 input.custom-combobox-input").text());
}
}
);
I have generated the keys in test/ssl like this:
openssl req -newkey rsa:2048 -new -nodes -keyout key.pem -out csr.pem
openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out server.crt
I have followed various threads and tried suggestions in https://github.com/coolaj86/node-ssl-root-cas including bad ideas such as the use of process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
When I do a curl -vk https://www.example.com then I get the following output:
ac2f15#UOS-012145 ~/git/NodeJS/test (master)
$ curl -vk https://www.example.com
* Rebuilt URL to: www.example.com
* timeout on name lookup is not supported
* Trying 192.168.168.116...
* Connected to www.example.com (192.168.168.116) port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:#STRENGTH
* successfully set certificate verify locations:
* CAfile: C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.0 (IN), TLS handshake, Server hello (2):
* TLSv1.0 (IN), TLS handshake, Certificate (11):
* TLSv1.0 (IN), TLS handshake, Server key exchange (12):
* TLSv1.0 (IN), TLS handshake, Server finished (14):
* TLSv1.0 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.0 (OUT), TLS change cipher, Client hello (1):
* TLSv1.0 (OUT), TLS handshake, Finished (20):
* TLSv1.0 (IN), TLS change cipher, Client hello (1):
* TLSv1.0 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.0 / DHE-RSA-AES256-SHA
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: OU=Domain Control Validated; CN=sitepublisher-dev.soton.ac.uk
* start date: Dec 9 00:00:00 2014 GMT
* expire date: Dec 8 23:59:59 2017 GMT
* issuer: C=NL; ST=Noord-Holland; L=Amsterdam; O=TERENA; CN=TERENA SSL CA 2
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET / HTTP/1.1
> Host: srv00700.soton.ac.uk
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Mon, 10 Oct 2016 16:09:05 GMT
< Server: Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/0.9.8e-fips-rhel5
< Location: https://www.example.com/
< Content-Length: 358
< Content-Type: text/html; charset=iso-8859-1
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
My question is, is there someone who has done a successful chai test using jdom on a https URL ? If so, could you please share your experience with me?

Resources