J'ai un groupe de serveurs qui acceptent les certificats clients. Si j'utilise un navigateur Web, le navigateur Web semble recevoir une liste d'autorités de certification valides pour les certificats clients. Le navigateur affiche uniquement les certificats clients signés par l'une de ces autorités de certification.

La commande openssl suivante me donne une liste de certificats CA :

openssl s_client -showcerts -servername myserver.com -connect myserver.com:443 </dev/null

Les lignes qui m'intéressent ressemblent à ceci :

---
Acceptable client certificate CA names
/C=XX/O=XX XXXX
/C=YY/O=Y/OU=YY YYYYYL
...
Client Certificate Types: RSA sign, DSA sign, ECDSA sign

Comment puis-je obtenir les mêmes informations avec python :

J'ai l'extrait de code suivant, qui permet d'obtenir un certificat de serveur, mais ce code ne renvoie pas la liste des CA pour les certificats clients.

import ssl

def get_server_cert(hostname, port):
    conn = ssl.create_connection((hostname, port))
    context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    sock = context.wrap_socket(conn, server_hostname=hostname)
    cert = sock.getpeercert(True)
    cert = ssl.DER_cert_to_PEM_cert(cert)
    return cerft

Je m'attendais à trouver un équivalent fonctionnel de getpeercert(), quelque chose comme getpeercas() mais je n'ai rien trouvé.

Solution actuelle :

import os
import subprocess


def get_client_cert_cas(hostname, port):
    """
    returns a list of CAs, for which client certs are accepted
    """

    cmd = [
        "openssl",
        "s_client",
        "-showcerts",
        "-servername",  hostname,
        "-connect",  hostname + ":" + str(port),
        ]

    stdin = open(os.devnull, "r")
    stderr = open(os.devnull, "w")

    output = subprocess.check_output(cmd, stdin=stdin, stderr=stderr)
    ca_signatures = []
    state = 0
    for line in output.decode().split("\n"):
        print(state, line)
        if state == 0:
            if line == "Acceptable client certificate CA names":
                state = 1
        elif state == 1:
            if line.startswith("Client Certificate Types:"):
                break
            ca_signatures.append(line)
    return ca_signatures

Mise à jour : solution potentielle avec pyopenssl (que je n'ai jamais utilisé)

@Steffen Ulrich a suggéré d'utiliser pyopenssl, qui a une méthode get_client_ca_list() et cela m'a aidé à écrire un petit extrait de code.

Le code ci-dessous semble fonctionner. Je ne sais pas si cela peut être amélioré ou s'il y a des chutes de fosse.

Si personne ne répond dans les prochains jours, je posterai ceci comme réponse potentielle.

import socket
from OpenSSL import SSL

def get_client_cert_cas(hostname, port):
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    # next line for SNI
    sock.set_tlsext_host_name(hostname.encode("utf-8"))
    sock.connect((hostname, port))
    sock.send(b"G")  # must send at least one byte
    return sock.get_client_ca_list())
0
gelonida 2 nov. 2020 à 14:40

1 réponse

Meilleure réponse

Avec l'aide de @Steffen Ullrich, j'ai trouvé la solution suivante.

Il nécessite d'installer un package externe

pip install pyopenssl

Ensuite, les travaux suivants fonctionneront :

import socket
from OpenSSL import SSL

def get_client_cert_cas(hostname, port):
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    # next line for SNI
    sock.set_tlsext_host_name(hostname.encode("utf-8"))
    sock.connect((hostname, port))
    sock.send(b"G")  # must send at least one byte
    return sock.get_client_ca_list())

La ligne sock.send(b"G") est requise pour déclencher le protocole SSL et pour renseigner les informations client_ca_list.

Je ne suis pas un expert pyopenssl, mais je pourrais imaginer qu'il existe un moyen plus élégant / plus explicite d'obtenir le même comportement.

1
gelonida 6 janv. 2021 à 22:42