Adding certificates to HTTP request in NestJS [duplicate] - node.js

I'm trying to make a request with axios to an api endpoint and I'm getting the following error: Error: unable to verify the first certificate
It seems the https module, which axios uses, is unable to verify the SSL certificate used on the server.
When visiting the server with my browser, the certificate is valid and I can see/download it. I can also make requests to the api on my browser through https.
I can work around it by turning off verification. This code works.
const result = await axios.post(
`https://${url}/login`,
body,
{
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
}
)
Problem is, this doesn't verify the SSL certificate and therefore opens up security holes.
How can I configure axios to trust the certificate and correctly verify it?

Old question but chiming in for those who land here. No expert. Please consult with your local security gurus and what not.
Axios is an http(s) client and http clients usually participate in TLS anonymously. In other words, the server accepts their connection without identifying who is trying to connect. This is different then say, Mutual TLS where both the server and client verify each other before completing the handshake.
The internet is a scary place and we want to protect our clients from connecting to spoofed public endpoints. We do this by ensuring our clients identify the server before sending any private data.
// DO NOT DO THIS IF SHARING PRIVATE DATA WITH SERVICE
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
This is often posted (and more egregiously upvoted) as the answer on StackOverflow regarding https client connection failures in any language. And what's worse is that it usually works, unblocks the dev and they move on their merry way. However, while they certainly get in the door, whose door is it? Since they opted out of verifying the server's identity, their poor client has no way of knowing if the connection they just made to the company's intranet has bad actors listening on the line.
If the service has a public SSL cert, the https.Agent usually does not need to be configured further because your operating system provides a common set of publicly trusted CA certs. This is usually the same set of CA certs your browser is configured to use and is why a default axios client can hit https://google.com with little fuss.
If the service has a private SSL cert (self signed for testing purposes or one signed by your company's private CA to protect their internal secrets), the https agent must be configured to trust the private CA used to sign the server cert:
const httpsAgent = new https.Agent({ ca: MY_CA_BUNDLE });
where MY_CA_BUNDLE is an array of CA certs with both the server cert for the endpoint you want to hit and that cert's complete cert chain in .pem format. You must include all certs in the chain up to the trust root.
Where are these options documented?
HTTPS is the HTTP protocol over TLS/SSL. In Node.js this is implemented as a separate module.
Therefore options passed to the https.Agent are a merge of the options passed to tls.connect() and tls.createSecureContext().

Create a custom agent with SSL certificate:
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // (NOTE: this will disable client verification)
cert: fs.readFileSync("./usercert.pem"),
key: fs.readFileSync("./key.pem"),
passphrase: "YYY"
})
axios.get(url, { httpsAgent })
// or
const instance = axios.create({ httpsAgent })
From https://github.com/axios/axios/issues/284

For me, when my application is running in development mode, I have disabled rejectUnauthorized directly in axios.defaults.options. This works very well. be careful and use this only in developer mode.
import https from 'https'
import axios from 'axios'
import config from '~/config'
/**
* Axios default settings
*/
axios.defaults.baseURL = config.apiURL
/**
* Disable only in development mode
*/
if (process.env.NODE_ENV === 'development') {
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
axios.defaults.httpsAgent = httpsAgent
// eslint-disable-next-line no-console
console.log(process.env.NODE_ENV, `RejectUnauthorized is disabled.`)
}

These configuration worked for me (In a Mutual Authentication scenario).
const httpsAgent = new https.Agent({
ca: fs.readFileSync("./resource/bundle.crt"),
cert: fs.readFileSync("./resrouce/thirdparty.crt"),
key: fs.readFileSync("./resource/key.pem"),
})
Note: bundle.crt was prepared from provided certificates (root,intermediate,end entry certificate). Unfortunately no clear documentation found in this regards.

This is very dirty, but at the top of your script, just put:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
This basically tells node to not check SSL certificates, which is very convenient when you get self signed certificates rejected in development.
Please don't use this in production.

This what worked for me , using axios with nodejs + express
exports.test_ssl = async (req,res) => {
let cert_file = fs.readFileSync("./ssl/my_self_signed_certificate.crt")
let ca_file = fs.readFileSync("./ssl/my_self_signed_certificate_ca.crt")
const agent = new https.Agent({
requestCert: true,
rejectUnauthorized: true,
cert: cert_file,
ca: ca_file
});
const options = {
url: `https://51.195.45.154/test`, // <---this is a fake ip do not bother
method: "POST",
httpsAgent : agent,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/txt;charset=UTF-8'
},
data: {}
};
console.log(cert_file.toString())
axios(options)
.then(response => {
payload = response.data ;
return res.status(200).send({"status":1});
}).catch(err => {
console.log(err);
return false
});
}

