How to setup an EV Certificate a node.js server - node.js

I've received four files from Comodo:
AddTrustExternalCARoot.crt
COMODORSAAddTrustCA.crt
COMODORSAExtendedValidationSecureServerCA.crt
mydomain.crt
This is my first time setting up a https server.
I know that I have to put on parameters that is passed to https.createServer but my problem is I don't know which one is the correct property.

The server certificate is set as cert, whereas your CA certificates are set under ca:
var fs = require('fs'),
https = require('https');
var cfg = {
key: fs.readFileSync('/path/to/privatekey.pem'),
cert: fs.readFileSync('/path/to/mydomain.crt'), // PEM format
ca: [
fs.readFileSync('/path/to/AddTrustExternalCARoot.crt'), // PEM format
fs.readFileSync('/path/to/COMODORSAAddTrustCA.crt'), // PEM format
fs.readFileSync('/path/to/COMODORSAExtendedValidationSecureServerCA.crt') // PEM format
]
};
https.createServer(cfg, function(req, res) {
// ...
}).listen(443);
Or you can use just pfx if you have your key, cert, and ca files all bundled into a single PFX/PKCS12-formatted file.

Related

openssl: ca key too small but key size is 4096

I am getting a weird error when try to upload a zip to an external API. The error is "ca key too small".
I have already verified and my p12(key + certificate) is generated with SHA256 signature and the key size is 4096 bytes.
In order to verify this, I added my p12 file to OSX keychain.
I checked the certificate it says Signature Algorithm as SHA-256 with RSA Encryption and Public key size as 4.096 bits. I checked the private key it says RSA, 4096-bit. I already tried generating a new p12 key pair but the issue persists.
I am facing this issue when deploying a zipped package through bitbucket pipelines. We recently switched our node version from 14.XX to 16.XX and because of this we started facing this issue.
Upon deeper investigation it seems that the "openssl.cnf" packaged as part of node 16.XX docker image has SECLEVEL specified with value 2. If I downgrade the node version to 14.XX and inspect the "openssl.cnf", it is missing the SECLEVEL entry altogether. So, for node image 16.XX, if I modify the SECLEVEL to 1 by manipulating the "openssl.cnf" everything works fine.
# node 16.XX docker image /etc/ssl/openssl.cnf
[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT:#SECLEVEL=2
I am also able to replicate the same issue locally by add the SECLEVEL in my local "openssl.cnf" file and then making the API call to the server. Below is the stripped down version of the nodejs code.
I do not want to use this hack of downgrading the SECLEVEL as I would like to understand what exactly the issue is. As per my understanding, my key size(4096) is not small.
var fs = require('fs');
var request = require('request');
var endpoint = 'SOME_ENDPOINT";
var instance = "SOME_INSTANCE"
var file = './test.zip';
const options = {
pfx: '/path/to/certificate.p12',
passphrase: 'SOME_PASSPHRASE',
}
var token = 'SOME_TOKEN';
var opts = {
baseUrl: 'https://' + instance,
uri: endpoint,
auth: {
bearer: ( token ? token : null )
},
strictSSL: true,
method: 'PUT'
};
if (options && options.pfx && fs.existsSync(options.pfx)) {
var stat = fs.statSync(options.pfx);
if (stat.isFile()) {
opts.agentOptions = {
pfx: fs.readFileSync(options.pfx),
passphrase: options.passphrase // as passphrase is optional, it can be undefined here
};
}
}
var req = request(opts, function(err) {
console.log(err);
});
fs.createReadStream(file).pipe(req);

node.js can i use multiple ssl certificates and keys for same project and how?

i have my paypal ssl certificate for the paypal ipn added for my code like that and it working without any problems
var httpsOptions = {
key: fs.readFileSync('./app/certsandkeys/my-prvkey.pem'),
cert: fs.readFileSync('./app/certsandkeys/my-pubcert.pem'),
requestCert: true
//pfx: fs.readFileSync('./app/certsandkeys/ssl/crt.pfx'),
//passphrase:"password"
}
https.createServer(httpsOptions, app).listen(443,function (req,res) {
console.log("server listening on port " + 443);
});
but what i need now is to certificating my whole site so i created an ssl cert and key using openssl (server.crt and server.csr and server.key ) but now i don't know how to add it beside the paypal ipn cert and key on httpsOptions
the only thing i found about something like that is this code from github issues
var options = {
key: [key1, key2],
cert: [cert1, cert2],
ca: caCert
};
var server = https.createServer(options);
so what's the right way for doing that ?
Using different keys on the same server is handled by Server Name Indication (SNI) and requires different domain names for the different servers. This question shows how SNI would be used to create a different security context for the second domain name.
Changing that code for a key in the default context should look something like this:
const secondContext = tls.createSecureContext({
key: [key2],
cert: [cert2]
});
const options = {
key: [key1],
cert: [cert1],
SNICallback: function (domain, cb) {
if (domain === 'key2domain.example.com') {
cb(null, secondContext);
} else {
cb();
}
}
}
It's not clear from the paypal docs you refer to whether paypal is having you set up an alternate domain for this IPN service URL. Instead, it looks like their process accepts a CSR to handle self-signed certs for IPN-only use and offers a payed signing of it to use it for user visible buttons, i.e. they offer their own CA service?
You can submit multiple CSRs on the same key, so you could try relying on a single private key and keep a certificate chain from a normal CA. But if they enforce usage of their own certificate chain, then you will probably need to create a separate (sub)domain for this usage to provide different chains with SNI.

How to accept self-signed certificates with LWP::UserAgent

I am attempting to set up a node.js server that uses HTTPS. I will then write a scripts in Perl to make a HTTPS request to the server and measure latency of the round trip.
Here is my node.js:
var express = require('express');
var https = require('https');
var fs = require('fs');
var key = fs.readFileSync('encrypt/rootCA.key');
var cert = fs.readFileSync('encrypt/rootCA.pem');
// This line is from the Node.js HTTPS documentation.
var options = {
key: key,
cert: cert
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world - https\n");
}).listen(8088);
Key/cert generation was done as follows:
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
This is my Perl script:
#!/usr/bin/perl
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => 'https://127.0.0.1:8080');
my $res = $ua->request($req);
if ($res->is_success) {
print $res->as_string;
} else {
print "Failed: ", $res->status_line, "\n";
}
Returning the error:
Failed: 500 Can't verify SSL peers without knowing which Certificate Authorities to trust
The node.js documentation describes how to set up an HTTPS server but it is vague about generating primary cert and intermediate cert.
https://medium.com/netscape/everything-about-creating-an-https-server-using-node-js-2fc5c48a8d4e
To make LWP::UserAgent ignore server certificate use the following configuration:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
verify_hostname => 0
);
The typical answer to such question is to disable certificate validation at all. But, this is totally insecure and essentially disables most of the protection offered by HTTPS. If validation is disabled completely a man in the middle attacker just can use an arbitrary certificate to intercept the connection and sniff and modify the data. Thus, don't do it.
The correct way to deal with such certificates is to add these certificates as trusted. This can be done with the SSL_ca_file argument:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(SSL_ca_file => 'rootCA.pem');
$ua->get('https://127.0.0.1:8080');
By explicitly trusting the self-signed server certificate as CA it will no longer throw "certificate verify failed".
But, unless your server certificate is actually issued to "127.0.0.1" you will now get "hostname verification failed" since the subject of the certificate does not match the domain of the URL. This can be fixed by setting the expected hostname:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(
SSL_ca_file => 'rootCA.pem',
SSL_verifycn_name => 'www.example.com',
);
$ua->get('https://127.0.0.1:8080');
Note that SSL_ca_file needs the self-signed certificate to have the CA flag set to true, i.e. that the certificate is a CA certificate which can be used to issue other certificates. If this is not the case with your certificate or if you just want to accept a specific certificate no matter if it is expired, revoked, does not match the hostname etc you can do the validation by using the fingerprint of the certificate.
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(SSL_fingerprint => 'sha1$9AA5CFED857445259D90FE1B56B9F003C0187BFF')
$ua->get('https://127.0.0.1:8080');
The fingerprint here is the same you get with openssl x509 -noout -in rootCA.pem -fingerprint -sha1, only the algorithm added in front (sha1$...) and the colons removed.

