APN-Node: Error when loading PEM file - node.js

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

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

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

Unable to pass SSL Client Cert to IBM MQ from Nodejs/MQRC_KEY_REPOSITORY_ERROR/MQRC_SSL_INITIALIZATION_ERROR

I'm trying to push messages to IBM MQ through secure SSL Channel. This is my first nodejs application that integrates with MQ, though I've done this in the past using .net. I've tried the following code, but getting exception below exception.
let hConn;
let cD = new msmq.MQCD();
let cno = new msmq.MQCNO();
let oD = new msmq.MQOD()
oD.ObjectName = config.queueName;
cD.ConnectionName = config.port;
cD.ChannelName = config.channel;
cD.CertificateLabel = config.certLabel;
cD.SSLCipherSpec = config.cipherSpec;
msmq.Connx(config.queueManager, cno, (err, hConn) => {
msmq.Open(hConn, oD, openOptions, (err, hObj) => {
let mqmd = new msmq.MQMD();
let pmo = new msmq.MQPMO();
msmq.Put(hObj, mqmd, pmo, message, (err) => {})
})
})
Error:
error fields.name:MQError fields.message:CONNX: MQCC = MQCC_FAILED [2] MQRC = MQRC_KEY_REPOSITORY_ERROR [2381] [Pkg ver = 0.5.1] fields.mqrcstr:MQRC_KEY_REPOSITORY_ERROR fields.stack:MQError: CONNX: MQCC = MQCC_FAILED [2] MQRC = MQRC_KEY_REPOSITORY_ERROR
I'm sure that it has something to do with this line of code, but I couldn't find any other apt property either to set the cert name or the path as I did in .net (SSL_CERT_STORE_PROPERTY, this is the property I used there). Any help is highly appreciated.
cD.CertificateLabel = config.certLabel;
Here is my latest code, but still getting the same error
let hConn;
let cD = new msmq.MQCD();
let cno = new msmq.MQCNO();
let oD = new msmq.MQOD();
let cO=new msmq.MQSCO;
oD.ObjectName = config.queueName;
cD.ConnectionName = config.port;
cD.ChannelName = config.channel;
cD.SSLCipherSpec = config.cipherSpec;
cO.KeyRepository = 'path of the cert file' (.pfx)
cno.ClientConn = cD;
cno.SSLConfig=cO;
msmq.Connx(config.queueManager, cno, (err, hConn) => {
msmq.Open(hConn, oD, openOptions, (err, hObj) => {
let mqmd = new msmq.MQMD();
let pmo = new msmq.MQPMO();
msmq.Put(hObj, mqmd, pmo, message, (err) => {})
})
})
Solution:
I ended up solving my problem by passing both keyRepo and certLabel and the cert files in .kdb and .sth format. There was an issue that I faced with username being more than 12 characters, hence I used certLabel with less than 12 characters identifier after the standard 'ibmwebspheremq'
cO.CertificateLabel = config.certLabel;
cO.KeyRepository = 'path of the cert file and key name' (no extension in the file name)
Client .ini file (in its SSL section) CertificateLabel attribute - this step from the link below is must to get the above code working, in fact you don't even need to pass certificate label if you have this set up at the server side
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.0.0/com.ibm.mq.sec.doc/q014340_.htm
The MQSCO structure is used to name the keystore. Then that is attached to the MQCNO along with the MQCD.
In MQ, the first thing you need to look for is the Reason Code.
MQError: CONNX: MQCC = MQCC_FAILED [2] MQRC =
MQRC_KEY_REPOSITORY_ERROR
Reason Code of MQRC_KEY_REPOSITORY_ERROR (2381) is the important bit. If you look in the MQ KnowLedge Center here you will see:
On an MQCONN or MQCONNX call, the location of the key repository is either not specified, not valid, or results in an error when used to access the key repository. The location of the key repository is specified by one of the following:
The value of the MQSSLKEYR environment variable (MQCONN or MQCONNX
call), or
The value of the KeyRepository field in the MQSCO structure
(MQCONNX call only).
For the MQCONNX call, if both MQSSLKEYR and KeyRepository are specified, the latter is used.

Environment variable not accepting for azure

I have defined environment variables for azure in terminal like as below,
SET AZURE_STORAGE_ACCOUNT=accountname
SET SAS_TOKEN="sr=c&sp=rwl&sig=signatureKey%3D&sv=2017-04-17&se=2018-03-10"
After defining the varibale, i have called these variables inside the azure blob storage function like,
AZURE_STORAGE_ACCOUNT= process.env.AZURE_STORAGE_ACCOUNT;
SAS_TOKEN = process.env.SAS_TOKEN;
var blobUri = "http://"+AZURE_STORAGE_ACCOUNT+".blob.core.windows.net";
var blobService = azureStorage.createBlobServiceWithSas(blobUri, SAS_TOKEN).withFilter(new azureStorage.ExponentialRetryPolicyFilter());
blobService.createBlockBlobFromLocalFile('mycontainer', 'sparks-events-data', fileToWrite, function(error, result, response) {
if (!error) {
console.log("upload successful..");
} else {
console.log(error);
}});
When i run above file i am getting error like
StorageError: Server failed to authenticate the request. Make sure
the value of Authorization header is formed correctly including the
signature.
But when i call the SAS token directly inside the code it works fine. I am using like this
var sasKey = "sr=c&sp=rwl&sig=signatureKey%3D&sv=2017-04-17&se=2018-03-10";
var blobService = azureStorage.createBlobServiceWithSas(blobUri, sasKey ).withFilter(new azureStorage.ExponentialRetryPolicyFilter());
it works fine for me. I need to set the SAS token as environment variable. Here what i am missing. Please someone suggest me a solution for this.
Thanks in Advance,
export those env vars instead. Exporting a variable causes the variable to be inherited by subsequent processes started in that shell.
export SAS_TOKEN="..."
You may want to take a look at this npm package before rolling your own env var secrets handling logic. We already cracked this, as a people —
https://github.com/motdotla/dotenv
In Windowsland, just use set, but make sure your Node process starts in the same terminal window:
C:\lab> set KEY="secret"
C:\lab> type app.js
let key = process.env.KEY;
console.log('KEY is ' + key);
C:\lab> node app.js
KEY is "secret"
Watch out for those quotes!

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.

Resources