Narzędzia użytkownika

Narzędzia witryny


sk2:openssl_ssl_api

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Both sides previous revision Poprzednia wersja
Nowa wersja
Poprzednia wersja
sk2:openssl_ssl_api [2024/11/22 09:46]
jkonczak [Kolejne przykładowe programy]
sk2:openssl_ssl_api [2025/11/21 17:06] (aktualna)
jkonczak [Kolejne przykładowe programy] bugfix
Linia 32: Linia 32:
 ''​-quiet''​ wyłącza zupełnie komunikaty diagnostyczne,​ ''​-quiet''​ wyłącza zupełnie komunikaty diagnostyczne,​
 ''​-brief''​ skraca komunikaty diagnostyczne;​ bez tych przełączników są pokazywane ''​-brief''​ skraca komunikaty diagnostyczne;​ bez tych przełączników są pokazywane
-długie ​komunkty+długie ​komunkaty
 \\ \\
 ''​[-connect] <​host>:<​port>''​ wskazuje gdzie się połączyć;​ słowo kluczowe ''​[-connect] <​host>:<​port>''​ wskazuje gdzie się połączyć;​ słowo kluczowe
Linia 147: Linia 147:
 trzeba linkować do bibliotek ''​libssl''​ oraz ''​libcrypto'',​ np: trzeba linkować do bibliotek ''​libssl''​ oraz ''​libcrypto'',​ np:
 \\ \\
-''​c++ //​prog.cpp//​ -o //prog// **-lssl -lcrypto**''​+''​c%%++%% //​prog.cpp//​ -o //prog// **-lssl -lcrypto**''​
  
 Przy użyciu CMake należy dodać do pliku ''​CMakeLists.txt''​ wyszukanie biblioteki Przy użyciu CMake należy dodać do pliku ''​CMakeLists.txt''​ wyszukanie biblioteki
