From ef84a109e8f492c83979443b6366fb8ca4028009 Mon Sep 17 00:00:00 2001 From: mathieui Date: Tue, 10 Oct 2017 00:52:44 +0200 Subject: Fix #3190 (TOFU the SPKI hash and not the whole cert) Makes letsencrypt renewals more pleasant. Thanks jonasw and aioxmpp for the ASN.1 wizardry --- doc/source/configuration.rst | 13 +++++++++---- poezio/core/handlers.py | 31 ++++++++++++++++++------------- setup.py | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index d9b3d6e9..51f1176d 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -62,11 +62,16 @@ and certificate validation. **Default value:** ``[empty]`` - The SHA-2 fingerprint of the SSL certificate as a hexadecimal string, - you should not touch it, except if know what you are doing. + The SHA-2 fingerprint of the SubjectPublicKeyInfo of the SSL + certificate as a hexadecimal string, you should not touch it, + except if know what you are doing. - .. note:: the fingerprint was previously stored in SHA-1, and has been - silently upgraded to SHA-2 if the SHA-1 still matched. + .. note:: the fingerprint was previously a fingerprint of the whole + certificate, while it is now only of the SubjectPublicKeyInfo, + which persists across LetsEncrypt renewals, and therefore + reduces the noise generated by the alert dialog. + + .. versionchanged:: 0.12 ciphers diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py index 0e74d02d..a26ead72 100644 --- a/poezio/core/handlers.py +++ b/poezio/core/handlers.py @@ -13,9 +13,12 @@ import ssl import sys import time from datetime import datetime -from hashlib import sha1, sha512 +from hashlib import sha256, sha512 from os import path, makedirs +import pyasn1.codec.der.decoder +import pyasn1.codec.der.encoder +import pyasn1_modules.rfc2459 from slixmpp import InvalidJID from slixmpp.xmlstream.stanzabase import StanzaBase, ElementBase from xml.etree import ElementTree as ET @@ -54,9 +57,9 @@ This can be part of a normal renewal process, but can also mean that \ an attacker is performing a man-in-the-middle attack on your connection. When in doubt, check with your administrator using another channel. -SHA-512 of the old certificate: %s +SHA-256 of the old certificate (SPKI): %s -SHA-512 of the new certificate: %s +SHA-256 of the new certificate (SPKI): %s """ HTTP_VERIF_TEXT = """ @@ -1357,24 +1360,26 @@ class HandlerCore: config.set_and_save('certificate', cert) der = ssl.PEM_cert_to_DER_cert(pem) - sha1_digest = sha1(der).hexdigest().upper() - sha1_found_cert = ':'.join(i + j for i, j in zip(sha1_digest[::2], sha1_digest[1::2])) + asn1 = pyasn1.codec.der.decoder.decode(der, asn1Spec=pyasn1_modules.rfc2459.Certificate())[0] + spki = asn1.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo") + spki_digest = sha256(pyasn1.codec.der.encoder.encode(spki)).hexdigest().upper() + spki_found_cert = ':'.join(i + j for i, j in zip(spki_digest[::2], spki_digest[1::2])) sha2_digest = sha512(der).hexdigest().upper() sha2_found_cert = ':'.join(i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2])) if cert: - if sha1_found_cert == cert: - log.debug('Current hash is SHA-1, moving to SHA-2 (%s)', - sha2_found_cert) - config.set_and_save('certificate', sha2_found_cert) + if sha2_found_cert == cert: + log.debug('Current hash is cert hash, moving to SPKI hash (%s)', + spki_found_cert) + config.set_and_save('certificate', spki_found_cert) return - elif sha2_found_cert == cert: + elif spki_found_cert == cert: return else: - self._ssl_pop_tab(cert, sha2_found_cert) + self._ssl_pop_tab(cert, spki_found_cert) else: - log.debug('First time. Setting certificate to %s', sha2_found_cert) - if not config.silent_set('certificate', sha2_found_cert): + log.debug('First time. Setting certificate to %s', spki_found_cert) + if not config.silent_set('certificate', spki_found_cert): self.core.information('Unable to write in the config file', 'Error') def http_confirm(self, stanza): diff --git a/setup.py b/setup.py index 8aea5a6f..49677d87 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ setup(name="poezio", ('share/poezio/', ['README.rst', 'COPYING', 'CHANGELOG'])] + find_doc('share/doc/poezio/source', 'source') + find_doc('share/doc/poezio/html', 'build/html')), - install_requires=['slixmpp>=1.2.4', 'aiodns'], + install_requires=['slixmpp>=1.2.4', 'aiodns', 'pyasn1', 'pyasn1_modules'], extras_require={'OTR plugin': 'python-potr>=1.0', 'Screen autoaway plugin': 'pyinotify==0.9.4'}) -- cgit v1.2.3