openssl: ca key too small but key size is 4096 - node.js

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);

Related

Trying to symmetrically encrypt a value for storage in the client (httpOnly cookie) and having an issue decrypting

I am trying to encrypt a value on my server with a private key to store it on the client within an httpOnly cookie.
I am having trouble with the encryption/decryption lifecycle
function encrypt(input) {
const encryptedData = crypto.privateEncrypt(
privateKey,
Buffer.from(input)
)
return encryptedData.toString('base64')
}
function decrypt(input) {
const decryptedData = crypto.privateDecrypt(
{ key: privateKey },
Buffer.from(input, 'base64'),
)
return decryptedData.toString()
}
const enc = encrypt('something moderately secret')
const dec = decrypt(enc)
console.log(dec) // 'something moderately secret'
However the crypto.privateDecrypt function is throwing with
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Side question, is it safe to reuse the same private key the server uses to sign JWTs. It's an rsa key generated using ssh-keygen -t rsa -b 4096 -m PEM -f RS256.key
So, you don't use crypto.privateEncrypt() with crypto.privateDecrypt(). That's not how they work. Those functions are for asymmetric encryption, not for symmetric encryption. You use either of these two pairs:
crypto.publicEncrypt() ==> crypto.privateDescrypt()
crypto.privateEncrypt() ==> crypto.publicDecrypt()
So, that's why you're getting the error you're getting. The nodejs doc for crypto.privateDecript() says this:
Decrypts buffer with privateKey. buffer was previously encrypted using the corresponding public key, for example using crypto.publicEncrypt().
If what you really want is symmetric encryption, there are a bunch of options in the crypto module for that. There are some examples shown here: https://www.section.io/engineering-education/data-encryption-and-decryption-in-node-js-using-crypto/ and https://fireship.io/lessons/node-crypto-examples/#symmetric-encryption-in-nodejs.

Encrypt data on angular decrypt on Nodejs

What's the way of doing this? I tried using CryptoJS on angular and Crypto Module on node, without success I keep getting description error
Angular encrypt method :
_rsaEnc(p) {
var e = new JSEncrypt();
const key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/kaskaskKAS';
e.setPublicKey(key);
return e.encrypt(p);
}
Node decrypt method
privK = {
key: fs.readFileSync('./app/services/private.pem').toString(),
passphrase: 'xxxxxx'
};
var buf = Buffer.from(base64Data, 'base64');
origData = crypto.privateDecrypt(privK, buf);
return origData.toString('utf-8');
error:
Error: error:040A1079:rsa
routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Ended up changing the angular lib to jsencrypt, CryptoJS doesnt support RSA, and changed node lib to node-rsa to set the encryption scheme to pkcs1 with
myDecrypter.setOptions({encryptionScheme: 'pkcs1'});

Using node.js to verify a X509 certificate with CA cert

I am looking for a node.js way to verify a client certificate in X509 format with a CA certificate which was given to me (none of those are created/managed by me, my software only has to verify what is beeing sent to it).
I have found several modules for this job, however I am having issues with each of them:
X509 is able to do it using x509.verify(cert, CABundlePath, cb), however it needs to read the certificates from FS, and I am having them in memory already. This is cumbersome as it will be done with each web request which reaches my app.
It seems like PKI.js is able to do it, however their examples don't work for me but complain about missing files, so I can't even try it out.
I tried node-forge, but while I am unsure if I use it correctly (they don't have any API documentation) its throwing a forge.pki.BadCertificate error from forge.pki.verifyCertificateChain(caStore, [ cer ], cb).
When trying pem, using a simple pem.verifySigningChain(cer, [ ca ], cb) would throw some error complaining about loading a file from /var/.... Even if it would work, I would avoid using this lib as its relying on the openssl command line tool, which I would like to avoid
Now I feel pretty stupid because I failed to get this simple task done with any of the above modules. Could someone point me to a simple solution which will allow me to verify the signature/validity of a X509 certificate using a given CA certificate? :s
[edit] Basically I would need openssl verify -verbose -CAfile ca-crt.pem client1-crt.pem in Node.js but without dependencies to the openssl command line tool and without temporarily saving the certs to disk.
[edit2] Would it be possible to just use https://nodejs.org/api/crypto.html#crypto_verify_verify_object_signature_signatureformat?
I finally managed to do it using node-forge. Heres a working code example:
let pki = require('node-forge').pki;
let fs = require('fs');
let caCert;
let caStore;
try {
caCert = fs.readFileSync('path/to/ca-cert.pem').toString();
caStore = pki.createCaStore([ caCert ]);
} catch (e) {
log.error('Failed to load CA certificate (' + e + ')');
return....;
}
try {
pki.verifyCertificateChain(caStore, [ cert ]);
} catch (e) {
return handleResponse(new Error('Failed to verify certificate (' + e.message || e + ')'));
}
Both certificates shall be given in base64 encoded PEM format/js string.
verifyCertificateChain checks the certifitate validity (notBefore/notAfter) as well as verifies the given CA chain.
I am not 100% sure if this is the best approach, or if this library is doing a good job, since their source code of verifyCertificateChain is full of #TODOs, so maybe this is not ready for production?
But at least I have a somewhat working solution. Probably it would be better to create a node module which wraps the libssl c calls, but thats just a lot of effort for this small task.
You can also do like this if you want to check the using the client certificates from the http request directly :
// retrieve certificates from the request ( in der format )
clientCert = req.connection.getPeerCertificate(true).raw.toString('base64'))
Method to convert the der certificate to pem and verify against the castore.
const caCert = fs....
const ca = pki.certificateFromPem(caCert)
const caStore = pki.createCaStore([ ca ])
const verify = (clientCert, next) => {
try {
const derKey = forge.util.decode64(clientCert)
const asnObj = forge.asn1.fromDer(derKey)
const asn1Cert = pki.certificateFromAsn1(asnObj)
const pemCert = pki.certificateToPem(asn1Cert)
const client = pki.certificateFromPem(pemCert)
return pki.verifyCertificateChain(caStore, [ client ], cb)
} catch (err) {
next(new Error(err))
}
}
I did not find a better way to verify the client "der" certificate from the request.
fas3r
This works for me:
const fs = require('fs'), pki = require('node-forge').pki
var ca = pki.certificateFromPem(fs.readFileSync('ca.pem', 'ascii'))
var client = pki.certificateFromPem(fs.readFileSync('client.pem', 'ascii'))
try {
if (!ca.verify(client)) throw 'verify failed'
} catch (err) {
console.log(err)
}
try/catch was required, because .verify threw an error (instead of returning false) in my case.

