node.jsssl-certificate

apply custom pemEncoded to httpsAgent failed


I am getting problem unable to verify the first certificate from my node server while tried to get response from https://testcall.zerophone.us/janus/info.

I tried to using get-ssl-certificate package to get the SSL cert, then using fs to write pemEncoded into cert.pem, and add it's to httpsAgent. Below is my code to get the cert before init express:

let httpsAgent
const handleServerCert = () => {
    return new Promise((resolve, reject) => {
        sslCertificate.get('testcall.zerophone.us').then(function (certificate) {
            console.log('certificate', certificate)
            fs.writeFile('cert1.pem', certificate.pemEncoded, async (err) => {
                if (err) {
                    console.error('create cert failed: ' + err);
                    reject()
                } else {
                    console.log('created cert success!\n')
                    // https://stackoverflow.com/a/60020493/1308590
                    const certFile = path.resolve(__dirname, 'cert1.pem')
                    rootCas.addFile(certFile);
                    httpsAgent = new https.Agent({ ca: rootCas });
                    https.globalAgent.options.ca = rootCas;
                    resolve()
                }
            });
        }).catch(error => {
            console.log(`can not get server cert:${SERVER_URL} \nerror:  ${error}`)
            reject()
        });
    })
}
const getAgent = () => {
    return httpsAgent
}

module.exports = {
    handleServerCert,
    getAgent
}

After initExpress, I used global method to add httpsAgents into request:

static defaultHeader() {
        return {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            agent: getAgent()
        }
    }

But I still got error unable to verify the first certificate, I dont know why I still got this, but If I open website from chorme, then export cert, using this, everything work normally. Can someone help me to know my problem?


Solution

  • As the answer you reference correctly says, you need to add the intermediate CA cert (aka chain [CA] cert) to your local truststore. get-ssl-certificate doesn't get the intermediate CA cert, or any CA cert; it gets only and always the server aka leaf cert. That is of no help whatsoever in forming a trust chain.

    OTOH browsers like Chrome, as the answer correctly says, can often fetch the missing cert (mostly and here using AIA). If you export the intermediate (not leaf) cert from a browser and add it in your code, that will work. So will the method in that answer using OpenSSL and curl. Or if you want node code:

    // I'm not a promiser so that's left as an exercize, plus whatever errorhandling you want
    const tls = require('tls'), http  = require('http'), fs = require('fs');
    
    function pemEncode(b,l){ return "-----BEGIN "+l+"-----\n"
      + b.toString('base64').replace(/.{64}/g,'$&\n')
      + "\n-----END "+l+"-----\n";
    }
    function getCert(u){ http.get(u, (res)=>{ var d=[];
      res.on('data',(chunk)=>{ d.push(chunk) });
      res.on('end',()=>{ fs.writeFileSync('cert1.pem',pemEncode(Buffer.concat(d),'CERTIFICATE')) });
      });
    }
    var host = 'testcall.zerophone.us', port = 443;
    // many servers today require SNI to get their cert (or anything else),
    // in which case add servername:host, but this one doesn't
    const s = tls.connect( {host,port,rejectUnauthorized:false}, ()=>{
      getCert( s.getPeerCertificate().infoAccess['CA Issuers - URI'][0]);
      s.destroy() });
    

    Note that about 8 lines of this (with the AIA.ca fetch removed) is equivalent to the whole get-ssl-certificate package.