Node.js HTTP2 server Error: socket hang up - node.js

Given the latest version of Node.js with experimental HTTP2 support:
$ node -v
v9.2.0
An HTTP2 server:
var options = {
key: getKey(),
cert: getCert(),
allowHTTP1: true
}
var server = http2.createSecureServer(options)
server.on('stream', onstream)
server.on('error', onerror)
server.on('connect', onconnect)
server.on('socketError', onsocketerror)
server.on('frameError', onframeerror)
server.on('remoteSettings', onremotesettings)
server.listen(8443)
function onconnect() {
console.log('connect')
}
function onremotesettings(settings) {
console.log('remote settings', settings)
}
function onframeerror(error) {
console.log('frame error', error)
}
function onsocketerror(error) {
console.log('socket error', error)
}
function onerror(error) {
console.log(error)
}
function onstream(stream, headers) {
console.log('stream')
}
And a request made to it:
var https = require('https')
var options = {
method: 'GET',
hostname: 'localhost',
port: '8443',
path: '/',
protocol: 'https:',
rejectUnauthorized: false,
agent: false
}
var req = https.request(options, function(res){
var body = ''
res.setEncoding('utf8')
res.on('data', function(data){
body += data;
});
res.on('end', function(){
callback(null, body)
})
})
req.end()
It just hangs and eventually says:
Error: socket hang up
at createHangUpError (_http_client.js:330:15)
at TLSSocket.socketOnEnd (_http_client.js:423:23)
at TLSSocket.emit (events.js:164:20)
at endReadableNT (_stream_readable.js:1054:12)
at _combinedTickCallback (internal/process/next_tick.js:138:11)
at process._tickCallback (internal/process/next_tick.js:180:9)
If rejectUnauthorized: true is set, then it errors:
Error: self signed certificate
at TLSSocket.onConnectSecure (_tls_wrap.js:1036:34)
at TLSSocket.emit (events.js:159:13)
at TLSSocket._finishInit (_tls_wrap.js:637:8)
Not sure what is going wrong and why it won't get to the point of logging stream.
If I go in the browser and visit https://localhost:8443, and click through the warning messages, it does actually log stream and successfully make the request. But haven't been able to get node to make the request.
I would like to treat this as an HTTP1 server, so don't want to use the HTTP2 client to make the request. But tried using that and same thing.