APN-Node: Error when loading PEM file

I am trying to get apn-node to push to my devices. The server is hosted on Heroku, so I do not want to commit the file. Also, I do not want to fetch it from a remote server but instead put it in an environment variable.
I already tried the following (source):
I created and downloaded the certificate from Apple and now have it in my Keychain. I exported it as a *.p12 file and converted it with openssl pkcs12 -in dev.p12 -out dev.pem -nodes into a *.pem file.
To set the environment variable, I did export APN_CERT="$(cat dev.pem)". When I print it out in my application it shows the certificate perfectly fine.
However, when I actually send a notification (and node-apn opens the connection) it throws an [Error: wrong tag].
This error is emitted by the crypto module:
apn Raising error: +4ms [Error: wrong tag] undefined undefined
apn Error occurred with trace: +1ms Error: wrong tag
at Object.exports.createCredentials (crypto.js:176:17)
at Object.exports.connect (tls.js:1344:27)
at apnSocketLegacy
The module also throws a APN transmission error: moduleInitialisationFailed (Code: 513).
I was unable to find any useful information other than that this could be related to the crypto module itself of node itself. That's why I suspect I did something wrong when creating the certificate but thankful for any guiding advice.
I found this guide for apns-sharp which actually described how to generate a valid .p12 file.
Still, writing it into an environment variable did not work. My code for reading it is: new Buffer(certString, 'binary') but I thought it still was not supplied in a correct format.
The solution for me was to actually read the buffer directly from a file via fs.readFileSync.
To get the env variable to work you could encode the file via cat cert.p12 | base64 and load it as such with new Buffer(certString, 'base64'). This finally worked for me.
The preference here would be to use an encrypted p12 stored alongside the applciation and specify a passphrase which you set via an environment variable.
I was unable to replicate your problem using the following script
var apn = require("apn");
var token = "<token>; // iPad
var service = new apn.connection({
cert: process.env.APN_CERT, key: process.env.APN_KEY
});
service.on("connected", function() {
console.log("Connected");
});
service.on("error", function(err) {
console.log("Standard error", err);
});
function pushNotification() {
var note = new apn.notification().setAlertText("Hello");
service.pushNotification(note, token);
service.shutdown();
}
pushNotification();
Run with:
$ export APN_CERT=$(cat certificates/cert.pem)
$ export APN_KEY=$(cat certificates/key.pem)
$ node apn-env.js
The error you are seeing "wrong tag" is from OpenSSL and suggests a parsing error of the data contained within the certificate data itself rather than the data being loaded incorrectly from the env. Loading PEM files from environment variables works correctly
var apn = require("apn");
var deviceToken = "device token";
var service = new apn.Provider({
cert: '.path to /cert.pem', key:'pat to ./key.pem'
});
var note = new apn.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 60; // Expires 1 minute from now.
note.badge = 3;
note.sound = "ping.aiff";
note.alert = " You have a new message";
note.payload = {'messageFrom': 'Rahul test apn'};
note.topic = "(Bundle_id).voip";
note.priority = 10;
note.pushType = "alert";
service.send(note, deviceToken).then( (err,result) => {
if(err) return console.log(JSON.stringify(err));
return console.log(JSON.stringify(result))
});
Load the pem files and run with the tokens