This worked for me:
import axios from 'axios'
import https from 'https'
const headers = {};
const httpsAgent = new https.Agent({
ca: fs.readFileSync('./certs/cert.pem'),
cert: fs.readFileSync('./certs/cert.pem'),
})
const data = await axios.get(url, { httpsAgent, headers })

const https = require('https');
const axios = require('axios')
const CA = "-----BEGIN CERTIFICATE-----$$$$$-----END CERTIFICATE-----"
const url = "bla"
const httpsAgent = new https.Agent({
ca: CA
});
const response = await axios.get(url, { httpsAgent });
This is what work for me.

Good morning dear.
My problem is the following:
"Enable to verify the first certificate" with an error code 'ENABLE_TO_VERIFY_LEAF_SIGNATURE'.
They sent me a certificate with a .pfx extension and with the following commands I generated the .pem certificate and the key also with a .pem extension.
I attach the commands.
openssl pkcs12 -in certificate.pfx -nocerts -out key.pem -nodes
openssl pkcs12 -in certificate.pfx -nokeys -out certificate.pem
It should be noted that I am using axios to make the request.
I attach my agent configuration in axios.
const httpsAgent = new https.Agent ({
pfx: fs.readFileSync ("path.pfx"),
passphrase: 'password',
requestCert: true,
rejectUnauthorized: true
});

Related

How to ignore SSL certificate validation in node requests?

I need to disable peer SSL validation for some of my https requests using node.js
Right now I use node-fetch package which doesn't have that option, as far as I know.
That should be something like CURL's CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false
Does any networking package allow to do so? Is there a way to skip SSL validation in axios maybe?
Axios doesn't address that situation so far - you can try:
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
BUT THAT'S A VERY BAD IDEA since it disables SSL across the whole node server.
Or, you can configure axios to use a custom agent and set rejectUnauthorized to false for that agent as mentioned here.
Example:
const https = require('https');
const axios = require('axios')
//or
// import https from 'https';
// import axios from 'axios';
// At instance level
const instance = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
instance.get('https://something.com/foo');
// At request level
const agent = new https.Agent({
rejectUnauthorized: false
});
axios.get('https://something.com/foo', { httpsAgent: agent });

How to configure axios to use SSL certificate?

