Node: getPeerCertificate() only works once on the same host

Created on 20 Nov 2015  ·  7Comments  ·  Source: nodejs/node

Requesting from the same host twice using https will only return the certificate on the first connection.

var req = https.request(url);
req.once('socket', function(socket) {
    socket.once('secureConnect', function() {
        var cert = socket.getPeerCertificate(); // detailed or not does not seem to matter
        // cert is ok the first time, empty the second time.
    });
});

I would expect to get back the cert every time even if the connection is reused.

I'm caching it now but it's a bad workaround as other code could be using https.request to connect to the same host without using the cache which means that my code will never see the cert.

Also caching the cert means I'm relying on the assumption that the fact that I did not get a cert this time means the cert in my cache still applies and is valid.

https tls

Most helpful comment

So I've been struggling with this as well and here is how I get a fresh cert and fingerprint every time.

let options = {
    hostname: url,
    port: port,
    method: 'GET',
    headers: {
        'User-Agent': 'Node/https'
    },
    //disable session caching   (ノ°Д°)ノ︵ ┻━┻
    agent: new https.Agent({
        maxCachedSessions: 0
    })
};

let req = https.request(options);

req.on('error', (e) => {
    if(e.code === "ECONNRESET") {/* I don't care about these */}
    else console.error(e);
});

req.on('socket', function(socket) {
    socket.on('secureConnect', function() {
        let cert = socket.getPeerCertificate();
        callback(cert);
        return req.abort();
    });
});

callback(cert) returns the certificate each time.

All 7 comments

/cc @nodejs/http

The session is resumed the next time, so this info is no longer available. It is actually a pretty good question on how it should work.

@nodejs/crypto : what are your thoughts on this? Should we store the cert in the session cache?

If we store the cert, we need to check its revocation at every connection even if it is a short time.
To get a certificate in every request from the connection, I think it is better to make it be full handshake not resumption.
IIRC, using no agent is one idea. Or is it better to add a new options to disable a session cache?

@shigeki technically, the session should expire if the cert has expired. I mean, if the server has made session timeout way too high, it is its fault, and not node.js.

I'm definitely +1 for an option to disable it, however I think that the particular request code should not depend on agent configuration.

@f0zi could using socket.isSessionReused() be an option for you? If the return value is true, then there generally is no certificate available.

@indutny Not really, if I don't see the cert I have to abort the connection. Since I'm using this from inside node-fetch I can't easily restart the request internally (I tried tlsSocket.renegotiate but that just seems to abort the fetch request with "closed socket"). So I'll have client code doing fetch() dealing with TLS connections. And at that point, if I see this correctly (I have not tried this), I would have to deal with replacing the global agent to make it not reuse the connection.

Note that I would be ok with reusing the request as long as it's the same domain (as sent with SNI). If it is talking to the same host but for a different domain as requested it would have to request the right cert from the host.

Would it be possible to limit a request to reusing a session to the domain indicated by SNI or trigger a renegotiation otherwise?

@f0zi SNI should be indeed accounting when doing https requests. Filed a PR to fix this issue: https://github.com/nodejs/node/pull/4389, thanks for pointing out!

Regarding the cert presence, I think that this is just a documentation bug. The cert simply cannot be obtained if the session was reused, but it should be safe to assume that the connection is valid and secure if the session is reused.

Cheers!

So I've been struggling with this as well and here is how I get a fresh cert and fingerprint every time.

let options = {
    hostname: url,
    port: port,
    method: 'GET',
    headers: {
        'User-Agent': 'Node/https'
    },
    //disable session caching   (ノ°Д°)ノ︵ ┻━┻
    agent: new https.Agent({
        maxCachedSessions: 0
    })
};

let req = https.request(options);

req.on('error', (e) => {
    if(e.code === "ECONNRESET") {/* I don't care about these */}
    else console.error(e);
});

req.on('socket', function(socket) {
    socket.on('secureConnect', function() {
        let cert = socket.getPeerCertificate();
        callback(cert);
        return req.abort();
    });
});

callback(cert) returns the certificate each time.

Was this page helpful?
0 / 5 - 0 ratings