Vlákno názorů k článku SSL je zbytečné, pokud se nekontroluje certifikát od Pavel Janík - Nevím, jestli stejný, nicméně podobný problém jsem již...

  • Článek je starý, nové názory již nelze přidávat.
  • 29. 10. 2012 8:05

    Pavel Janík (neregistrovaný)

    Nevím, jestli stejný, nicméně podobný problém jsem již viděl.

    curl občas při "regulérním" požadavku GET na https server (v našem případě Apache) DOSTAL (nevymyslel si) od serveru HTTP 400 Bad request. V logu bylo informace o tom, že jméno v SNI neodpovídalo hlavičce Host: HTTP požadavku.

    A vysvětlení: curl s nss knihovnou má zřejmě nějaký SSL session pool (cachuje si SSL sessions zřejmě podle cílové IP adresy). No a když komunikuje se dvěma různými jmény, která mají stejnou IP adresu (třeba CNAMEs), tak se může stát, že pro spojení se druhým serverem použije TLSv1 session, která byla původně iniciována pro první server.

    První session byla vytvořena tak, že v TLSv1 požadavku šlo SNI: server1 a v hlavičce HTTP Host: server1. Takovou SSL session si ovšem cachuje i server a uloží si, že pro danou session SNI=server1.

    Druhý požadavek jde na server2, na stejnou IP. curl/nss si z poolu vybere předchozí SSL session. Posílá požadavek na server, SNI je nyní server2 a Host: server2. Apache je v SSL handshaku SNI nepotvrdí (RFC 4366, poslední odstavec 3.1; a tedy SNI je v rámci dané session server1 i nadále) a pokračuje na úrovni HTTP návratovou hodnotou 400, protože SNI je server1 a Host: server2.

    Problém je hezky popsán následujícím demo skriptem (bez curlu).

    rm /tmp/sslsession

    (
    echo GET /neco.html HTTP/1.1
    echo Host: server1
    echo
    echo
    sleep 5
    ) | openssl s_client -no_ticket -host server1 -port 443 -sess_out /tmp/sslsession -servername server1

    openssl sess_id -inform pem -in /tmp/sslsession -text -noout

    (
    echo GET /necojineho.html HTTP/1.1
    echo Host: server2
    echo
    echo
    sleep 5
    ) | openssl s_client -no_ticket -host server2 -port 443 -sess_in /tmp/sslsession -servername server2

    Budu rád, když mi napíšete, jestli se jedná o stejný problém.

    Díky.

    P.S. Děkuji všem kolegům, kteří se na odhalení tohoto problému podíleli. I za hádky zda "regulérní" je opravdu regulérní nebo ne, myslím, že nás to všechny docela obohatilo (mne určitě, už vím co je to SNI 8) ;-)

  • 29. 10. 2012 15:31

    Ondrej Mikle (neregistrovaný)

    U mně se to s curl+NSS děje i když se explicitně zavře connection a curl ji skutečne zavře (na SNI jsem si díval jako na první, ten často způsobuje podobné "bážky"). Ve wiresharku je vidět dva connectiony a v nový TLS handshake má správné SNI. Host headery jsou taky správně.

    Zkoušel jsem i vypnout SSL session cache, nemá to na to vliv (CURLOPT_SSL_SES­SIONID_CACHE).

    Napsal jsem krátký PoC, který to reprodukuje (pythoní zdroják a CACert certifikáty): https://dl.dropbox.com/u/53317596/curl_nss_test.tar.gz

    Podobné je, že oba wiki.vorratsda­tenspeicherun­g.de i www.vorratsdatenspeicherung.de mají stejnou IP (je to uděláno přes CNAME). Zvláštní je, že pokud se vymění NSS za openssl, změní se 'Server' response header u toho HTTP 400. Ostatní servery, které se chovali stejně, vykazovali podobnou změnu v Server header, jenom tam bylo vidět navíc, že je to Varnish.

  • 29. 10. 2012 16:00

    Pavel Janík (neregistrovaný)

    > Ve wiresharku je vidět dva connectiony a v nový TLS handshake má správné SNI. Host headery jsou taky správně.

    Je SNI potvrzeno v ServerHello druhého požadavku (viz můj odkaz na poslední odstavec RFC4366 3.1 v prvním komentáři)? Je v druhém požadavku znovu použita daná SSL session? Můžu vidět PCAP? Mailem jsem napsal více, celý obrázek se mi už pomalu skládá.

  • 29. 10. 2012 16:40

    Ondrej Mikle (neregistrovaný)

    Tak jsem konečně odhalil důvod. Chyba zdá se je na straně klienta i servera.

    Než se uzavře connection, posílá se alert message. V TLS protokolu by se mělo invalidovat session ID. Klient (NSS) by ho neměl posílat a server by ho neměl přijmout (sekce 7.2 u RFC 2246 a 5246 pro TLS 1.0, resp. 1.2).

    Nějaká předřazená cache nebo SSL load balancer vidí stejné TLS session ID, nedívá se na SNI a pravděpodobně připojí vnitřní HTTP tunely na nějaké existující interní cachované spojení. To má nejspíš někde poznamenáno ke kterému virtualhostu patřilo a nelíbí se mu nový Host header.

    Ještě detail: pokud se u curl+NSS vypne validace, nepoužijou se ty session ID u nového spojení a funguje to (když se ty zmiňované SSL_VERIFYHOST a SSL_VERIFYPEER nastaví na 0). Naopak, u zapnuté validace curl+NSS ignoruje nastavení CURLOPT_SSL_SES­SIONID_CACHE a posíla session ID stejně.

  • 29. 10. 2012 16:52

    Pavel Janík (neregistrovaný)

    Ad alert: ssl session invalidace by se měla provést pouze pokud AlertLevel je větší nebo roven fatal. IMO jsou close_notify zasílány normálně jako warning. Jinak by to ani nemohlo fungovat.

    SSL_VERIFYPEER jsem vysvětlil v mailu:

    Už chápu i souvislost s tvrzením, že SSL_VERIFYPEER=0 pomůže nebo také curl bez nss pomůže.

    curl/nss.c totiž obsahuje:

    /* do not use SSL cache if we are not going to verify peer */
    ssl_no_cache = (data->set.ssl.veri­fypeer) ? PR_FALSE : PR_TRUE;
    if(SSL_Option­Set(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
    goto error;

    a tedy pokud je verifikace peera vypnuta, není použita ani ssl cache. Tedy nová SSL session, tedy nemůže dojít k SNI!=Host.

  • 29. 10. 2012 17:01

    Ondrej Mikle (neregistrovaný)

    Takže nakonec je to stejná chyba. Na začátku mě mátlo, že curl ignoruje nastavení SSL session ID.

  • 30. 10. 2012 10:50

    Ondrej Mikle (neregistrovaný)

    Jen doplním, že u TLS 1.2 to bude "naopak" - server bude muset udělat full handshake na novou session a nesmí potvrzovat server_name. Vývojáři z toho budou mít radost, ale dává to větší smysl. Z RFC 6066 konec sekce 3:

    A server that implements this extension MUST NOT accept the request
    to resume the session if the server_name extension contains a
    different name. Instead, it proceeds with a full handshake to
    establish a new session. When resuming a session, the server MUST
    NOT include a server_name extension in the server hello.