How to add custom certificate authority (CA) to nodejs - node.js

I'm using a CLI tool to build hybrid mobile apps which has a cool upload feature so I can test the app on a device without going through the app store (it's ionic-cli). However, in my company like so many other companies TLS requests are re-signed with the company's own custom CA certificate which I have on my machine in the keychain (OS X). However, nodejs does not use the keychain to get its list of CA's to trust. I don't control the ionic-cli app so I can't simply pass in a { ca: } property to the https module. I could also see this being a problem for any node app which I do not control. Is it possible to tell nodejs to trust a CA?
I wasn't sure if this belonged in Information Security or any of the other exchanges...

Node.js 7.3.0 (and the LTS versions 6.10.0 and 4.8.0) added NODE_EXTRA_CA_CERTS environment variable for you to pass the CA certificate file. It will be safer than disabling certificate verification using NODE_TLS_REJECT_UNAUTHORIZED.
$ export NODE_EXTRA_CA_CERTS=[your CA certificate file path]

You can specify a command line option to tell node to use the system CA store:
node --use-openssl-ca
Alternatively this can be specified as an environment variable, if you are not running the node CLI directly yourself:
NODE_OPTIONS=--use-openssl-ca

There is an undocumented, seemingly stable, API for appending a certificate to the default list:
const tls = require('tls');
const secureContext = tls.createSecureContext();
// https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt
secureContext.context.addCACert(`-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----`);
const sock = tls.connect(443, 'host', { secureContext });
For more information, checkout the open issue on the subject: https://github.com/nodejs/node/issues/27079

I'm aware of two npm modules that handle this problem when you control the app:
https://github.com/fujifish/syswide-cas (I'm the author of this one)
https://github.com/coolaj86/node-ssl-root-cas
node-ssl-root-cas bundles it's own copies of nodes root CAs and also enables adding your own CAs to trust. It places the certs on the https global agent, so it will only be used for https module, not pure tls connections. Also, you will need extra steps if you use a custom Agent instead of the global agent.
syswide-cas loads certificates from pre-defined directories (such as /etc/ssl/certs) and uses node internal API to add them to the trusted list of CAs in conjunction to the bundled root CAs. There is no need to use the ca option since it makes the change globally which affects all later TLS calls automatically.
It's also possible to add CAs from other directories/files if needed.
It was verified to work with node 0.10, node 5 and node 6.
Since you do not control the app you can create a wrapper script to enable syswide-cas (or node-ssl-root-cas) and then require the ionic-cli script:
require('syswide-cas'); // this adds your custom CAs in addition to bundled CAs
require('./path/to/real/script'); // this runs the actual script

This answer is more focused towards package maintainers/builders.
One can use this method if you do not want end users to rely on additional environment variables.
When nodejs is built from source, it (by default, can be overridden) embeds the Mozilla CA certificate database into the binary itself. One can add more certificates to this database using the following commands:
# Convert your PEM certificate to DER
openssl x509 -in /path/to/your/CA.pem -outform der -out CA.der
# Add converted certificate to certdata
nss-addbuiltin -n "MyCompany-CA" -t "CT,C,C" < CA.der >> tools/certdata.txt
# Regenerate src/node_root_certs.h header file
perl tools/mk-ca-bundle.pl
# Finally, compile
make install

Related

HTTPS in Nodejs - error:06065064 digital envelope routines evp_decryptfinal_ex bad decrypt