HTTP/1 doesn't share the same request semantics as HTTP/2 so HTTP/1 clients need to be detected and handled in a HTTP/2 server. To support both you need to use the HTTP2 Compatibility API.
The "hang" occurs when a HTTP1 client connects to a HTTP/2 server with allowHTTP1: true set but doesn't handle the HTTP/1 request.
The examples are based on the Node documentation example code.
HTTP/1 and /2 Mixed Server
const http2 = require('http2')
const fs = require('fs')
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
//ca: fs.readFileSync('ca-crt.pem'),
allowHTTP1: true,
}
var server = http2.createSecureServer(options, (req, res) => {
// detects if it is a HTTPS request or HTTP/2
const { socket: { alpnProtocol } } = (req.httpVersion === '2.0')
? req.stream.session
: req
res.writeHead(200, { 'content-type': 'application/json' })
res.end(JSON.stringify({
alpnProtocol,
httpVersion: req.httpVersion
}))
})
server.listen(8443)
HTTP/2 Client
const http2 = require('http2')
const fs = require('fs')
const client = http2.connect('https://localhost:8443', {
ca: fs.readFileSync('ca-crt.pem'),
rejectUnauthorized: true,
})
client.on('socketError', (err) => console.error(err))
client.on('error', (err) => console.error(err))
const req = client.request({ ':path': '/' })
req.on('response', (headers, flags) => {
for (const name in headers) {
console.log('Header: "%s" "%s"', name, headers[name])
}
})
req.setEncoding('utf8')
let data = ''
req.on('data', chunk => data += chunk)
req.on('end', () => {
console.log('Data:', data)
client.destroy()
})
req.end()
Then running:
→ node http2_client.js
(node:34542) ExperimentalWarning: The http2 module is an experimental API.
Header: ":status" "200"
Header: "content-type" "application/json"
Header: "date" "Sat, 02 Dec 2017 23:27:21 GMT"
Data: {"alpnProtocol":"h2","httpVersion":"2.0"}
HTTP/1 Client
const https = require('https')
const fs = require('fs')
var options = {
method: 'GET',
hostname: 'localhost',
port: '8443',
path: '/',
protocol: 'https:',
ca: fs.readFileSync('ca-crt.pem'),
rejectUnauthorized: true,
//agent: false
}
var req = https.request(options, function(res){
var body = ''
res.setEncoding('utf8')
res.on('data', data => body += data)
res.on('end', ()=> console.log('Body:', body))
})
req.on('response', response => {
for (const name in response.headers) {
console.log('Header: "%s" "%s"', name, response.headers[name])
}
})
req.end()
Then running
→ node http1_client.js
Header: "content-type" "application/json"
Header: "date" "Sat, 02 Dec 2017 23:27:08 GMT"
Header: "connection" "close"
Header: "transfer-encoding" "chunked"
Body: {"alpnProtocol":false,"httpVersion":"1.1"}
HTTP/2 Server
Using the plain HTTP/2 Server will work with the http2_client but will "hang" for a http1_client. The TLS connection from a HTTP/1 client will be closed when you remove allowHTTP1: true.
const http2 = require('http2')
const fs = require('fs')
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-crt.pem'),
ca: fs.readFileSync('ca-crt.pem'),
allowHTTP1: true,
}
var server = http2.createSecureServer(options)
server.on('error', error => console.log(error))
server.on('connect', conn => console.log('connect', conn))
server.on('socketError', error => console.log('socketError', error))
server.on('frameError', error => console.log('frameError', error))
server.on('remoteSettings', settings => console.log('remote settings', settings))
server.on('stream', (stream, headers) => {
console.log('stream', headers)
stream.respond({
'content-type': 'application/html',
':status': 200
})
console.log(stream.session)
stream.end(JSON.stringify({
alpnProtocol: stream.session.socket.alpnProtocol,
httpVersion: "2"
}))
})
server.listen(8443)
Certs
With the extended intermediate certificate setup detailed in the gist, the complete certificate chain for the CA needs to be supplied to the clients.
cat ca/x/certs/x.public.pem > caxy.pem
cat ca/y/certs/y.public.pem >> caxy.pem
Then in the clients use this ca in the options.
{
ca: fs.readFileSync('caxy.pem'),
}
These examples were run withe the following simple CA setup from this circle.com article:
To simplify the configuration, let’s grab the following CA
configuration file.
wget https://raw.githubusercontent.com/anders94/https-authorized-clients/master/keys/ca.cnf
Next, we’ll create a new certificate authority using this
configuration.
openssl req -new -x509 \
-days 9999 \
-config ca.cnf \
-keyout ca-key.pem \
-out ca-crt.pem
Now that we have our certificate authority in ca-key.pem and
ca-crt.pem, let’s generate a private key for the server.
openssl genrsa \
-out server-key.pem \
4096
Our next move is to generate a certificate signing request. Again to
simplify configuration, let’s use server.cnf as a configuration
shortcut.
wget https://raw.githubusercontent.com/anders94/https-authorized-clients/master/keys/server.cnf
Now we’ll generate the certificate signing request.
openssl req -new \
-config server.cnf \
-key server-key.pem \
-out server-csr.pem
Now let’s sign the request.
openssl x509 -req -extfile server.cnf \
-days 999 \
-passin "pass:password" \
-in server-csr.pem \
-CA ca-crt.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out server-crt.pem

Related

What is similar to -k / --insecure of CURL for Nodejs? [duplicate]

