Unable to finish TLS handshake for my express webserver - node.js

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?

Related

Browser returns SSL_ERROR_BAD_CERT_DOMAIN

i've created my own CA and intermediate CA with openssl.
On my Zyxel AP i've created an csr.
I signed the csr through my intermediate CA and imported the server
cert to my zyxel AP.
I've also uploaded the fullchain root CA (CA and Intermediate) to my zyxel AP.
I'm using ubuntu so i've installed fullchain CA in my cerstore (/usr/local/share/ca-certificates | update update-ca-certificates)
I also imported the fullchain CA in my Browser Cert Store (tested with firefox, brave)....
But when i now call up the webgui from my AP i get the follwing message:
SSL_ERROR_BAD_CERT_DOMAIN
This happens for all browsers.
When i check with curl or openssl, everything looks good:
curl -vvI https://zyxel.home.arpa
ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* 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 handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256
.
.
.
SSL certificate verify ok.
openssl s_client -CAfile ca-chain-bundle.cert.pem https://zyxel.home.arpa
New, TLSv1.3, Cipher is TLS_CHACHA20_POLY1305_SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
What can i try to fix this browser issue?
Edit 1:
without CAfile:
openssl s_client -connect zyxel.home.arpa:443
.
.
verify error:num=20:unable to get local issuer certificate
.
verify error:num=21:unable to verify the first certificate
.
Edit 2:
# President James K. Polk, here are the output of the server crt:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 37:5b:25:e6:31:1b:7e:f7:63:14:30:e1:b6:ca:d2:11:a2:83:44:1c
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = DE, ST = Home, O = Home Inc., CN = Home Intermediate-CA, emailAddress = admin#home.sh
Validity
Not Before: Nov 22 20:20:47 2022 GMT
Not After : Nov 22 20:20:47 2023 GMT
Subject: CN = zyxel.home.arpa, C = DE, ST = Home, L = Home, O = Home Inc, OU = IT
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:aa:be:cf:97:ad:fd:da:1a:9b:c4:af:9a:07:ac:
b7:08:bb:e3:eb:b8:d0:dc:fc:34:bf:8c:c8:5b:3f:
37:a7:20:7d:7e:eb:c9:e1:ce:c6:a8:84:2a:a3:35:
74:83:c9:62:94:ad:92:5b:c5:54:99:9e:14:c9:2b:
73:44:75:06:de:d4:dc:13:a6:0c:8c:b6:d9:84:e2:
1e:51:f8:2f:83:3c:62:95:ca:32:39:07:2b:81:41:
f9:88:08:95:fb:f9:c2:10:f0:de:25:b6:e2:83:f0:
f2:86:41:7d:5d:09:91:3b:04:b8:5f:74:b7:f5:2e:
e8:fa:9b:f5:17:a7:ef:d1:45:ed:05:8e:f3:8e:c3:
a1:96:42:9d:dd:ef:2f:03:81:97:b9:c5:df:9c:41:
3b:b6:9d:7b:09:a0:bf:ad:e8:6a:e2:05:ef:2b:fc:
67:4d:5a:dd:a4:ba:7b:58:a8:65:53:08:06:60:00:
08:85:12:34:31:9b:93:27:d2:35:75:00:f0:01:f5:
58:7f:1a:e5:1f:e5:08:8a:14:ca:c4:17:4d:90:ec:
30:7a:38:3e:ad:90:db:08:46:35:c4:6e:a7:8e:81:
77:eb:15:47:50:7e:d1:71:d5:09:38:64:c3:fa:08:
2a:31:a0:bd:87:0b:70:27:b3:42:fe:20:de:b1:be:
87:a9
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Server
Netscape Comment:
OpenSSL Generated Server Certificate
X509v3 Subject Key Identifier:
F5:D6:9B:95:CA:2C:8E:41:92:5F:3E:3E:9C:D5:31:CE:6A:D0:F8:95
X509v3 Authority Key Identifier:
keyid:25:0C:ED:3A:E1:A0:B9:86:A0:FB:43:9F:20:F0:F4:C0:5E:85:C4:D7
DirName:/C=DE/ST=Home/L=Home/O=Home Inc./CN=Home Root-CA/emailAddress=admin#home.sh
serial:01
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
and here's the output ofthe generated csr on the zyxel AP:
Certificate Request:
Data:
Version: 1 (0x0)
Subject: CN = zyxel.home.arpa, C = DE, ST = Home, L = Home, O = Home Inc, OU = IT
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:aa:be:cf:97:ad:fd:da:1a:9b:c4:af:9a:07:ac:
b7:08:bb:e3:eb:b8:d0:dc:fc:34:bf:8c:c8:5b:3f:
37:a7:20:7d:7e:eb:c9:e1:ce:c6:a8:84:2a:a3:35:
74:83:c9:62:94:ad:92:5b:c5:54:99:9e:14:c9:2b:
73:44:75:06:de:d4:dc:13:a6:0c:8c:b6:d9:84:e2:
1e:51:f8:2f:83:3c:62:95:ca:32:39:07:2b:81:41:
f9:88:08:95:fb:f9:c2:10:f0:de:25:b6:e2:83:f0:
f2:86:41:7d:5d:09:91:3b:04:b8:5f:74:b7:f5:2e:
e8:fa:9b:f5:17:a7:ef:d1:45:ed:05:8e:f3:8e:c3:
a1:96:42:9d:dd:ef:2f:03:81:97:b9:c5:df:9c:41:
3b:b6:9d:7b:09:a0:bf:ad:e8:6a:e2:05:ef:2b:fc:
67:4d:5a:dd:a4:ba:7b:58:a8:65:53:08:06:60:00:
08:85:12:34:31:9b:93:27:d2:35:75:00:f0:01:f5:
58:7f:1a:e5:1f:e5:08:8a:14:ca:c4:17:4d:90:ec:
30:7a:38:3e:ad:90:db:08:46:35:c4:6e:a7:8e:81:
77:eb:15:47:50:7e:d1:71:d5:09:38:64:c3:fa:08:
2a:31:a0:bd:87:0b:70:27:b3:42:fe:20:de:b1:be:
87:a9
Exponent: 65537 (0x10001)
Attributes:
Netscape Comment :OpenSSL Generated Certificate
Requested Extensions:
X509v3 Subject Key Identifier:
F5:D6:9B:95:CA:2C:8E:41:92:5F:3E:3E:9C:D5:31:CE:6A:D0:F8:95
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Data Encipherment, Certificate Sign
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Server Authentication
X509v3 Subject Alternative Name:
othername: UPN::zyxel.home.arpa, DNS:zyxel.home.arpa
Signature Algorithm: sha512WithRSAEncryption
Signature Value:
1b:04:05:e6:02:89:4d:1d:67:a4:bb:15:06:20:93:58:c3:2b:
72:3c:e0:39:cd:f0:ea:6e:3a:db:33:02:3e:fa:ca:67:ec:e4:
58:a5:cf:ad:c0:51:b1:bb:0d:22:3f:a5:fc:ac:d1:e9:90:d6:
71:71:5d:dc:56:f1:14:21:cb:a3:53:be:0a:32:43:8f:8a:74:
55:08:e3:1d:44:7d:72:f3:94:7f:1a:99:71:b7:97:be:a6:ff:
a8:cf:95:f5:3b:c3:c6:6c:e3:ef:d9:39:8d:03:17:2e:15:2c:
92:ee:00:88:5f:23:21:a4:ac:27:c6:66:00:a5:5d:89:8d:f3:
87:43:34:35:16:e4:bd:72:38:ba:2c:27:f4:c9:08:22:aa:86:
21:6c:98:9f:4b:2b:7e:5e:6a:aa:5c:19:80:29:32:6d:4f:78:
1d:db:5b:1d:a1:bc:31:86:e9:65:af:ee:30:35:12:ce:d4:18:
b1:06:57:e3:da:f6:63:b0:48:53:64:0a:4b:ca:2a:20:d6:5d:
90:1a:bf:af:bb:d3:18:e8:5e:42:2f:3f:c8:96:20:63:66:9e:
2e:b1:2a:fa:82:f6:ba:d0:d3:7f:e7:a5:5d:f5:3a:fb:a9:b0:
54:a5:0a:14:48:8a:37:1e:e8:32:6c:73:7d:4c:af:dc:21:bc:
24:60:9e:2c
Solution:
i forgot to add the Subject Alt Name part into my openssl.cnf (thanks for the hint President James K. Polk):
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = #alt_names
[alt_names]
IP.1 = IP
DNS.1 = DNS NAME

