Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
|
sk2:openssl_ssl_api [2025/11/17 21:39] jkonczak [Kolejne przykładowe programy] |
sk2:openssl_ssl_api [2025/11/21 17:06] (aktualna) jkonczak [Kolejne przykładowe programy] bugfix |
||
|---|---|---|---|
| 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> | ||