Node.js: access the client certificate - node.js

I'm working on a Node.js app that we will call "server A" where users have to provide a client certificate in order to access services.
Simplified example:
/** server setup **/
var serverOptions = {
key: fs.readFileSync('certs/server.key'),
cert: fs.readFileSync('certs/server.crt'),
ca: [fs.readFileSync('certs/ca.crt'), fs.readFileSync('certs/bar.cer'), fs.readFileSync('certs/foo.cer')],
requestCert: true
};
https.createServer(serverOptions, app).listen(SERVER_PORT, '', null, function () {
var host = this.address().address;
var port = this.address().port;
console.log('listening at http://%s:%s', host, port);
});
Everything works as expected, the following code prints the details of the client certificate.
app.get('/', function (req, res) {
/*** Dump ***/
res.contentType('application/json');
res.send(util.inspect(req.socket.getPeerCertificate(true), {colors: true}));
});
However, I would like to be able to use this client certificate obtained in serverA, to make a request to another server called "server B".
options = {
hostname: 'localhost',
port: '3010',
path: '/',
method: 'POST',
headers: {
'Content-Type': 'text/xml;charset=UTF-8',
'Content-Length': serverResponse.length,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
},
cert: clientCertificate
};
var req = https.request(options, function (res) {
console.log('statusCode: ', res.statusCode);
console.log('headers: ', res.headers);
res.on('data', function(d) {
callback(d);
});
});
The problem is that I have not found a way to get a proper X509 certificate with the getPeerCertificate function, which returns a "custom" object representation of the certificate.
As described in the official documentation, the cert parameter must be provided with the following data :
Public x509 certificate to use. Default null.
Is there a way to get the client certificate in the correct format for that purpose?

I had the same problem and saw your question with no answers, so I'm coming back to post my solution.
The certificate object has a raw field which contains the certificate data you want in byte form. To get it in X509 format, just convert it to base64. So the code you're looking for is:
req.socket.getPeerCertificate(true).raw.toString('base64');
Hope that helps!

Related

How to fix 'Hostname/IP does not match certificate's altnames'

I have a problem when I run get a response for service. There are two services. For the first service, it's working perfectly but in the second I have issue "Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Cert is empty". When I used Postman with certificates its works perfectly. What should I set or implement to make it works?​
I tried using: checkServerIdentity, rejectUnauthorized: true.
let options = {
hostname: 'ws.aaa.bbb.nn',
path: '/api',
method: 'GET',
key: fs.readFileSync('/private.crt'),
cert: fs.readFileSync('/public.crt'),
passphrase: '***'
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk)=> {
body += chunk;
console.log(body)
});
});
Should be status code 200.
Seems you got a bad certificate.
Get a correct one or just turn the SSL certificate verification off.
I had the same problem, Just try to set Authorization Type to "Inherit auth from parent".

P12 PFX NodeJS Request