Axios/HTTPS certificate expired error only on Windows

I am currently working on a VSCode extension that internally makes some API calls to a HTTPS protected endpoint. On Linux and Mac OS the extension works as expected but on Windows machines axios, the internal HTTP client used to make the API calls, is rejecting those requests due to the certificates being expired. When I access the API endpoint though via Firefox, Chrome and even Edge the certificates seems find.
I have upgraded Node to 16.14.0 and also to 17.6.0 but the problem still remains. As the API is only accessible through our VPN, with my VPN activated of course, I used testssl.sh to verify that the whole trust-chain is still valid:
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 not offered
TLS 1.1 not offered
TLS 1.2 offered (OK)
TLS 1.3 offered (OK): final
NPN/SPDY not offered
ALPN/HTTP2 h2, http/1.1 (offered)
Testing cipher categories
NULL ciphers (no encryption) not offered (OK)
Anonymous NULL Ciphers (no authentication) not offered (OK)
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4] (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered
Obsolete CBC ciphers (AES, ARIA etc.) offered
Strong encryption (AEAD ciphers) offered (OK)
Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4
PFS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA
DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES128-SHA DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA
Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519 X448
DH group offered: HAProxy (2048 bits)
Testing server preferences
Has server cipher order? yes (OK) -- TLS 1.3 and below
Negotiated protocol TLSv1.3
Negotiated cipher TLS_AES_256_GCM_SHA384, 253 bit ECDH (X25519)
Cipher order
TLSv1.2: ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA
TLSv1.3: TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256
...
Certificate Validity (UTC) 65 >= 30 days (2022-02-01 08:18 --> 2022-05-02 08:18)
...
The other certificates in the trustchain are also valid and not expired at all.
Axios is used within an own HttpClient class that looks like this:
export class HttpClient {
private readonly instance: AxiosInstance;
constructor() {
this.instance = axios.create();
}
async asyncGet<T>(url: string): Promise<Response<T>> {
return this.instance.get<T>(url)
.then(axiosResponse => {
...
})
.catch(error => {
console.error(error);
throw error;
});
}
}
I also tried the https
const options: https:RequestOptions = {
hostname: '...',
port: 443,
path: '/...',
method: 'GET'
};
const request = https.request(url, options, (res: IncomingMessage) => {
console.log(`statusCode: ${res.statusCode ? res.statusCode : 'undefined'}`);
res.on('data', d => {
process.stdout.write(d as string);
}
});
request.on('error', error => {
console.error(error);
};
request.end();
analog to the samples given in the NodeJS documentation and tls
const socket = tls.connect(443, hostname, undefined, () => {
console.log('Connected to ' + hostname);
console.log('TLS.connect', socket.authorized ? 'authorized' : 'unauthorized');
console.log('Cipher: ' + JSON.stringify(socket.getCipher()));
console.log('Protocol: ' + JSON.stringify(socket.getProtocol()));
const peerCert: tls.DetailedPeerCertificate = socket.getPeerCertificate(true);
console.log(`Peer-Cert ${JSON.stringify(peerCert.subject)} - valid from: ${peerCert ? peerCert.valid_from : 'invalid'} valid till: ${peerCert ? peerCert.valid_to : 'invalid'}`);
const issuerCert = peerCert.issuerCertificate;
console.log(`issuer-Cert ${JSON.stringify(issuerCert.subject)} - valid from: ${issuerCert ? issuerCert.valid_from : 'invalid'} valid till: ${issuerCert ? issuerCert.valid_to : 'invalid'}`);
const rootCert = issuerCert.issuerCertificate;
console.log(`root-Cert ${JSON.stringify(rootCert.subject)} - valid from: ${rootCert ? rootCert.valid_from : 'invalid'} valid till: ${rootCert ? rootCert.valid_to : 'invalid'}`);
});
as proposed in this answer.
Both axios and https return an error like this:
{
"message": "certificate has expired",
"name": "Error",
"stack": "Error: certificate has expired\n\tat TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)\n\tat TLSSocket.emit (events.js:315:20)\n\tat TLSSocket._finishInit (_tls_wrap.js:932:8)\n\tat TLSWrap.onhandshakedone (_tls_wrap.js:706:12)\n\tat TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"transformRequest": [
"null"
],
"transformResponse": [
"null"
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"headers": {
"Accept": "application/json, text/plain, */*",
"user-Agent": "axios/0.26.0"
},
"method": "get",
"url": "https://..."
},
"code": "CERT_HAS_EXPIRED",
"status": null
}
with a more human-readable stacktrace:
Error: certificate has expired
at TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)
at TLSSocket.emit (events.js:315:20)
at TLSSocket._finishInit (_tls_wrap.js:932:8)
at TLSWrap.onhandshakedone (_tls_wrap.js:706:12)
at TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)
while for tls I get the following output:
Connected to ...
TLS.connect authorized
Cipher: {"name":"TLS_CHACHA20_POLY1305_SHA256","standardName":"TLS_CHACHA20_POLY1305_SHA256","version":"TLSv1/SSLv3"}
Protocol: "TLSv1.3"
Peer-Cert {"CN": "*...."} - valid from: Feb 1 08:18:23 2022 GMT valid till: May 2 08:18:22 2022 GMT
issuer-Cert {"C":"US","O":"Let's Encrypt","CN":"R3"} - valid from Sep 4 00:00:00 2020 GMT valid till: Sep 15 16:00:00 2025 GMT
root-Cert {"C":"US","O":"Internet Security Research Group","CN":"ISRG Root X1"} - valid from: Jan 20 19:14:03 2021 GMT valid till: Sep 30 18:14:03 2024 GMT
So tls seems to be able to connect to the API server and perform the SSL/TLS handshake, but https and axios somehow fail.
I also stumbled upon this question here, which seems to be related, but as I am already on the latest NodeJS release (as well as any dependency used in the extension is on the most recent version) and this error only occurs on Windows (mostly 10, unsure if and how many users actually use Windows 11) machines I think the question deserves its own spot here on SO.
In order to rule out a lack of common supported ciphers between the Windows and Node.js based tls implementation and the nginx managed server side, I also checked the available ciphers in Node via node -p crypto.constants.defaultCoreCipherList which returns a list like this:
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
DHE-RSA-AES256-SHA384
ECDHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA256
HIGH
!aNULL
!eNULL
!EXPORT
!DES
!RC4
!MD5
!PSK
!SRP
!CAMELLIA
which indicates that enough ciphers would overlap between client and server.
Why do I still get a certificate expired error on Windows machines with axios/https
when Linux and MacOS work just fine with these settings and tls is able to connect to the remote API sucessfully even on Windows machines?

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

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.

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?