Programmatically create certificate and certificate key in Node

Using node.js, I'd like to write code to programmatically do the equivalent of the following:
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
openssl rsa -passin pass:x -in server.pass.key -out server.key
rm server.pass.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
When complete, I need the RSA key server.key and the self-signed SSL certificate server.crt.
forge looks the most promising, but so far I haven't figured out how to get it to work. I have the following code:
var pki = forge.pki;
var keys = pki.rsa.generateKeyPair(2048);
var privKey = forge.pki.privateKeyToPem(keys.privateKey);
var pubKey = forge.pki.publicKeyToPem(keys.publicKey);
But when I write the pubKey to a file, I've noticed it starts with ...
-----BEGIN PUBLIC KEY-----
MIIB...
-----END PUBLIC KEY-----
... and isn't recognized, whereas using openssl above it starts with:
-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----
Since the original link went dead, I've made my own code that generates a self-signed certificate using node-forge (which it looks like they already have based on the original question), so I thought I'd put it here for someone who wants it
Simply creating a public and private key pair isn't enough to work as a certificate, you have to put in attributes, node-forge is incredibly useful this way, as its pki submodule is designed for this.
First, you need to create a certificate via pki.createCertificate(), this is where you'll assign all of your certificate attributes.
You need to set the certificate public key, serial number, and the valid from date and valid to date. In this example, the public key is set to the generated public key from before, the serial number is randomly generated, and the valid from and to dates are set to one day ago and one year in the future.
You then need to assign a subject, and extensions to your certificate, this is a very basic example, so the subject is just a name you can define (or let it default to 'Testing CA - DO NOT TRUST'), and the extensions are just a single 'Basic Constraints' extension, with certificate authority set to true.
We then set the issuer to itself, as all certificates need an issuer, and we don't have one.
Then we tell the certificate to sign itself, with the private key (corresponding to its public key we've assigned) that we generated earlier, this part is important when signing certificates (or child certificates), they need to be signed with the private key of its parent (this prevents you from making fake certificates with a trusted certificate parent, as you don't have that trusted parent's private key)
Then we return the new certificate in a PEM-encoded format, you could save this to a file or convert it to a buffer and use it for a https server.
const forge = require('node-forge')
const crypto = require('crypto')
const pki = forge.pki
//using a blank options is perfectly fine here
async function genCACert(options = {}) {
options = {...{
commonName: 'Testing CA - DO NOT TRUST',
bits: 2048
}, ...options}
let keyPair = await new Promise((res, rej) => {
pki.rsa.generateKeyPair({ bits: options.bits }, (error, pair) => {
if (error) rej(error);
else res(pair)
})
})
let cert = pki.createCertificate()
cert.publicKey = keyPair.publicKey
cert.serialNumber = crypto.randomUUID().replace(/-/g, '')
cert.validity.notBefore = new Date()
cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1)
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
cert.setSubject([{name: 'commonName', value: options.commonName}])
cert.setExtensions([{ name: 'basicConstraints', cA: true }])
cert.setIssuer(cert.subject.attributes)
cert.sign(keyPair.privateKey, forge.md.sha256.create())
return {
ca: {
key: pki.privateKeyToPem(keyPair.privateKey),
cert: pki.certificateToPem(cert)
},
fingerprint: forge.util.encode64(
pki.getPublicKeyFingerprint(keyPair.publicKey, {
type: 'SubjectPublicKeyInfo',
md: forge.md.sha256.create(),
encoding: 'binary'
})
)
}
}
//you need to put the output from genCACert() through this if you want to use it for a https server
/* e.g
let cert = await genCACert();
let buffers = caToBuffer(cert.ca);
let options = {};
options.key = buffers.key;
options.cert = buffers.cert;
let server = https.createServer(options, <listener here>);
*/
function caToBuffer(ca) {
return {
key: Buffer.from(ca.key),
cert: Buffer.from(ca.cert)
}
}
Do with this what you will.
Okay, as you probably realized, I wasn't generating a certificate. It required quite a bit more work, which you can find here.
Essentially, after a bunch of setup, I had to create, sign, and convert the certificate to Pem:
cert.sign(keys.privateKey);
var pubKey = pki.certificateToPem(cert);
Hope this helps someone else!

