Faire du pinning SSL en Python

Consommer une API sur HTTP c'est bien, être sûr que l'on discute avec le bon serveur c'est mieux.

Il n'en reste pas moins que ce modèle de confiance est défaillant. Cela ne met pas en cause le chiffrement lui-même, il reste possible de remplacer la liste des « autorités de confiance » par une liste choisie avec soin, voire un unique certificat.

Pour certains besoins comme les API consommées par les applis mobiles, celà se pratique fréquemment, ça s'appelle le certificate-pining.

On peut se poser la question pour un site web d'utiliser un certificat commercial, signé par une Autorité « de confiance » à des fins de simplicité pour l'utilisateur. Pour des APIs consommées par des développeurs, utiliser une liste de CA de confiance est une perte de sécurité.

En outre, utiliser un certificat auto-signé ou géré par sa propre CA vous donne la maîtrise et la gratuité.

Étonnamment, on trouve très peu d'exemples et de docs sur le pining en python.

Exemples

En python avec urllib2

Par défaut, Python ne vérifie pas du tout le certificat envoyé par le serveur. Il faut donc changer ça pour lui spécifier un fichier contenant le certificat pinné au format X.509/base64 (dit parfois PEM), c'est l'argument ca_certs, et lui dire de refuser une connexion d'un certificat qui n'est pas dans cette liste : cert_reqs (cf doc du module ssl).

import httplib, socket, ssl

class HTTPSPinnedClient(httplib.HTTPSConnection):
    def __init__(self, host, port, ca_certs, **kwargs):
        httplib.HTTPSConnection.__init__(self, host, port, **kwargs)
        self.ca_certs = ca_certs

    def connect(self):
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()
        sock = socket.create_connection((self.host, self.port))
        self.sock = ssl.wrap_socket(sock, ca_certs=self.ca_certs, cert_reqs=ssl.CERT_REQUIRED)


cli = HTTPSPinnedClient('db.ffdn.org', 443, ca_certs='/tmp/ffdn.org')
cli.request('GET', '/api/v1/isp/36/')

On ne peut pas dire que ça soit aisé, mais ça se fait…

En python, avec la bibliothèque restkit

Restkit est une lib python écrite il y a quelques années dédiée à la consommation d'API REST, basée sur la notion de ressource.

Exemple d'utilisation

from restkit.resource import Resource

resource = restkit.Resource('https://db.ffdn.org/api/v1/isp/36/')
data = resource.get()

On peut utiliser du HTTPS sans problème, mais par défaut, python ne valide pas du tout le certificat reçu.

Il faut alors sauvegarder le certificat reçu dans un fichier PEM pour lui indiquer ensuite. On peut le faire par un moyen out of band1 ou

Limites

Révocation ?


  1. Out of band, c'est à dire typiquement recevoir le certificat hors-ligne, main-à-main de la part d'une personne de confiance.