dnspython icon indicating copy to clipboard operation
dnspython copied to clipboard

dns.query.quic() cert validation with custom verify path broken?

Open tykling opened this issue 2 years ago • 2 comments

Describe the bug Using verify=/path/to/ca.pem with dns.query.quic() doesn't seem to reject invalid certificates. Passing a selfsigned cert to verify= does not prevent a lookup from working, as demonstrated below.

Is it me doing something wrong? Thanks!

To Reproduce

(venv) user@privat-dev:~/devel/dns_exporter/src$ pip freeze | grep dnspython
dnspython==2.6.1
(venv) user@privat-dev:~/devel/dns_exporter/src$ python
Python 3.10.13 (main, Nov 15 2023, 13:09:29) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dns.message, dns.name, dns.query
>>> q = dns.message.make_query(dns.name.from_text("example.com"), "A")
>>> dns.query.quic(q, "94.140.14.140", port=853, verify=True)
<DNS message, ID 0>
>>> dns.query.quic(q, "94.140.14.140", port=853, verify="/home/user/devel/dns_exporter/src/tests/certificates/test.crt")
<DNS message, ID 0>
>>> 
(venv) user@privat-dev:~/devel/dns_exporter/src$ 

The file /home/user/devel/dns_exporter/src/tests/certificates/test.crt contains a self-signed certificate.

I haven't got a DoQ server with an invalid certificate handy so I can't test which of these three scenarios is true, but I guess either:

  1. no certificates are validated by dns.query.quic()
  2. or more likely that the custom CA logic is a no-op
  3. or that the custom CA logic doesn't remove system CAs when adding the custom ones

Context (please complete the following information): see above

ps. I cut away a couple of aioquic cryptography deprecation warnings for clarity, they are not relevant to this

Thanks! :)

tykling avatar Feb 26 '24 12:02 tykling

btw 94.140.14.140 is one of adguards doq servers and they do have IP:94.140.14.140 as a SAN so the certificate should (and does) validate with normal system CAs.

tykling avatar Feb 26 '24 12:02 tykling

I can testify that if you give it bad certificates validation fails :).

The answer appears to be number 3 on your list, as aioquic always loads certifi and then also loads anything else. Dnspython will have passed the verify string into cafile in this aioquic TLS code:

    # load CAs                                                                          
    store = crypto.X509Store()
    store.load_locations(certifi.where())
    if cadata is not None:
        for cert in load_pem_x509_certificates(cadata):
            store.add_cert(crypto.X509.from_cryptography(cert))

    if cafile is not None or capath is not None:
        store.load_locations(cafile, capath)

This was a bit surprising to me too as my expectation was like yours that if you specify a CA then it should be the only source. I'll ask Jeremy.

rthalley avatar Feb 26 '24 12:02 rthalley

This is fixed and will be in the next aioquic release.

rthalley avatar Mar 11 '24 15:03 rthalley