I am trying to make a request with a p12 file or a pfx, but I can't get it to work. If I use PEM and KEY the code works fine. But Azure Keyvault does not support PEM and KEY. Is there an alternative that works with KEY/PEM certificates?
This is how I generated a p12/pfx file if that is the problem.
openssl pkcs12 -export -out certificate.pfx -inkey 1231181189.key -in
1231181189.pem -certfile CA.pem
Here is an example code, if I comment out cert and key the system does not work,
Error: read ECONNRESET
But if I comment out pfx and passphrase and use pem and key the connection work.
var request = require('request');
var fs = require('fs');
var path = require('path');
var certFile = __dirname + '/certs/1231181189.pem';
var keyFile = __dirname + '/certs/1231181189.key';
var options = {
method: 'POST',
url: 'https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests',
headers: { 'Content-Type': 'application/json' },
agentOptions: {
cert: fs.readFileSync(certFile),
key: fs.readFileSync(keyFile),
pfx: fs.readFileSync(__dirname + '/certs/certificate.pfx'),
passphrase: 'swish'
},
body: {
payeePaymentReference: '0123456789',
callbackUrl: 'https://example.com/api/swishcb/paymentrequests',
payerAlias: '4671234768',
payeeAlias: '1231181189',
amount: '100',
currency: 'SEK',
message: 'Kingston USB Flash Drive 8 GB'
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(response.headers);
console.log(body);
});
ECONNRESET means the far end -- in your case the endpoint on swish.net -- unceremoniously disconnected from the https client in your nodejs program. It's hard to know precisely why it did so. It's likely due to some sort of security failure. Robust servers don't explain security failures; after all why help cybercreeps? It's possible looking at a log on that server will tell you more.
In the meantime, it's possible the npm request package you use to wrap node's https agent function doesn't know anything about .pfx files or passwords, and therefore attempts to connect without any client certificates.
The pemutils package may allow you to extract the information you need from your .pfx file and use it. Something like this may work (not debugged).
var request = require('request');
var pemutils = require('pemutils');
var fs = require('fs');
var path = require('path');
const pfxFile = __dirname + '/certs/certificate.pfx';
pemutils.fromPfx({
path: pfxFile,
password: 'myPass'
}, function(err, pfxresults) {
if(err) throw err;
var options = {
method: 'POST',
url: 'https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests',
headers: { 'Content-Type': 'application/json' },
agentOptions: {
cert: pfxresults.certificate,
key: pfxresults.key,
},
body: {
...
},
json: true
};
...
Notice the .fromPfx method is asynchronous.
I have the same issue with Azure APIM and I also need basic auth for the request.
I send request with axios like below:
const fs = require("fs");
const axios = require("axios");
const https = require("https");
(async function () {
try {
const response = await axios.request({
url: "url",
method: "get",
headers: {
accept: "application/json",
},
auth: {
username: "name",
password: "pw",
},
httpsAgent: new https.Agent({
pfx: fs.readFileSync(
__dirname + "/mycert.pfx"
),
}),
});
console.log(JSON.stringify(response.data));
} catch (error) {
console.log(error);
}
})();

nodejs, make https request to backend server

Using https request, I was trying to connect to backend server using the below documentation guide:
http://nodejs.org/api/https.html#https_https_request_options_callback
function getHttpsReq (request, response)
{
var options = {
hostname: 'encrypted.google.com',
port: 443,
path: '/',
rejectUnauthorized: false,
strictSSL: false,
agent: false,
method: 'GET'
}
https.request(options, function(res) {
console.log("statusCode: ", res.statusCode);
console.log("headers: ", res.headers);
var data = '';
res.on('data', function(d) {
process.stdout.write(d);
data += data + d;
});
res.on('end', function() {
console.log("Done");
response.end(data);
});
});
}
But when I am trying this, it is throwing the below error:
Error: 140735290610048:error:0607907F:digital envelope
routines:EVP_PKEY_get1_RSA:expecting an rsa
key:../deps/openssl/openssl/crypto/evp/p_lib.c:288:
$ node --version
v0.8.15
Please let me know what I need to do extra?
What you need are the certificates to your domain.
This can be added using the below code.
key: fs.readFileSync('/etc/apache2/ssl/domain.key'),
cert: fs.readFileSync('/etc/apache2/ssl/domain.crt')
to your options.
The certificates can be obtained using a trusted authority or you can use self signed certificates. But self signed certificates would generate an error on client's side if it is not added to the trusted certificates in client's machine.

Verify ssl certificate in nodejs

How can I verify SSL certificate on https of given domain by NodeJS?
I need to know if user open this like is it trusted or should add it as exception?
--
I think this code should raise error during unsigned or selfsigned certificate.
var https = require('https');
var options = {
host: 'daarkoob.ir',
port: 2222,
path: '/',
method: 'GET',
rejectUnauthorized:true
};
var req = https.request(options, function(res) {
console.log("statusCode: ", res.statusCode);
console.log("headers: ", res.headers);
});
req.end();
req.on('error', function(e) {
console.error(e);
});
https://daarkoob.ir:2222 is self signed certificated,so above code should raise error during surfing.but nothing happened.
From the documentation:
rejectUnauthorized: If true, the server certificate is verified against
the list of supplied CAs. An 'error' event is emitted if verification
fails. Verification happens at the connection level, before the HTTP
request is sent. Default true.
I guess that you need to specify a list of CA or the option is ignored.

How do I avoid SELF_SIGNED_CERT_IN_CHAIN error in node.js TLS module?

I am trying to create a TLS/SSL connection using node.js v0.10.5 and the einaros/ws (WebSockets) module, but I get the following error:
Error: SELF_SIGNED_CERT_IN_CHAIN
I get my cert from my own CA, which is an EJBCA server, Version : EJBCA 4.0.15 (r16671) and I am using the following code in my client:
define(["ws", "fs"], function (WebSocket, fs) {
"use strict";
return function (jsonRequest) {
var response,
error,
successCallback,
errorCallback,
HB_PFX = "server.domain.com.p12",
HB_CA = "certs/my-ca.pem";
var secureOptions = {
passphrase: "the-passphrase",
pfx: fs.readFileSync(HB_PFX),
ca : [fs.readFileSync(HB_CA)]
};
var sendRequest = function () {
var client = new WebSocket("wss://server.domain.com:8080/draft", secureOptions);
client.on("open", function () {
client.send(jsonRequest);
});
client.on("error", function (e) {
error = e.toString();
console.log(error);
if (errorCallback) {
errorCallback(error);
}
});
client.on("message", function (message) {
response = message;
if (successCallback) {
successCallback(message);
}
});
client.on("close", function (code) {
console.log("Connection closed with code: " + code);
});
};
return {
send: function (callback) {
if (response && !error) {
callback(response);
} else {
successCallback = callback;
}
sendRequest();
return this;
},
ifError: function (callback) {
if (error) {
callback(response);
} else {
errorCallback = callback;
}
return this;
}
};
};
});
The p12 store (PKCS12) is generated by the CA, and it includes the key, my server certificate, and the CA certificate.
I can connect to the server with a browser with no problems, I just get prompted to accept the certificate on first connection. But when I try to connect with my client, I always get that error. I am connecting to the server using its FQDN, not an IP address.
If I try to use a self-signed certificate (a cert generated in my local machine and used instead of the p12 file), I get a DEPTH_ZERO_SELF_SIGNED_CERT error.
I am running on Mac OS X 10.8.4.
I have tried almost every permutation, even exporting the key and certificates from the PKCS12 file to PEM files, but I get the exact same error. I have also added the CA certificate to all the cacert files that I could find in my computer, which are the following:
/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/lib/security/cacerts
/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts
/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home/jre/lib/security/cacerts
/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/security/cacerts
/System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security/cacerts
/System/Library/Java/Support/Deploy.bundle/Contents/Home/lib/security/cacerts
Does anybody know how to solve this error and create secure connections in node?
Does anybody know how to solve this error and create secure connections in node?
For the case of Mac OS X, you need to put the root certificate (self signed, used for signing) in your Keychain.
check with this
npm config set ca ""
more info here
https://github.com/gootyfer/simple-ws-bradcast-server
The following is the https_server and client which i created as sample, take a look if this helps you.
https_server.js
var https = require('https');
var fs = require('fs');
var url = require('url');
var options = {
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('cacert.pem')
};
https.createServer(options, function (request, response) {
var url_parts = url.parse(request.url, true);
var query = url_parts.query;
console.log("----------------------------------");
console.log(request.method + " method" + " and " + " timeout is "+ query.timeout);
setTimeout(function () {
console.log(" Executing setTimeout Function ");
if(request.method=='POST') {
console.log(" Inside post " );
var body='';
request.on('data', function (data) {
body +=data;
});
response.setHeader("keyPOST","ValuePair");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<b>Hello World</b>");
response.end();
request.on('end',function(){
var POST = qs.parse(body);
console.log("on request end " +POST);
console.log("----------------------------------");
});
}
else if(request.method=='GET') {
console.log(" Inside get" );
console.log("Query sent to the server is :" +query.name);
response.setHeader("keyGET","ValuePair");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<b>Hello User, Response sent at "+query.timeout+" milli seconds from server</b>");
response.end();
request.on('end',function(){
console.log("on request end");
console.log("----------------------------------");
});
}
}, query.timeout);
}).listen(8000, "127.0.0.1",function() {
console.log(' Server has been started at '+(new Date()) +' and running in https://127.0.0.1:8000/');
});
https_client.js:
var https = require("https");
var fs = require("fs");
var querystring = require('querystring');
var data = querystring.stringify({
name: "Arun",
timeout:5000
});
var options = {
hostname: '127.0.0.1',
port: 8000,
path: '/saveText?name=Arun&timeout=5000',
method: 'GET',
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('cacert.pem'),
agent: false,
rejectUnauthorized: false
};
var req = https.request(options, function(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('BODY: ' + chunk);
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
// write data to request body
//req.write(data);
req.end();
create privkey.pem and cacert.pem using openssl :
1) command to create privkey.pem is : openssl genrsa -out privkey.pem 2048
2) command to create cacert.pem is : openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095
(or) if the second command shows unable to locate openssl.cnf error use -config {openssl.cnf file path} option along with second command.

Resources