Request with X509 Certificate

I have received a X509 certificate (one .cer file), I can decode it, so no problems on that. Now I want to sign a request with this certificate in node, but I can't get this to work:
var https = require("https");
var fs = require("fs");
var options = {
host: 'management.core.windows.net',
path: '/my-subscription-id/services/hostedservices',
port: 443,
method: 'GET',
cert: fs.readFileSync("./SSLDevCert.cer"),
agent: false
};
var req = https.request(options, function(res) {
console.log("statusCode: ", res.statusCode);
console.log("headers: ", res.headers);
res.on('data', function(d) {
process.stdout.write(d);
});
});
This fails with
Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
at Object.createCredentials (crypto.js:72:31)
at Object.connect (tls.js:857:27)
at Agent._getConnection (https.js:61:15)
at Agent._establishNewConnection (http.js:1183:21)
Doing the same in C# works fine:
var req = (HttpWebRequest)WebRequest.Create(string.Format("https://management.core.windows.net/{0}/services/hostedservices", "my-subscription-id"));
req.ClientCertificates.Add(new X509Certificate2(File.ReadAllBytes("./SSLDevCert.cer"));
var resp = req.GetResponse();
PEM_read_bio expects certificate in PEM format, while you have certificate in "raw" DER format. Obviously you need to convert your certificate to PEM format.
BTW .cer files in DER format don't contain private key and can't be used for signing anything.
You need to re-check what you actually have in your .cer file and in what format.
A follow up on this:
Only .cer file probably means that the private key is in the certificate (well that's the case with the Azure certs), you will have to transform in a PEM file (that starts with ----BEGIN RSA PRIVATE KEY----) and then do a request with:
var key = fs.readFileSync("./key.pem");
var options = {
cert: key,
key: key
}
Getting the private key from the file can be a bit tricky, but this worked on Azure certificates, so it might help any of you:
openssl pkcs12 -in ' + file + ' -nodes -passin pass:
(note the empty pass argument)

Resources