I'm trying to make a request with axios to an api endpoint and I'm getting the following error: Error: unable to verify the first certificate
It seems the https module, which axios uses, is unable to verify the SSL certificate used on the server.
When visiting the server with my browser, the certificate is valid and I can see/download it. I can also make requests to the api on my browser through https.
I can work around it by turning off verification. This code works.
const result = await axios.post(
`https://${url}/login`,
body,
{
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
}
)
Problem is, this doesn't verify the SSL certificate and therefore opens up security holes.
How can I configure axios to trust the certificate and correctly verify it?
Old question but chiming in for those who land here. No expert. Please consult with your local security gurus and what not.
Axios is an http(s) client and http clients usually participate in TLS anonymously. In other words, the server accepts their connection without identifying who is trying to connect. This is different then say, Mutual TLS where both the server and client verify each other before completing the handshake.
The internet is a scary place and we want to protect our clients from connecting to spoofed public endpoints. We do this by ensuring our clients identify the server before sending any private data.
// DO NOT DO THIS IF SHARING PRIVATE DATA WITH SERVICE
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
This is often posted (and more egregiously upvoted) as the answer on StackOverflow regarding https client connection failures in any language. And what's worse is that it usually works, unblocks the dev and they move on their merry way. However, while they certainly get in the door, whose door is it? Since they opted out of verifying the server's identity, their poor client has no way of knowing if the connection they just made to the company's intranet has bad actors listening on the line.
If the service has a public SSL cert, the https.Agent usually does not need to be configured further because your operating system provides a common set of publicly trusted CA certs. This is usually the same set of CA certs your browser is configured to use and is why a default axios client can hit https://google.com with little fuss.
If the service has a private SSL cert (self signed for testing purposes or one signed by your company's private CA to protect their internal secrets), the https agent must be configured to trust the private CA used to sign the server cert:
const httpsAgent = new https.Agent({ ca: MY_CA_BUNDLE });
where MY_CA_BUNDLE is an array of CA certs with both the server cert for the endpoint you want to hit and that cert's complete cert chain in .pem format. You must include all certs in the chain up to the trust root.
Where are these options documented?
HTTPS is the HTTP protocol over TLS/SSL. In Node.js this is implemented as a separate module.
Therefore options passed to the https.Agent are a merge of the options passed to tls.connect() and tls.createSecureContext().
Create a custom agent with SSL certificate:
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // (NOTE: this will disable client verification)
cert: fs.readFileSync("./usercert.pem"),
key: fs.readFileSync("./key.pem"),
passphrase: "YYY"
})
axios.get(url, { httpsAgent })
// or
const instance = axios.create({ httpsAgent })
From https://github.com/axios/axios/issues/284
For me, when my application is running in development mode, I have disabled rejectUnauthorized directly in axios.defaults.options. This works very well. be careful and use this only in developer mode.
import https from 'https'
import axios from 'axios'
import config from '~/config'
/**
* Axios default settings
*/
axios.defaults.baseURL = config.apiURL
/**
* Disable only in development mode
*/
if (process.env.NODE_ENV === 'development') {
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
axios.defaults.httpsAgent = httpsAgent
// eslint-disable-next-line no-console
console.log(process.env.NODE_ENV, `RejectUnauthorized is disabled.`)
}
These configuration worked for me (In a Mutual Authentication scenario).
const httpsAgent = new https.Agent({
ca: fs.readFileSync("./resource/bundle.crt"),
cert: fs.readFileSync("./resrouce/thirdparty.crt"),
key: fs.readFileSync("./resource/key.pem"),
})
Note: bundle.crt was prepared from provided certificates (root,intermediate,end entry certificate). Unfortunately no clear documentation found in this regards.
This is very dirty, but at the top of your script, just put:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
This basically tells node to not check SSL certificates, which is very convenient when you get self signed certificates rejected in development.
Please don't use this in production.
This what worked for me , using axios with nodejs + express
exports.test_ssl = async (req,res) => {
let cert_file = fs.readFileSync("./ssl/my_self_signed_certificate.crt")
let ca_file = fs.readFileSync("./ssl/my_self_signed_certificate_ca.crt")
const agent = new https.Agent({
requestCert: true,
rejectUnauthorized: true,
cert: cert_file,
ca: ca_file
});
const options = {
url: `https://51.195.45.154/test`, // <---this is a fake ip do not bother
method: "POST",
httpsAgent : agent,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/txt;charset=UTF-8'
},
data: {}
};
console.log(cert_file.toString())
axios(options)
.then(response => {
payload = response.data ;
return res.status(200).send({"status":1});
}).catch(err => {
console.log(err);
return false
});
}
This worked for me:
import axios from 'axios'
import https from 'https'
const headers = {};
const httpsAgent = new https.Agent({
ca: fs.readFileSync('./certs/cert.pem'),
cert: fs.readFileSync('./certs/cert.pem'),
})
const data = await axios.get(url, { httpsAgent, headers })
const https = require('https');
const axios = require('axios')
const CA = "-----BEGIN CERTIFICATE-----$$$$$-----END CERTIFICATE-----"
const url = "bla"
const httpsAgent = new https.Agent({
ca: CA
});
const response = await axios.get(url, { httpsAgent });
This is what work for me.

How can I create an SSL connection for socket.io in nest.js?

|Greetings|
We are developing an application using nest.js and socket.io, and I'd like to know whether it's possible to create an SSL connection for this environment.
Here's the link to the repo: https://github.com/nokia/skilltree
( the latest attempts have been made in the David branch )
I tried this one, but the socket.io still doesn't use SSL connection:
https://blog.cloudboost.io/everything-about-creating-an-https-server-using-node-js-2fc5c48a8d4e
They suggest this:
var options = {
key: key,
cert: cert,
ca: ca
};
var https = require('https');
https.createServer(options, app).listen(443);
Thank you for any help in advance
Nest takes an option object as second parameter, which also contains https options, like:
const app = await NestFactory.create(AppModule, {
httpsOptions: {
key: 'key',
ca: 'ca',
cert: 'cert',
},
});
await app.listen(3000);
So there should be no need to create the express instance yourself.
Haven't tested, but it should actually work. :)
See also: HttpOptions Interface NestJs
Spent entire day with exactly the same issue, here the best solution I could find:
const httpsOptions = {
key: key,
cert: cert,
ca: ca
};
const expressInstance: express.Express = express();
const app: NestApplication = await NestFactory.create(
MainModule,
expressInstance,
{ httpsOptions }
);
await app.listen(Environment.PORT);
With this approach secure websockets work just fine for me

Nodejs TLS with self-signed Certificate Authority