Encrypting data with a public key in Node.js

I need to encrypt a string using a public key (.pem file), and then sign it using a private key (also a .pem).
I am loading the .pem files fine:
publicCert = fs.readFileSync(publicCertFile).toString();
But after hours of scouring Google, I can't seem to find a way to encrypt data using the public key. In PHP I simply call openssl_public_encrypt(), but I don't see any corresponding function in Node.js or in any modules.
A library is not necessary. Enter crypto.
Here's a janky little module you could use to encrypt/decrypt strings with RSA keys:
var crypto = require("crypto");
var path = require("path");
var fs = require("fs");
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
var publicKey = fs.readFileSync(absolutePath, "utf8");
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
var decryptStringWithRsaPrivateKey = function(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey);
var privateKey = fs.readFileSync(absolutePath, "utf8");
var buffer = Buffer.from(toDecrypt, "base64");
var decrypted = crypto.privateDecrypt(privateKey, buffer);
return decrypted.toString("utf8");
};
module.exports = {
encryptStringWithRsaPublicKey: encryptStringWithRsaPublicKey,
decryptStringWithRsaPrivateKey: decryptStringWithRsaPrivateKey
}
I would recommend not using synchronous fs methods where possible, and you could use promises to make this better, but for simple use cases this is the approach that I have seen work and would take.
I tested this in Node.js 10, you can use encrypt/decrypt functions (small changes on Jacob's answer):
const crypto = require('crypto')
const path = require('path')
const fs = require('fs')
function encrypt(toEncrypt, relativeOrAbsolutePathToPublicKey) {
const absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey)
const publicKey = fs.readFileSync(absolutePath, 'utf8')
const buffer = Buffer.from(toEncrypt, 'utf8')
const encrypted = crypto.publicEncrypt(publicKey, buffer)
return encrypted.toString('base64')
}
function decrypt(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
const absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey)
const privateKey = fs.readFileSync(absolutePath, 'utf8')
const buffer = Buffer.from(toDecrypt, 'base64')
const decrypted = crypto.privateDecrypt(
{
key: privateKey.toString(),
passphrase: '',
},
buffer,
)
return decrypted.toString('utf8')
}
const enc = encrypt('hello', `public.pem`)
console.log('enc', enc)
const dec = decrypt(enc, `private.pem`)
console.log('dec', dec)
For the keys you can generate them with
const { writeFileSync } = require('fs')
const { generateKeyPairSync } = require('crypto')
function generateKeys() {
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: '',
},
})
writeFileSync('private.pem', privateKey)
writeFileSync('public.pem', publicKey)
}
The updated public/private decrypt and encryption module is URSA. The node-rsa module is outdated.
This Node module provides a fairly complete set of wrappers for the
RSA public/private key crypto functionality of OpenSSL.
npm install ursa
Use the node-rsa module. Here's a link to the test.js file that demonstrates usage.
TL;DR: URSA is your best bet. It's really funky that this doesn't come standard with Node.js' crypto.
Every other solutions I found either doesn't work in Windows or aren't actually encryption libraries. URSA, recommended by Louie, looks like the best bet. If you don't care about Windows, you're even more golden.
Note on Ursa: I had to install OpenSSL along with something called "Visual C++ 2008 Redistributables" in order to get the npm install to work. Get that junk here: http://slproweb.com/products/Win32OpenSSL.html
The breakdown:
Annoying additional manual installation steps for Windows
https://github.com/Obvious/ursa - probably the best of the lot
Not compatible with Windows
https://npmjs.org/package/rsautl - says BADPLATFORM
https://github.com/katyo/node-rsa - node-waf isn't available on Windows
https://github.com/paspao/simple_rsa_encrypt - unistd.h isn't on windows
https://npmjs.org/package/pripub - large amounts of linker errors, also not on GitHub
Not encryption libraries
https://github.com/substack/secure-peer
https://github.com/substack/rsa-json - just generates keys, but doesn't use them
https://github.com/substack/rsa-unpack - just unpacks PEM strings
This is literally all I could find.
This is not supported natively by Node.js version v0.11.13 or below, but it seems that next version of Node.js (a.k.a v0.12) will support this.
Here is the clue: https://github.com/joyent/node/blob/v0.12/lib/crypto.js#L358
See crypto.publicEncrypt and crypto.privateDecrypt
Here is the future documentation for this
https://github.com/joyent/node/blob/7c0419730b237dbfa0ec4e6fb33a99ff01825a8f/doc/api/crypto.markdown#cryptopublicencryptpublic_key-buffer

Resources