I'm working on a little app that logs into my local wireless router (Linksys) but I'm running into a problem with the router's self-signed ssl certificate.
I ran wget 192.168.1.1 and get:
ERROR: cannot verify 192.168.1.1's certificate, issued by `/C=US/ST=California/L=Irvine/O=Cisco-Linksys, LLC/OU=Division/CN=Linksys/emailAddress=support#linksys.com':
Self-signed certificate encountered.
ERROR: certificate common name `Linksys' doesn't match requested host name `192.168.1.1'.
To connect to 192.168.1.1 insecurely, use `--no-check-certificate'.
In node, the error being caught is:
{ [Error: socket hang up] code: 'ECONNRESET' }
My current sample code is:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
method: 'GET'
}, function(res){
var body = [];
res.on('data', function(data){
body.push(data);
});
res.on('end', function(){
console.log( body.join('') );
});
});
req.end();
req.on('error', function(err){
console.log(err);
});
How can I go about getting node.js to do the equivalent of "--no-check-certificate"?
Cheap and insecure answer:
Add
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
in code, before calling https.request()
A more secure way (the solution above makes the whole node process insecure) is answered in this question
In your request options, try including the following:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
method: 'GET',
rejectUnauthorized: false,
requestCert: true,
agent: false
},
Don't believe all those who try to mislead you.
In your request, just add:
ca: [fs.readFileSync([certificate path], {encoding: 'utf-8'})]
If you turn on unauthorized certificates, you will not be protected at all (exposed to MITM for not validating identity), and working without SSL won't be a big difference. The solution is to specify the CA certificate that you expect as shown in the next snippet. Make sure that the common name of the certificate is identical to the address you called in the request(As specified in the host):
What you will get then is:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
ca: [fs.readFileSync([certificate path], {encoding: 'utf-8'})],
method: 'GET',
rejectUnauthorized: true,
requestCert: true,
agent: false
},
Please read this article (disclosure: blog post written by this answer's author) here in order to understand:
How CA Certificates work
How to generate CA Certs for testing easily in order to simulate production environment
Add the following environment variable:
NODE_TLS_REJECT_UNAUTHORIZED=0
e.g. with export:
export NODE_TLS_REJECT_UNAUTHORIZED=0
(with great thanks to Juanra)
Adding to #Armand answer:
Add the following environment variable:
NODE_TLS_REJECT_UNAUTHORIZED=0 e.g. with export:
export NODE_TLS_REJECT_UNAUTHORIZED=0 (with great thanks to Juanra)
If you on windows usage:
set NODE_TLS_REJECT_UNAUTHORIZED=0
Thanks to: #weagle08
You can also create a request instance with default options:
require('request').defaults({ rejectUnauthorized: false })
For meteorJS you can set with npmRequestOptions.
HTTP.post(url, {
npmRequestOptions: {
rejectUnauthorized: false // TODO remove when deploy
},
timeout: 30000, // 30s
data: xml
}, function(error, result) {
console.log('error: ' + error);
console.log('resultXml: ' + result);
});
try
export NODE_TLS_REJECT_UNAUTHORIZED=0
Or you can try to add in local name resolution (hosts file found in the directory etc in most operating systems, details differ) something like this:
192.168.1.1 Linksys
and next
var req = https.request({
host: 'Linksys',
port: 443,
path: '/',
method: 'GET'
...
will work.
So, my company just switched to Node.js v12.x.
I was using NODE_TLS_REJECT_UNAUTHORIZED, and it stopped working.
After some digging, I started using NODE_EXTRA_CA_CERTS=A_FILE_IN_OUR_PROJECT that has a PEM format of our self signed cert and all my scripts are working again.
So, if your project has self signed certs, perhaps this env var will help you.
Ref: https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file
In case you are looking for posting using #nestjs/axios,
here is the syntax without certificate (Non Production Solution):
const token = Buffer.from(`${user}:${password}`,'utf8').toString('base64')
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${token}`,
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
};
const responseData = await firstValueFrom(
this.httpService.post(url, data, config).pipe(map((response) => response.data)),
);
here is the syntax with certificate (Production Solution):
const token = Buffer.from(`${user}:${password}`,'utf8').toString('base64')
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${token}`,
},
httpsAgent: new https.Agent({
rejectUnauthorized: true,
ca: fs.readFileSync(path.join(__dirname, './resources/certificateName'))
}),
};
const responseData = await firstValueFrom(
this.httpService.post(url, data, config).pipe(map((response) => response.data)),
);
When you cannot control the request creation
When using packages you sometimes don't have the option to set the correct settings on the request call, nor does the package offer you a way to inject a request.
However you might still want to avoid the insecure NODE_TLS_REJECT_UNAUTHORIZED=0 and opt for only having an insecure connection to a specified target.
This is how I solved the issue:
// check if host and port fit your application
function isSelf(host, port) {
return host === myHost && port === myPort;
}
// get the built in tls module and overwrite the default connect behavior
const tls = require("tls");
const _connect = tls.connect;
function wrappedConnect(options, secureConnectListener) {
if (isSelf(options.host, options.port)) {
options.rejectUnauthorized = false;
}
return _connect(options, secureConnectListener);
}
tls.connect = wrappedConnect;

How do I get key and cert from --capath

With OpenSSL and adding CA have curl -X GET https://someserver.com -I --capath /etc/ssl/certs/ working fine. curl -X GET https://someserver.com -I --cacert /etc/ssl/certs/someserver.pem works as well.
I would like to make the same call with node.js https.request()
I've tried node --use-openssl-ca app.js with SSL_CERT_DIR set to /etc/ssl/certs/ - didn't help.
Tried:
const https = require('https');
const fs = require('fs');
const options = {
hostname: 'someserver.com',
port: 443,
path: '/',
method: 'GET',
ca: fs.readFileSync('/etc/ssl/certs/someserver.pem')
}
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
res.on('data', d => {
process.stdout.write(d)
})
})
req.on('error', error => {
console.error(error)
})
req.end()
Error: write EPROTO 140249139050368:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1544:SSL alert number 40
Thank you.
Just rebuild node.js with openssl support. So now I use the same libs as curl.
Thank you everyone

How to fix server connection using https client with basic auth and tls pfx certificate?

I am trying connect third part server with basic auth and pfx tls certificate using node express platform. I am not sure; Where do I need to set basic auth, cert file and header params?
For get operation call from remote server, I have tried to use, https client and tls tool.
Request requires ;
header = basic auth,schemaVersion, id, applicationName
In addition to sending the basic auth credentials, send the following in the HTTP Header: Content-Type: application/json
Accept: application/json
and also request require one additional parameter i.e. categories
onst https = require('https');
const options = {
hostname: 'https://yadda/cards/getAssets',
headers: {
Authorization: 'Basic ' + new Buffer(username + ':' + passw).toString('base64'),
schemaVersion: '2.0.0',
id: '0000000',
applicationName: 'yadda',
Accept: 'application/json',
'Content-Type': 'application/json',
},
pfx: fs.readFileSync('/somefile.cert.pfx'),
passphrase: 'passphrase',
categories: 'food',
};
https
.get(options, (response: any) => {
response.on('data', (d: any) => {
console.log(`BODY: `, d);
});
})
.on('error', (e: any) => {
console.error('Error: ', e);
});
// With Tls tool, I tried as;
var fs = require('fs');
var socket = tls.connect(options, () => {
console.log('client connected');
process.stdin.pipe(socket);
process.stdin.resume();
});
I would like to get connected with remote server and receive data in response; instead I am getting following error;
ERROR] Error: { Error: getaddrinfo ENOTFOUND https://yadd/cards/getAssets:443
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26)
errno: 'ENOTFOUND',
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'https://yadda/cards/getAssets',
A hostname (like www.example.com) is expected here, not a URL. The string you gave will be used as hostname and a DNS lookup will fail:
ERROR] Error: { Error: getaddrinfo ENOTFOUND https://yadd/cards/getAssets:443

How to send an HTTP/2 frame to server in Node.js

With the following code:
var req = http2.request({
hostname: 'api.push.apple.com',
protocol: 'https:',
port: 443,
method: 'POST',
path: '/3/device/' + deviceToken,
agent: new http2.Agent({log: logger}),
headers: {
'apns-topic': 'web.com.example'
},
cert: cert,
key: key,
}, function(res) {
res.pipe(process.stdout);
});
req.write(JSON.stringify(post));
req.end();
how do I send a PING frame to check the health of my connection with node-http2 module?
Thanks in advance.
From Node.js HTTP/2 Documentation:
session.ping(Buffer.from('12345678'), (err, duration, payload) => {
if (!err) {
console.log(`Ping acknowledged in ${duration} milliseconds`);
console.log(`With payload '${payload.toString()}'`);
}
});

Ignore invalid self-signed ssl certificate in node.js with https.request?

I'm working on a little app that logs into my local wireless router (Linksys) but I'm running into a problem with the router's self-signed ssl certificate.
I ran wget 192.168.1.1 and get:
ERROR: cannot verify 192.168.1.1's certificate, issued by `/C=US/ST=California/L=Irvine/O=Cisco-Linksys, LLC/OU=Division/CN=Linksys/emailAddress=support#linksys.com':
Self-signed certificate encountered.
ERROR: certificate common name `Linksys' doesn't match requested host name `192.168.1.1'.
To connect to 192.168.1.1 insecurely, use `--no-check-certificate'.
In node, the error being caught is:
{ [Error: socket hang up] code: 'ECONNRESET' }
My current sample code is:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
method: 'GET'
}, function(res){
var body = [];
res.on('data', function(data){
body.push(data);
});
res.on('end', function(){
console.log( body.join('') );
});
});
req.end();
req.on('error', function(err){
console.log(err);
});
How can I go about getting node.js to do the equivalent of "--no-check-certificate"?
Cheap and insecure answer:
Add
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
in code, before calling https.request()
A more secure way (the solution above makes the whole node process insecure) is answered in this question
In your request options, try including the following:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
method: 'GET',
rejectUnauthorized: false,
requestCert: true,
agent: false
},
Don't believe all those who try to mislead you.
In your request, just add:
ca: [fs.readFileSync([certificate path], {encoding: 'utf-8'})]
If you turn on unauthorized certificates, you will not be protected at all (exposed to MITM for not validating identity), and working without SSL won't be a big difference. The solution is to specify the CA certificate that you expect as shown in the next snippet. Make sure that the common name of the certificate is identical to the address you called in the request(As specified in the host):
What you will get then is:
var req = https.request({
host: '192.168.1.1',
port: 443,
path: '/',
ca: [fs.readFileSync([certificate path], {encoding: 'utf-8'})],
method: 'GET',
rejectUnauthorized: true,
requestCert: true,
agent: false
},
Please read this article (disclosure: blog post written by this answer's author) here in order to understand:
How CA Certificates work
How to generate CA Certs for testing easily in order to simulate production environment
Add the following environment variable:
NODE_TLS_REJECT_UNAUTHORIZED=0
e.g. with export:
export NODE_TLS_REJECT_UNAUTHORIZED=0
(with great thanks to Juanra)
Adding to #Armand answer:
Add the following environment variable:
NODE_TLS_REJECT_UNAUTHORIZED=0 e.g. with export:
export NODE_TLS_REJECT_UNAUTHORIZED=0 (with great thanks to Juanra)
If you on windows usage:
set NODE_TLS_REJECT_UNAUTHORIZED=0
Thanks to: #weagle08
You can also create a request instance with default options:
require('request').defaults({ rejectUnauthorized: false })
For meteorJS you can set with npmRequestOptions.
HTTP.post(url, {
npmRequestOptions: {
rejectUnauthorized: false // TODO remove when deploy
},
timeout: 30000, // 30s
data: xml
}, function(error, result) {
console.log('error: ' + error);
console.log('resultXml: ' + result);
});
try
export NODE_TLS_REJECT_UNAUTHORIZED=0
Or you can try to add in local name resolution (hosts file found in the directory etc in most operating systems, details differ) something like this:
192.168.1.1 Linksys
and next
var req = https.request({
host: 'Linksys',
port: 443,
path: '/',
method: 'GET'
...
will work.
So, my company just switched to Node.js v12.x.
I was using NODE_TLS_REJECT_UNAUTHORIZED, and it stopped working.
After some digging, I started using NODE_EXTRA_CA_CERTS=A_FILE_IN_OUR_PROJECT that has a PEM format of our self signed cert and all my scripts are working again.
So, if your project has self signed certs, perhaps this env var will help you.
Ref: https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file
In case you are looking for posting using #nestjs/axios,
here is the syntax without certificate (Non Production Solution):
const token = Buffer.from(`${user}:${password}`,'utf8').toString('base64')
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${token}`,
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
};
const responseData = await firstValueFrom(
this.httpService.post(url, data, config).pipe(map((response) => response.data)),
);
here is the syntax with certificate (Production Solution):
const token = Buffer.from(`${user}:${password}`,'utf8').toString('base64')
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${token}`,
},
httpsAgent: new https.Agent({
rejectUnauthorized: true,
ca: fs.readFileSync(path.join(__dirname, './resources/certificateName'))
}),
};
const responseData = await firstValueFrom(
this.httpService.post(url, data, config).pipe(map((response) => response.data)),
);
When you cannot control the request creation
When using packages you sometimes don't have the option to set the correct settings on the request call, nor does the package offer you a way to inject a request.
However you might still want to avoid the insecure NODE_TLS_REJECT_UNAUTHORIZED=0 and opt for only having an insecure connection to a specified target.
This is how I solved the issue:
// check if host and port fit your application
function isSelf(host, port) {
return host === myHost && port === myPort;
}
// get the built in tls module and overwrite the default connect behavior
const tls = require("tls");
const _connect = tls.connect;
function wrappedConnect(options, secureConnectListener) {
if (isSelf(options.host, options.port)) {
options.rejectUnauthorized = false;
}
return _connect(options, secureConnectListener);
}
tls.connect = wrappedConnect;

Resources