Client certificate validation on server side, DEPTH_ZERO_SELF_SIGNED_CERT error

I'm using node 0.10.26 and trying to establish https connection with client validation.
Server's code:
var https = require('https');
var fs = require('fs');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var options = {
key: fs.readFileSync('ssl/server1.key'),
cert: fs.readFileSync('ssl/server1.pem'),
requestCert: true,
rejectUnauthorized: false,
};
var server = https.createServer(options, function (req, res) {
if (req.client.authorized) {
res.writeHead(200, {"Content-Type":"application/json"});
res.end('{"status":"approved"}');
console.log("Approved Client ", req.client.socket.remoteAddress);
} else {
console.log("res.connection.authroizationError: " + res.connection.authorizationError);
res.writeHead(403, {"Content-Type":"application/json"});
res.end('{"status":"denied"}');
console.log("Denied Client " , req.client.socket.remoteAddress);
}
});
server.on('error', function(err) {
console.log("server.error: " + err);
});
server.on("listening", function () {
console.log("Server listeining");
});
server.listen(5678);
Client's code:
var https = require('https');
var fs = require('fs');
var options = {
host: 'localhost',
port: 5678,
method: 'GET',
path: '/',
headers: {},
agent: false,
key: fs.readFileSync('ssl/client2.key'),
cert: fs.readFileSync('ssl/client2.pem'),
ca: fs.readFileSync('ssl/ca.pem')
};
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var req = https.request(options, function(res) {
console.log(res.req.connection.authorizationError);
});
req.on("error", function (err) {
console.log('error: ' + err);
});
req.end();
I've created certificates with following commands, each time providing result of "uname -n" as "Common Name":
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -days 999 -out ca.pem
openssl genrsa -out server1.key 1024
openssl req -new -key server1.key -out server1.csr
openssl x509 -req -days 999 -in server1.csr -CA ca.pem -CAkey ca.key -set_serial 01 -out server1.pem
openssl genrsa -out client1.key 1024
openssl req -new -key client1.key -out client1.csr
openssl x509 -req -days 999 -in client1.csr -CA ca.pem -CAkey ca.key -set_serial 01 -out client1.pem
openssl genrsa -out server2.key 1024
openssl req -new -key server2.key -out server2.csr
openssl x509 -req -days 999 -in server2.csr -CA server1.pem -CAkey server1.key - set_serial 02 -out server2.pem
openssl genrsa -out client2.key 1024
openssl req -new -key client2.key -out client2.csr
openssl x509 -req -days 999 -in client2.csr -CA client1.pem -CAkey client1.key -set_serial 02 -out client2.pem
I've run client and server with all compbinations of client's and server's certificates (that is: [(server1, client1), (server1, client2), (server2, client1), (server2, client2)] and for each combination of those server was tested both with default value of "agent" field and with "agent" set to "false".
Each time I ran client.js, res.req.connection.authorizationError was set to DEPTH_ZERO_SELF_SIGNED_CERT.
How can I establish secure connection in node with client's certificate authentication?
I believe you have two problems, one with your code and one with your certificates.
The code issue is in your server. You are not specifying the CA to check client certificates with an options property like you have in your client code:
ca: fs.readFileSync('ssl/ca.pem'),
The second problem is the one that really causes that DEPTH_ZERO_SELF_SIGNED_CERT error. You are giving all your certificates - CA, server, and client - the same Distinguished Name. When the server extracts the issuer information from the client certificate, it sees that the issuer DN is the same as the client certificate DN and concludes that the client certificate is self-signed.
Try regenerating your certificates, giving each one a unique Common Name (to make the DN also unique). For example, name your CA certificate "Foo CA", your server certificate the name of your host ("localhost" in this case), and your client something else (e.g. "Foo Client 1").
For those of that want to use a self-signed certificate, the answer is to add rejectUnauthorized: false to the https.request options.
This one worked for me:
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
Note: Posting answer so it can help others in future.
Just add strictSSL: false to your options.
Despite of long lines of description in this page, I still got 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' error in the client side with this recipe. Maybe I had something wrong in following rhashimoto's comment.
By referencing book of 'Professional Node.js', I found another way to test HTTPS with Client Certificate Verification successfully.
Here is my story.
By setting requestCert: true in server side, the server tries to validate client certificate. But the default CA doesn't validate the self-signed certificate of the client. I can succeed the test with simple trick -- copy the client certificate and say that is a Certificate Authority.
I reused original code and modified it slightly to make it work.
The big difference is in creating certificate files.
Creating certificate files:
# create client private key
openssl genrsa -out client2.key
openssl req -new -key client2.key -out client2.csr
# create client certificate
openssl x509 -req -in client2.csr -signkey client2.key -out client2.pem
# create server private key and certificate
openssl genrsa -out server1.key
openssl req -new -key server1.key -out server1.csr
openssl x509 -req -in server1.csr -signkey server1.key -out server1.pem
# * Important *: create fake CA with client certificate for test purpose
cp client2.pem fake_ca.pem
Server code:
var options = {
key: fs.readFileSync('ssl/server1.key'),
cert: fs.readFileSync('ssl/server1.pem'),
ca: [fs.readFileSync('ssl/fake_ca.pem')], // Line added
requestCert: true,
rejectUnauthorized: false,
};
Client code:
key: fs.readFileSync('ssl/client2.key'),
cert: fs.readFileSync('ssl/client2.pem'),
//ca: fs.readFileSync('ssl/ca.pem') // Line commented
};
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var req = https.request(options, function(res) {
//console.log(res.req.connection.authorizationError);
console.log("statusCode:", res.statusCode);
res.on('data', function(d) {
console.log('data:', d.toString());
});
});
req.on("error", function (err) {
console.log('error: ' + err);
});
req.end();
As mentioned above, there is a sledgehammer to hammer in your nail, using rejectUnauthorized: false.
A more sensible option, from a security point of view, would be to ask the user if (s)he would like to accept and store the self-signed server certificate, just like a browser (or SSH) does.
That would require:
(1) That NodeJS throws an exception that contains the server certificate, and
(2) that the application calls https.request with the stored certificate in the ca: property (see above for description of ca) after the certificate has been manually accepted.
It seems that NodeJS does not do (1), making (2) impossible?
Even better from a security point of view would be to use EFF's SSL observatory to make a crowd-sourced judgement on the validity of a self-signed certificate. Again, that requires NodeJS to do (1).
I think a developer needs to improve NodeJS with respect to (1)...
If you have only a .pem self-signed certificate (e.g. /tmp/ca-keyAndCert.pem) the following options will work:
var options = {
url: 'https://<MYHOST>:<MY_PORT>/',
key: fs.readFileSync('/tmp/ca-keyAndCert.pem'),
cert: fs.readFileSync('/tmp/ca-keyAndCert.pem'),
requestCert: false,
rejectUnauthorized: false
};

Resources