Matthew1471 / Enphase-API

Enphase-API is an unofficial project providing an API wrapper (including local/LAN Gateway API) and the documentation for Enphase®'s products and services.
GNU General Public License v3.0
72 stars 10 forks source link

Handle updated self-signed cert #25

Open mblythe86 opened 3 weeks ago

mblythe86 commented 3 weeks ago

I have a script that runs regularly to collect data from my enphase system, and put it into a local DB for my own custom charts.

A couple days ago, this script started failing with errors like this:

 ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: authority and subject key identifier mismatch (_ssl.c:1007)

I eventually realized that this was happening because the self-signed cert that the Envoy gateway was using had been updated. Based on the advice in Documentation/Wrappers/Python/README.adoc and the examples in Python/examples, I called Gateway.trust_gateway() to store the cert for future validation. I moved that captured cert file aside, and the next time I ran my script it succeeded, and created a new configuration/gateway.cer file.

Old cert:

> openssl x509 -noout -text -in configuration/gateway.cer.bak
Certificate:
    Data:
...snip...
        Validity
            Not Before: Aug 28 15:18:27 2023 GMT
            Not After : Aug 27 15:18:27 2024 GMT

New cert:

> openssl x509 -noout -text -in configuration/gateway.cer
Certificate:
    Data:
...snip...
        Validity
            Not Before: Jun 21 11:41:01 2024 GMT
            Not After : Jun 21 11:41:01 2025 GMT

I realize that it is more secure to use this trusted cert, but I'm not sure what a secure way to handle this update would look like. Obviously, accepting any new cert upon validation failure would be no better than not saving the cert in the first place. I had thought that I could reduce the window by only accepting a new cert close to the "Not After" validity date, but in my case, the cert was changed on Aug 21 - over 2 months before it would have expired. I could accept any new cert with the same Issuer/Subject fields, since these seem to be specific to my Envoy gateway (it has my serial number in it). But an attacker could just as easily read the current cert, and spoof a cert with those field values, too.

Please update the documentation to mention this case, and update the examples to gracefully handle it.

mblythe86 commented 3 weeks ago

I re-worded my bug after I realized that it was my own code (modeled after the examples in this repo) that was storing off the cert.

Matthew1471 commented 1 week ago

Apologies for the delay, I have been volunteering for a political party in the UK general elections.

The IQ Gateway does seem to renew its certificate early before expiration (a few months or so I've observed). As you've identified, this library stores the certificate as a one time operation when trust_gateway() is called to implement certificate pinning and the behaviour is indeed documented in the Python wrapper documentation.

It's not advisable to trust any HTTPS connection with any self-signed certificate as something on your network could then pretend to be the IQ Gateway and steal your credentials, but if that is not a concern ensuring you simply do not call trust_gateway() in your code will prevent the library from storing the certificate (delete the CER if you have already called it before) and binding to it.. and therefore any old HTTPS response will be valid.

It's of course possible to have your code call trust_gateway() at any point - but I suppose if you are routinely just trusting the certificate you are presented with you'd be better off not implementing any certificate pinning at all as you're at that point trusting whatever is available on your network. I suppose one option might be to have error handling that calls trust_gateway() if there is a certificate mismatch and the cached certificate is within a few months of expiry - for 10 or so months of the year you'd at least then be implementing certificate pinning.

For me an annual outage is acceptable - as with most organisations they have to have a brief outage when they rotate certificates and crypto material... but as stated the library does support the YOLO approach to trusting HTTPS servers by simply removing the trust_gateway() calls and ensuring there's no CER in the folder.

Is there anything further I can do to improve the library or its behaviour - or should I just close this issue? We are of course not in control of when the cert gets renewed - although I do have unofficial access to the IQ Gateway source code so could find out exactly what date the renewal happens (although it may be remotely invoked)?

Matthew1471 commented 1 week ago

Looking at the IQ Gateway code /opt/emu/scripts/certificate_check will re-generate if there are less than 100 days on the certificate and this is called from /part/1210/etc/cron.daily/certificate_check_cron which is executed at the following time:

# m h dom mon dow user  command
...
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )

So at 6:25 AM each day and < 100 days before expiration a new certificate will be generated. You could probably automate the openssl x509 -enddate -noout -in gateway.cer, check it against 100 * 3600 * 24 delete the CER and restart your script too within a few minutes of 6:25 AM each day (6:26 AM?).

Adding a hard dependency on x509.load_pem_x509_certificate() in the examples and performing an expiration check (using the not_valid_after property and then erasing the CER and re-calling trust_gateway) before every API call might convolute the examples a little?