Linia 353: Linia 353:
         std::​chrono::​system_clock::​now());​         std::​chrono::​system_clock::​now());​
     return std::​format(     return std::​format(
-        "HTTP/200\r\n"​+        "HTTP/1.1 200 Ok\r\n"
         "​content-type:​ text/​plain\r\n"​         "​content-type:​ text/​plain\r\n"​
         "​content-length:​ {}\r\n"​         "​content-length:​ {}\r\n"​
Linia 378: Linia 378:
 #include <​poll.h>​ #include <​poll.h>​
 #include <​string>​ #include <​string>​
 +#include <​string_view>​
 #include <​sys/​socket.h>​ #include <​sys/​socket.h>​
 #include <​unistd.h>​ #include <​unistd.h>​
 +    ​
 #include <​openssl/​err.h>​ #include <​openssl/​err.h>​
 #include <​openssl/​ssl.h>​ #include <​openssl/​ssl.h>​
 +    ​
 void die(const char *format, ...) { void die(const char *format, ...) {
     va_list args;     va_list args;
Linia 391: Linia 392:
     exit(1);     exit(1);
 } }
 +    ​
 int main(int argc, char **argv) { int main(int argc, char **argv) {
     if (argc != 3)     if (argc != 3)
         die("​Usage:​ %s <ip> <​port>​\n",​ argv[0]);         die("​Usage:​ %s <ip> <​port>​\n",​ argv[0]);
 +    ​
     // tworzy gniazdo i nawiązuje połączenie TCP     // tworzy gniazdo i nawiązuje połączenie TCP
     addrinfo *res;     addrinfo *res;
Linia 404: Linia 405:
         die("​connect failed\n"​);​         die("​connect failed\n"​);​
     freeaddrinfo(res);​     freeaddrinfo(res);​
 +    ​
     /* ───────────────────────────────────────────────────────────────────── */     /* ───────────────────────────────────────────────────────────────────── */
 +    ​
     // wybiera konfigurację warstwy szyfrującej     // wybiera konfigurację warstwy szyfrującej
     SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());​ // podstawowe ustawienia dla klienta TLS     SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());​ // podstawowe ustawienia dla klienta TLS
     SSL_CTX_set_default_verify_paths(ctx);​ // nakazuje użyć systemowych certyfikatów CA     SSL_CTX_set_default_verify_paths(ctx);​ // nakazuje użyć systemowych certyfikatów CA
 +    ​
     // żeby przetestować weryfikację certyfikatu bez instalowania certyfikatu CA w systemie:     // żeby przetestować weryfikację certyfikatu bez instalowania certyfikatu CA w systemie:
     // ustaw zmienną środowiskową MY_ROGUE_CA ma ścieżkę do własnego certyfikatu CA     // ustaw zmienną środowiskową MY_ROGUE_CA ma ścieżkę do własnego certyfikatu CA
     if (getenv("​MY_ROGUE_CA"​))     if (getenv("​MY_ROGUE_CA"​))
         SSL_CTX_load_verify_file(ctx,​ getenv("​MY_ROGUE_CA"​));​         SSL_CTX_load_verify_file(ctx,​ getenv("​MY_ROGUE_CA"​));​
 +    ​
     /* ───────────────────────────────────────────────────────────────────── */     /* ───────────────────────────────────────────────────────────────────── */
 +    ​
     // tworzy obiekt reprezentujący warstwę szyfrującą i nakazuje mu czytać/​pisać do pliku gniazda     // tworzy obiekt reprezentujący warstwę szyfrującą i nakazuje mu czytać/​pisać do pliku gniazda
     SSL *encryption_layer = SSL_new(ctx);​     SSL *encryption_layer = SSL_new(ctx);​
     SSL_set_fd(encryption_layer,​ cli);     SSL_set_fd(encryption_layer,​ cli);
 +    ​
     // informuje bibliotekę jakiej nazwie powinien odpowiadać certyfikat     // informuje bibliotekę jakiej nazwie powinien odpowiadać certyfikat
     SSL_set1_host(encryption_layer,​ argv[1]);     SSL_set1_host(encryption_layer,​ argv[1]);
 +    ​
     // ustanawia komunikację szyfrowaną TLS (na wskazanym połączeniu TCP) wykonując handshake     // ustanawia komunikację szyfrowaną TLS (na wskazanym połączeniu TCP) wykonując handshake
     if (SSL_connect(encryption_layer) != 1)     if (SSL_connect(encryption_layer) != 1)
         die("​TLS handshake failed!\n"​);​         die("​TLS handshake failed!\n"​);​
 +    ​
     // odbiera wyniki weryfikacji poprawności otrzymanego od serwera certyfikatu     // odbiera wyniki weryfikacji poprawności otrzymanego od serwera certyfikatu
     long serverCertStatus = SSL_get_verify_result(encryption_layer);​     long serverCertStatus = SSL_get_verify_result(encryption_layer);​
Linia 434: Linia 435:
         fprintf(stderr,​ "​[Warning:​ server certificate verification failed - %s!]\n",​         fprintf(stderr,​ "​[Warning:​ server certificate verification failed - %s!]\n",​
                 X509_verify_cert_error_string(serverCertStatus));​                 X509_verify_cert_error_string(serverCertStatus));​
 +    ​
     /* ───────────────────────────────────────────────────────────────────── */     /* ───────────────────────────────────────────────────────────────────── */
     ​     ​
Linia 446: Linia 447:
         ERR_print_errors_fp(stderr); ​                              \         ERR_print_errors_fp(stderr); ​                              \
         goto close_connection;​         goto close_connection;​
 +    ​
     pollfd pfds[2]{     pollfd pfds[2]{
         {.fd = 0,   ​.events = POLLIN},         {.fd = 0,   ​.events = POLLIN},
         {.fd = cli, .events = POLLIN},         {.fd = cli, .events = POLLIN},
     };     };
 +    ​
     // zdarzenia które są potrzebne do odczytania i zapisu danych przez warstwę szyfrującą     // zdarzenia które są potrzebne do odczytania i zapisu danych przez warstwę szyfrującą
-    int readNeeds = POLLIN; // uwaga: TLS może potrzebować zrobić zapis do gniazda żeby odebrać dane +    ​short int readNeeds = POLLIN; // uwaga: TLS może potrzebować zrobić zapis do gniazda żeby odebrać dane 
-    int writeNeeds = 0; // uwaga: TLS może potrzebować zrobić odczyt do gniazda żeby wysłać dane +    ​short int writeNeeds = 0;     ​// uwaga: TLS może potrzebować zrobić odczyt do gniazda żeby wysłać dane 
 +    short int mask; 
 +    ​
     // bufor na odbierane dane     // bufor na odbierane dane
     char recvBuffer[255];​     char recvBuffer[255];​
Linia 463: Linia 465:
     // uwaga: jeśli nieblokujące SSL_write się nie powiodło bo musiałoby czekać,     // uwaga: jeśli nieblokujące SSL_write się nie powiodło bo musiałoby czekać,
     //        trzeba je powtórzyć z identycznymi argumentami;​ stąd dwa bufory     //        trzeba je powtórzyć z identycznymi argumentami;​ stąd dwa bufory
 +    ​
     // ustawia gniazdo w tryb nieblokujący (!)     // ustawia gniazdo w tryb nieblokujący (!)
     fcntl(cli, F_SETFL, fcntl(cli, F_GETFL) | O_NONBLOCK);​     fcntl(cli, F_SETFL, fcntl(cli, F_GETFL) | O_NONBLOCK);​
Linia 469: Linia 471:
     // gnieździe, co przy blokującym trybie i użyciu zdarzeń mogłoby zablokować     // gnieździe, co przy blokującym trybie i użyciu zdarzeń mogłoby zablokować
     // program na obsłudze któregoś zdarzenia; stąd tryb nieblokujący     // program na obsłudze któregoś zdarzenia; stąd tryb nieblokujący
 +    ​
     while (1) {     while (1) {
         poll(pfds, 2, -1);         poll(pfds, 2, -1);
 +    ​
         if (pfds[0].revents) {         if (pfds[0].revents) {
-            char buf[256];+            char buf[255];
             int c = read(0, buf, 255);             int c = read(0, buf, 255);
             if (c > 0) {             if (c > 0) {
-                ​buf[c] = 0; +                dataWaitingToBeWritten += std::​string_view(buf, c);
-                ​dataWaitingToBeWritten += buf;+
                 writeNeeds |= POLLOUT;                 writeNeeds |= POLLOUT;
                 // dane będą wysłane dopiero w następnej iteracji pętli zdarzeń (to upraszcza kod)                 // dane będą wysłane dopiero w następnej iteracji pętli zdarzeń (to upraszcza kod)
Linia 491: Linia 492:
                     pfds[0].fd = -1;                     pfds[0].fd = -1;
                 } else                  } else 
-                    usleep(1000);+                    usleep(10000);
                 // aktywne czekanie (powtarzana próba odczytu z zamkniętego standardowego                 // aktywne czekanie (powtarzana próba odczytu z zamkniętego standardowego
                 // wejścia) na wepchnięcie do bufora nadawczego TCP zaszyfrowanych danych                 // wejścia) na wepchnięcie do bufora nadawczego TCP zaszyfrowanych danych
-                // przed włożeniem tam wiadomości zamknięciem warstwy szyfrującej jest tutaj+                // przed włożeniem tam wiadomości zamknięcia warstwy szyfrującej jest tutaj
                 // pozostawione dla uproszczenia kodu                 // pozostawione dla uproszczenia kodu
             }             }
         }         }
 +    ​
         /* ───────────────────────────────────────────────────────────────────── */         /* ───────────────────────────────────────────────────────────────────── */
- +        ​ 
-        ​bool isThereAnyDataToWrite ​(!dataBeingWritten.empty() || !dataWaitingToBeWritten.empty())+        ​mask 0
-        if (pfds[1].revents & writeNeeds ​&& isThereAnyDataToWrite) {+        if (pfds[1].revents & writeNeeds) {
             writeNeeds = 0;             writeNeeds = 0;
 +    ​
             if (dataBeingWritten.empty())             if (dataBeingWritten.empty())
                 std::​swap(dataBeingWritten,​ dataWaitingToBeWritten);​                 std::​swap(dataBeingWritten,​ dataWaitingToBeWritten);​
 +    ​
             int result = SSL_write(encryption_layer,​ dataBeingWritten.c_str(),​ dataBeingWritten.length());​             int result = SSL_write(encryption_layer,​ dataBeingWritten.c_str(),​ dataBeingWritten.length());​
 +    ​
             // obsługa niepowodzenia funkcji SSL_write             // obsługa niepowodzenia funkcji SSL_write
             switch (SSL_get_error(encryption_layer,​ result)) {             switch (SSL_get_error(encryption_layer,​ result)) {
Linia 519: Linia 520:
                 case SSL_ERROR_WANT_WRITE:​                 case SSL_ERROR_WANT_WRITE:​
                     writeNeeds = POLLOUT;                     writeNeeds = POLLOUT;
 +                    // jeżeli SSL_write zwrócił WANT_WRITE, nie można wywołać
 +                    // innych I/O przed kolejnym SSL_write
 +                    pfds[1].revents = 0;
 +                    mask = POLLIN;
                     break;                     break;
                 MY_ERROR_HANDLING_SWITCH_MISC                 MY_ERROR_HANDLING_SWITCH_MISC
             }             }
             ERR_clear_error();​             ERR_clear_error();​
 +    ​
             // obsługa jeśli udało się wysłać dane             // obsługa jeśli udało się wysłać dane
             if (result > 0) {             if (result > 0) {
Linia 532: Linia 537:
                     writeNeeds = POLLOUT;                     writeNeeds = POLLOUT;
             }             }
 +            ​
 +            // przypadek brzegowy: jeżeli SSL_read chciał zapisu ale równocześnie
 +            // pojawiły się dane do wysłania i SSL_write wysłał te dane, to SSL_read
 +            // może już nie potrzebować zapisu, tylko odczyt
 +            if (readNeeds == POLLOUT)
 +                readNeeds &= POLLIN;
         }         }
 +    ​
         /* ───────────────────────────────────────────────────────────────────── */         /* ───────────────────────────────────────────────────────────────────── */
 +    ​
         if (pfds[1].revents & readNeeds) {         if (pfds[1].revents & readNeeds) {
             readNeeds = POLLIN;             readNeeds = POLLIN;
 +    ​
             int result = SSL_read(encryption_layer,​ recvBuffer, 255);             int result = SSL_read(encryption_layer,​ recvBuffer, 255);
             if (result > 0) {             if (result > 0) {
Linia 565: Linia 576:
             }             }
         }         }
 +    ​
         /* ───────────────────────────────────────────────────────────────────── */         /* ───────────────────────────────────────────────────────────────────── */
 +    ​
         pfds[1].events = readNeeds | writeNeeds;         pfds[1].events = readNeeds | writeNeeds;
 +        pfds[1].events &= ~mask;
     }     }
 +    ​
 close_connection:​ close_connection:​
     shutdown(cli,​ SHUT_RDWR);     shutdown(cli,​ SHUT_RDWR);
Linia 742: Linia 754:
 </​small>​ </​small>​
  
 +==== [ekstra] SSLKEYLOGFILE ====
 +
 +Biblioteki do szyfrowanych połączeń potrafią przekazać
 +programiście klucze użyte do szyfrowania danych w
 +[[https://​tlswg.org/​sslkeylogfile/​draft-ietf-tls-keylogfile.html|(już de facto) standardowym formacie]].
 +Część aplikacji/​bibliotek odczytuje zmienną środowiskową ''​SSLKEYLOGFILE''​
 +i tworzy plik pod wskazaną tam nazwą, do którego wpisuje klucze wszystkich
 +szyfrowanych połączeń. Pozwala to np. 
 +[[https://​wiki.wireshark.org/​TLS#​tls-decryption|odszyfrować komunikację w programie wireshark]].
 +
 +Żeby zaimplementować takie zapisywanie kluczy do pliku w OpenSSL, należy
 +zarejestrować funkcję która będzie wywoływana ilekroć OpenSSL wygenruje klucz
 +do szyfrowania,​ i która dostanie gotową linię do wpisania do pliku.
 +\\
 +Przykładowy prosty kod realizujący takie zachowanie jest zaprezentowany poniżej:
 +<​html><​div style=margin-top:​-1.4em></​div></​html>​
 +<code cpp>
 +
 +int keylogFd;
 +
 +int main() {
 +    …
 +    SSL_CTX *ctx = …;
 +    …
 +    if (getenv("​SSLKEYLOGFILE"​)) {
 +        keylogFd = open(getenv("​SSLKEYLOGFILE"​),​ O_CREAT | O_WRONLY | O_APPEND, 0666);
 +        if (keylogFd == -1)
 +            die("​SSLKEYLOGFILE file opening failed with: %s", strerror(errno));​
 +        SSL_CTX_set_keylog_callback(ctx,​ [](const SSL *ssl, const char *line) {
 +            write(keylogFd,​ line, strlen(line));​
 +            write(keylogFd,​ "​\n",​ 1);
 +        });
 +    }
 +    …
 +</​code>​
sk2/openssl_ssl_api.1732265201.txt.gz · ostatnio zmienione: 2024/11/22 09:46 przez jkonczak