We tried to install our Hapis (Nodejs Version 14) Web service on our customer's server. It ran under HTTP for months, but when we went to enable HTTPS with the appropriate paths to the cert and key it fails when the service starts up with:
error:06065064:digital envelope routines:EVP_Decryptfinal_ex:bad decrypt
Their certificate and key are generated using the Venafi online portal. It gave them a crt and key. The crt uses a Signature algorithm: sha256RSA, Signature hash algorithm of sha256, and Thumbprint algorith: sha1.
Also, the private key is a RSA PRIVATE KEY with Proc-Type: 4,ENCRYPTED and DEK-Info: DES-EDE3-CBC.
I am not sure what is going on, because HTTPS works fine on our development servers.
Is the problem in HapiJS?
Is the problem with the certificate or key themselves?
Is there an Node option I need to be passing in when creating the service?
Please help.
The specified error 06065064:digital envelope routines:EVP_Decryptfinal_ex:bad decrypt occurs in an SSL/TLS connection using OpenSSL (which is what nodejs modules like tls and https actually use) when the privatekey is encrypted (with a passphrase) and the correct passphrase is not provided to decrypt it. The described file format, beginning with a line -----BEGIN RSA PRIVATE KEY----- followed by lines Proc-Type: and DEK-Info: is indeed one of the encrypted formats used by OpenSSL. Specifically this is the encrypted 'traditional' or 'legacy' format; the PKSC8 format added about 2000 but still considered new(!) uses -----BEGIN ENCRYPTED PRIVATE KEY----- and no 822-style headers, only base64 (of the encrypted structure defined by PKCS8); see ursinely-verbose https://security.stackexchange.com/questions/39279/stronger-encryption-for-ssh-keys/#52564 about OpenSSH's use of OpenSSL, which is basically the same as nodejs's use.
The tls module and others that build on it including https ultimately read the key(s) and cert(s) using tls.createSecureContext which accepts in options a member passphrase, or if you need to use multiple keys (and certs) you can provide a passphrase for each key as described in the linked doc.
Alternatively you can avoid the need for a passphrase by converting the key to an unencrypted file, if acceptable under applicable security policies and regulations. (Good policies may prohibit this, but they usually also prohibit getting the privatekey from or providing it to any other system, especially one 'online' somewhere, and your customer is doing the latter.) To retain traditional format do
openssl rsa -in oldfile -out newfile
# and give the passphrase when prompted, or see the man page about -passin
or you can use the 'new' PKCS8 format with
openssl pkey -in oldfile -out newfile
# technically only in release 1.0.0 up, but pretty much everyone is there now
#
# or in all versions back to about 2000
openssl pkcs8 -topk8 -nocrypt -in oldfile -out newfile
For me this error occured after pulling some old code that was workign to a fresh system, I was using too current of a node Version, I downgraded from 17 to 16 and that solved my problem.
Tried checking the github issues related to the TLS, handshake and versions. But couldn't find any.
The final fix was the one suggested by #Greggory Wiley.
Installed nvm - downgraded the node and npm versions. Recomplied the code. And it worked.
In my case I was exporting a certificate from windows to linux inside a docker using openSSL and facing this error.
The problem was in the versions of OpenSLL, when I was converting .pfx file to .crt and .key I was using 3.0.x version on windows, and on linux I had 1.1.1 installed. After I did the same using the same version of OpenSLL on windows it worked.
I had the same issue, I would say that the accepted answer is good expect it does not provide an example where the passphrase is used.
Here's code that worked in my case for express.js
const server = https
.createServer(
{
key: fs.readFileSync("./root/ca/cakey.pem"),
cert: fs.readFileSync("./root/ca/cacert.pem"),
passphrase: "abcdefg",
},
app
)
.listen(PORT, () => {
console.log(`Secure server listening on port:${PORT}`);
});

Disable TLS1.0 and TLS1.1 on Cherrypy, Python3

