diff options
authormathieui <>2017-10-10 00:52:44 +0200
committermathieui <>2017-10-10 00:52:44 +0200
commitef84a109e8f492c83979443b6366fb8ca4028009 (patch)
parentdcdc970acd64d1c3925a2c8c5690b58e209e001c (diff)
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
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
diff --git a/poezio/core/ b/poezio/core/
index 0e74d02d..a26ead72 100644
--- a/poezio/core/
+++ b/poezio/core/
@@ -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
@@ -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)
- elif sha2_found_cert == cert:
+ elif spki_found_cert == cert:
- self._ssl_pop_tab(cert, sha2_found_cert)
+ self._ssl_pop_tab(cert, spki_found_cert)
- 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/ b/
index 8aea5a6f..49677d87 100755
--- a/
+++ b/
@@ -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'})