Background:
I'm trying to communicate between a server and one (should be able to be multiple - hence the need of a CA) client through TLS.
Each node has a certificate that is signed with a common CA.
The CA is in turn self-signed.
The private key of each node is exported as key.pem.
The certificate of each node is exported as certificate.crt.
The CA certificate is exported as ca.crt.
The certificates are not bundled, just exported as is.
The server uses the following setup:
var tls = require("tls");
var fs = require("fs");
var options = {
key: fs.readFileSync("keys/key.pem", "utf8"),
cert: fs.readFileSync("keys/certificate.crt", "utf8"),
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('keys/ca.crt')]
}
var server = tls.createServer(options, function(res) {
console.log("Client connected");
console.log('Client connected',
res.authorized ? 'authorized' : 'unauthorized');
res.write("Hello World!");
res.setEncoding("utf8");
res.pipe(res);
}).listen(3000);
The client uses the following setup:
var tls = require("tls");
var fs = require("fs");
var options = {
key: fs.readFileSync("keys/key.pem", "utf8"),
cert: fs.readFileSync("keys/certificate.crt", "utf8"),
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('keys/ca.crt')]
}
var client = tls.connect(3000, options, function(){
console.log("Connected to server");
console.log(client.authorized ? "Authorized" : "Not authorized");
});
client.on("data", function(data){
console.log("Received from server", data);
client.end();
});
Note on keys / certificates:
The keys and certificates are generated with the openssl GUI / manager XCA.
The tree looks as follows:
The problem:
As you can see I am using explicit client certificate authentication and I want to disallow any non-permitted connections.
The problem with this is that the client is not able to connect, even though all the certificates come from the same CA.
The error I get from both the server (when a client connects) and the client(when it connects) is:
Error: socket hang up, code: ECONNRESET
If I disable rejectUnauthorized the client can connect, but res.authorized returns false.
What is causing authorised clients to not being able to be authenticated?
Your code is fine. I expect there is a problem with your certificates. The fact that there is no expiry date sticks out to me. I have found this OpenSSL Certificate Authority by Jamie Nguyen to be very useful.
Remember that Nodejs does not support multiple certificates in one cert file, so if you are following the guide, there is no need to copy the root ca and intermediate ca into one file. You will have to add them as separate file entries in the ca list argument.
Afaik the xca tool is build on openssl, so might be able to map the commands in openssl to xca.

Node.js: Require a client certificate for a directory (and allow a retry)

I want to create a reverse proxy in node js, where I can require client certificates for certain parts of the site.
The /secure section should require a client cert.
The /nosecure section does not require a client cert.
The user should be able to retry the client cert validation if he didn't provide a valid one before.
var options = {
key: fs.readFileSync(__dirname + '/key.pem'),
cert: fs.readFileSync(__dirname + '/cert.pem'),
ca: fs.readFileSync(__dirname + '/clientCA.pem'),
requestCert: true
};
https.createServer(options, function (req, res) {
//parse url
req.parsedUrl = url.parse(req.url);
//handle urls
switch(req.parsedUrl.pathname) {
case '/nosecure':
/*
* This location does not require a client cert
*/
res.end('nosecure');
break;
case '/secure':
/*
* This location requires a client certificate, which can be checked by getPeerCertificate()
* If no certificate is provided, the user will be redirected to secureError
*/
res.end('secure');
break;
case '/secureError':
/*
* This location does not require a client cert
* It displays the error page, in case the client cert was not provided/validated on /secure.
*/
res.end('Certificate validation failed. Try again');
break;
default:
res.end('not found');
break;
}
}).listen(9000);
Normally you can only set these requirements on server level.
But I could manually check the cert in the secure section.
The problem however remains. I can not ask the client browser to send me the cert again.
This only happens once, when the SSL session is started.
Any ideas on how to fix this?
I'm a noob myself but I'm playing around with the same thing.
Maybe something like
if (req.parsedUrl.pathname == '/secure' && !req.connection.getPeerCertificate().valid_to) req.parsedUrl.pathname = '/secureError';
I think you can simply allow unauthorized connections, and then logically check for authorization in order to allow access to some parts of your application.
Minimal Reproduceable Example
const fs = require('fs')
const https = require('https')
const opts = {
key: fs.readFileSync('some_key.pem'),
cert: fs.readFileSync('some_cert.pem'),
ca: [ fs.readFileSync('potentially_different_cert.pem') ],
requestCert: true,
rejectUnauthorized: false, // allow insecure connections
}
https.createServer(opts, (req, res) => {
const cert = req.connection.getPeerCertificate()
if (req.client.authorized) {
//secure
} else {
//insecure
}
}).listen(9000)

Resources