I am trying to disable TLS1.0, TLS1.1, SSL2, and SSL3 on my cherrypy server. I have seen the other stack over flow posts regarding how to disable them however, when I follow the code samples, I get the following error "ValueError: certfile must be specified for server-side operations". The windows service is still running, however I cannot load any pages. I've tried adding the certificate_chain as well, but that prevents cherrypy from running at all.
I am running cherrypy as a windows service, python 3.4.4, cherrypy 5.0.1, pyOpenSSL 19.0.0.
I've tried using the built in SSl library and with pyOpenSSL, they both result in the same error.
import OpenSSL.SSL as ssl
context = ssl.Context(ssl.SSLv23_METHOD)
context.set_cipher_list('ECDHE-RSA-AES256-GCM-SHA384')
context.set_options(ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
context.use_privatekey_file('myfile.key')
context.use_certificate_file('myfile.cer')
cherrypy.config.update({
'global':{
'server.socket_host':'0.0.0.0',
'server.socket_port': 0000, # https, however not using the port 443
'server.ssl_context' : context,
},
})
Is myfile.cer in PEM format? According to the docs it appears that PEM is the default filetype, which may be the cause of the error.
I'm also trying to figure out how to use ECDHE with Cherrypy, but with other web servers to use ECDHE there needs to be a curve file to generate the ephemeral key instead of a static key file (RSA style). It doesn't appear that Cherrypy has built-in capabilities for the curve file so it may only be possible with pyOpenSSL. The command to get the supported curves is OpenSSL.crypto.get_elliptic_curves() and you can specify the curve you want with context.set_tmp_ecdh(curve).
Looks like there may be some issues in syntax: ssl.OP_NO_TLSv1 s/b ssl.SSL.OP_NO_TLSv1 (per the pyOpenSSL documentation). This affects all of the OP* variables.
Oh, wait... nvrmd that.

NodeJS - Get certificate Chain from P7B file

I'm trying to take a CMS base64 encoded string, convert it into a pkcs7 file and extract the leaf and intermediate certificates using javascript/nodejs, similar to the following openssl command:
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
I've read almost every article and see solutions in other languages, but not for node. I understand you can achieve what I need using node-forge, but node-forge does not support ECC algorithms. Anyone know of any other solutions/npm packages that can help me accomplish this? Please help me. I'm very new to this.
Did you see PKI.js for Node.js? It is a pure JavaScript library implementing the formats that are used in PKI applications. It is based on the W3C Web Cryptography API with full support for all "Suite B" algorithms in CMS messages. Code snippet submitted by OP:
const cmsSignedBuffer = stringToArrayBuffer(fromBase64(token.signature));
const asn1 = asn1js.fromBER(cmsSignedBuffer);
const cmsContentSimpl = new pkijs.ContentInfo({ schema: asn1.result });
const cmsSignedSimpl = new pkijs.SignedData({ schema: cmsContentSimpl.content })
Another approach is to use a wrapper for openssl like openssl-nodejs, for example. The wrapper simply spawns a child process to invoke openssl. Thus, openssl must be installed on system where the Node.js application is deployed.

certificateValidationMode seems to be ignored by Azure

I'm developing a solution (web app) for windows azure that uses WSFederation for authentication. Since im on azure testing phase (local tests were successful) i've decided to use the same self signed certificate i've been using for local testing.
The problem here is that i'm getting an error saying that "The X.509 certificate CN=mytestsite.com.br is not in the trusted people store. The X.509 certificate CN=mytestsite.com.br chain building failed. The certificate that was used has a trust chain that cannot be verified".
This error makes complete sense because it's a self-signed certificate, but since im on a staging enviroment (and I would absolutally hate to ask my sponsor for extra-budget for a valid certificate right now...) I would like to use the self-signed one anyway. So I changed the certificateValidationMode to "None", but I still get the same validation error... It seems that the validation mode is being ignored!!!
Does anyone knows what can I do to make things work? (buy a valid certificate would be my last shot because they're quite expensive for my budget at this point...)
I would just use the self-signed cert, use code to "deploy" it to your Trusted People store, and all should be well. This will be closer to real production use anyways (and if you use PeerTrust in production, you'll have to do the same thing there as well, even with a real certificate).
private static void CopyServerCertIntoPeopleStore()
{
var myStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
myStore.Open(OpenFlags.ReadOnly);
var peopleStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
var cert = myStore.Certificates.Find(
X509FindType.FindByThumbprint,
SettingFetcher.GetSetting(SettingFetcher.SettingType.ApplicationVariable, "WcfServiceCertificateThumbprint"),
true
).OfType<X509Certificate2>().First();
peopleStore.Open(OpenFlags.ReadWrite);
peopleStore.Add(cert);
}
Use something like this, just replace the SettingFetcher with RoleEnvironment.GetConfigurationSettingValue or whatever to grab the thumbprint.
I managed to make it by adding "certificateValidationMode" to the STS web.config.

Create x509 certificate with openssl/makecert tool

I'm creating a x509 certificate using makecert with the following parameters:
makecert -r -pe -n "CN=Client" -ss MyApp
I want to use this certificate to encrypt and decrypt data with RSA algoritm.
I look to generated certificate in windows certificate store and everything seems ok (It has a private key, public key is a RSA key with 1024 bits and so on..)
Now i use this C# code to encrypt data:
X509Store store = new X509Store("MyApp", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Client", false);
X509Certificate2 _x509 = certs[0];
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)_x509.PublicKey.Key)
{
byte[] dataToEncrypt = Encoding.UTF8.GetBytes("hello");
_encryptedData = rsa.Encrypt(dataToEncrypt, true);
}
When executing the Encrypt method, i receive a CryptographicException with message "Bad key".
I think the code is fine. Probably i'm not creating the certificate properly.
Any comments?
Thanks
---------------- EDIT --------------
If anyone know how to create the certificate using OpenSsl, its also a valid answer for me.
To allow the key to be used for encryption, you should use the -sky-option. Per default ´makecert` uses the AT_SIGNATURE key specification, which will not work with encryption/decryption. Instead have it use the AT_KEYEXCHANGE specification by issuing the following command:
makecert -r -pe -n "CN=Client" -ss MyApp -sky Exchange
(Remember to delete the previous key or use another container-name).
This was another page I stumbled across when I was trying to find examples of makcert usage with x509 certificates and rsa using c#, and unfortunately it only provided part of the solution. I put all the bits together in a blog entry that people might be interested in, and it can be found here:
http://nick-howard.blogspot.com/2011/05/makecert-x509-certificates-and-rsa.html

Resources