przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
sk2
»
openssl_ssl_api
sk2:openssl_ssl_api
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
===== Generowanie kluczy i certyfikatów co ćwiczeń, TLS z powłoki ===== ==== Klucze i certyfikaty ==== Do wygenerowania klucza prywatnego CA i certyfikatu CA oraz klucza prywatnego serwera i podpisanego przez wcześniej wygenerowane CA certyfikatu serwera użyj {{make_simple_certs.sh|tego skryptu}}. ~~Zadanie.#~~ Wygeneruj potrzebne pliki za pomocą powyższego skryptu. Wyświetl informacje o certyfikatach komendą: \\ ''openssl x509 -noout -text -certopt no_sigdump,no_pubkey -in <scieżka do certyfikatu>'' ==== Gotowe narzędzia do tworzenia prostych połączeń TLS w powłoce ==== Podobną rolę jak program ''nc'' / ''netcat'' dla połączeń szyfrowanych spełniają narzędzia ''openssl s_client'' i ''openssl s_server''. \\ Również program ''socat'' pozwala na tworzenie połączeń szyfrowanych. === Klient SSL - openssl s_client / socat === ''**openssl s_client** [-verifyCAfile //ca.crt//] [-crlf] [-quiet|-brief] [-connect] //put.poznan.pl//://443//'' \\ np: ''openssl s_client -brief mail.put.poznan.pl:imaps'' \\ <small> ''-verifyCAfile <CA cert>'' dodaje plik CA jako zaufany do sprawdzania certyfikatu serwera \\ ''-crlf'' zamienia w danych odczytanych ze standardowego wejścia ''\n'' na ''\r\n'' \\ ''-quiet'' wyłącza zupełnie komunikaty diagnostyczne, ''-brief'' skraca komunikaty diagnostyczne; bez tych przełączników są pokazywane długie komunkty \\ ''[-connect] <host>:<port>'' wskazuje gdzie się połączyć; słowo kluczowe ''-connect'' można pominąć jeśli ''<host>:<port>'' jest na końcu komendy </small> ''**socat** [open]ssl://put.poznan.pl//://443//,verify=0[,crlf] stdio'' \\ np. ''socat ssl:mail.put.poznan.pl:imaps,verify=0 stdio'' \\ <small> ''verify=0'' wyłącza sprawdzanie certyfikatu serwera \\ ''crlf'' zamienia w wysyłanych danych ''\n'' na ''\r\n'' </small> ~~Zadanie.#~~ Połącz się z serwerem stron internetowych i otrzymaj od niego odpowiedź. \\ Kto podpisał certyfikat tego serwera? Czy certyfikat został zweryfikowany poprawnie? === Serwer SSL - openssl s_server / socat === Pamiętaj że do stworzenia prostego serwera połączeń szyfrowanych potrzebujesz((Istnieją też rozwiązania SSL/TLS używające anonimowej wymiany DH, ale nie są one wykorzystywane z powodu nieodporności na ataki man-in-the-middle.)) klucza prywatnego i certyfikatu. ''**openssl s_server** [-cert_chain //chain.pem//] -cert //s.crt// -key //s.key// [-quiet|-brief] [-naccept 1] -port //4000//'' \\ <small> ''-key <key>'' podaje ścieżkę do klucza prywatnego \\ ''-cert <cert>'' podaje ścieżkę do certyfikatu serwera \\ ''-cert_chain <chain>'' podaje ścieżkę do łańcucha certyfikatów które podpisały certyfikat serwera \\ ''-naccept <num>'' podaje po ilu połączeniach (obsługiwanych sekwencyjnie) serwer ma się zakończyć </small> ''**socat** openssl-listen://4000//,key=//server.key//,cert=//chain.pem//,reuseaddr,verify=0[,fork] stdio'' \\ <small> ''key=<key>'' podaje ścieżkę do klucza prywatnego \\ ''cert=<chain>'' podaje ścieżkę do (łańcucha) certyfikatów które podpisały certyfikat serwera \\ ''fork'' nakazuje socatowi się sklonować po nawiązaniu połączenia; jeden proces będzie obsługiwał nawiązane połączenie, drugi czekał na nowe; __uwaga:__ przy łączeniu drugiego końca z ''stdio'' wszystkie procesy obsługujące połączenia będą czytać z tego samego deskryptora (dane trafią niedeterministycznie do jednego z nich) </small> ~~Zadanie.#~~ Uruchom prosty serwer TLS używając wygenerowanych przez siebie certyfikatów. \\ Połącz się do niego klientem z konsoli. ~~Zadanie.#~~ Połącz się do prostego serwera TLS z przeglądarki internetowej. \\ Dlaczego przeglądarka uważa połączenie za niebezpieczne? \\ <small>Możesz albo uruchomić serwer (z uprawnieniami administratora) na porcie 443 i w przeglądarce podać ''%%https://<nazwa domenowa>/%%'', lub uruchomić na wybranym porcie i podać ''%%https://<nazwa domenowa>:<numer portu>/%%''</small> <small> ~~Zadanie.#~~ Zainstaluj certyfikat swojego CA w przeglądarce. Następnie połącz się podając jako nazwę domenową ''localhost'' oraz ''lab-net-▒▒''. \\ Czy przeglądarka teraz ufa serwerowi? </small> ===== Intefejs programistyczny biblioteki OpenSSL ===== ==== Wstęp do API ==== Biblioteki OpenSSL są oparte o abstrakcję wejścia/wyjścia [[https://docs.openssl.org/master/man7/bio/|BIO]]. \\ Tworząc kod używający biblioteki OpenSSL do szyfrowanych połączeń sieciowych TLS ([[https://docs.openssl.org/master/man7/ossl-guide-tls-introduction/#important-objects-for-an-openssl-tls-application|libssl]]) programista musi najpierw stworzyć zwykłe połączenie TCP. Może do tego użyć albo abstrakcji BIO ([[https://docs.openssl.org/master/man3/BIO_s_connect/#examples|klient]], [[https://docs.openssl.org/master/man3/BIO_s_accept/#examples|serwer]]), albo zwykłego interfejsu gniazd. \\ <small> Uwaga: tutaj jest opisana ta druga opcja. Często kod przykładowych programów używa do obsługi gniazd TCP abstrakcji OpenSSL – zwracaj na to uwagę. </small> \\ Następnie programista musi na już nawiązanym połączeniu TCP ustanowić połączenie TLS. W tym celu najpierw należy stworzyć "obiekt" SSL, następnie powiązać go z istniejącym połączeniem TCP (dla zwykłego interfejsu gniazd metodą ''[[https://docs.openssl.org/master/man3/SSL_set_fd|SSL_set_fd]]''). \\ Dalszy sposób użycia "obiektu" ''SSL'' do nawiązania bądź przyjęcia połączenia TLS jest opisany w materiałach z wykładów. OpenSSL nie wspiera równoległego wykonywania operacji na tym samym "obiekcie" z wielu wątków (por. [[https://docs.openssl.org/master/man7/openssl-threads/|man openssl-threads]]). \\ W szczególności nie wolno wykonywać równolegle SSL_read i SSL_write na tym samym "obiekcie" SSL (<small>i deweloperzy nie są chętni tego zmieniać, bo wymagałoby to dużych zmian w bibliotece – patrz [[https://github.com/openssl/openssl/issues/20622|ten issue]]</small>). <small>Nie należy też zmieniać ustawień "obiektu" SSL_CTX po jego pierwszym użyciu do stworzenia "obiektu" SSL.</small> ==== Linkowanie programu z biblioteką ==== Do połączenia skompilowanego kodu używającego OpenSSL do połączeń TLS trzeba linkować do bibliotek ''libssl'' oraz ''libcrypto'', np: \\ ''c%%++%% //prog.cpp// -o //prog// **-lssl -lcrypto**'' Przy użyciu CMake należy dodać do pliku ''CMakeLists.txt'' wyszukanie biblioteki OpenSSL oraz linkowanie wybranego celu do niej: <html><div style="margin-top:-1.2em"></div></html> <code cmake> find_package ( OpenSSL 1.1 REQUIRED ) target_link_libraries ( prog OpenSSL::SSL ) </code> <html><div style="margin-top:-1.2em"></div></html> <small>(Bez tego na etapie linkowania zaczną pojawiać się błędy ''undefined reference to…'' z nazwami funkcji bibliotecznych OpenSSL.)</small> ==== Przykładowe programy z wykładów ==== == Klient == <code c kod_z_wykladow__klient.c> #include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> #include <netdb.h> #include <openssl/ssl.h> int main(int argc, char **argv) { SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); SSL *ssl = SSL_new(ctx); struct hostent *he = gethostbyname(argv[1]); struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); memcpy(&sa.sin_addr.s_addr, he->h_addr, he->h_length); int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); connect(fd, (struct sockaddr *)&sa, sizeof(sa)); SSL_set_fd(ssl, fd); SSL_connect(ssl); SSL_write(ssl, "Hello, Server!\n", 15); char buf[1024]; int count = SSL_read(ssl, buf, sizeof(buf)); write(1, buf, count); SSL_shutdown(ssl); close(fd); SSL_free(ssl); SSL_CTX_free(ctx); return 0; } </code> ~~Zadanie.#~~ Skompiluj powyższy kod. Uruchom klienta łącząc się do serwera uruchomionego przy użyciu ''openssl s_server …'' lub ''socat openssl-listen:…''. == Serwer == <code c kod_z_wykladow__serwer.c> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> #include <openssl/ssl.h> int main(int argc, char **argv) { SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX_use_certificate_chain_file(ctx, "chain.pem") == 1 || fprintf(stderr, "problem with chain.pem\n"); //SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) == 1 || fprintf(stderr, "problem with server.crt\n"); SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) == 1 || fprintf(stderr, "problem with server.key\n"); struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_addr.s_addr = INADDR_ANY; sa.sin_port = htons(atoi(argv[1])); int sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); bind(sfd, (struct sockaddr *)&sa, sizeof(sa)) == 0 || fprintf(stderr, "problem with bind\n"); listen(sfd, 10); while (1) { int cfd = accept(sfd, NULL, NULL); SSL *ssl = SSL_new(ctx); SSL_set_fd(ssl, cfd); SSL_accept(ssl); char buf[1024]; SSL_read(ssl, buf, sizeof(buf)); SSL_write(ssl, "Welcome to the SSL/TLS server!\n", 31); SSL_shutdown(ssl); SSL_free(ssl); close(cfd); } } </code> ~~Zadanie.#~~ Skompiluj powyższy kod i uruchom serwer. Upewnij się że wszystkie klucze i certyfikaty zostały poprawnie załadowane i serwer rozpoczął nasłuchiwanie na właściwym porcie. \\ Następnie połącz się klientem uruchomionym z terminala oraz przeglądarką internetową. ==== Kolejne przykładowe programy ==== === Iteracyjny serwer z minimalną obsługą błędów === __Uwaga__: kompiluj poniższy kod ze standardem %%C++20%% lub nowszym (opcja ''--std=c%%++%%20'') <code cpp daytime_tls_server.cpp> #include <cstdio> #include <netinet/in.h> #include <string> #include <sys/socket.h> #include <unistd.h> #include <openssl/ssl.h> void die(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(1); } std::string craftHttpAnswer(); int main(int argc, char **argv) { if (argc != 2) die("Usage: %s <port>\n", argv[0]); // Wybiera konfigurację dla serwera SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); // Ładuje klucz prywatny serwera if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) != 1) die("Loading Private Key from \"server.key\" failed\n"); // Ładuje certyfikaty if (SSL_CTX_use_certificate_chain_file(ctx, "chain.pem") != 1) die("Loading certs from \"chain.pem\" failed\n"); // Tworzy gniazdo i rozpoczyna nasłuchiwanie - zwykły kod z API gniazd int serv = socket(AF_INET, SOCK_STREAM, 0); const int one = 1; setsockopt(serv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); sockaddr_in sa = {}; sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[1])); if (bind(serv, (sockaddr *)&sa, sizeof(sa)) != 0) die("bind failed: %s\n", strerror(errno)); listen(serv, 1); while (1) { // przyjmuje nowe połączenie int cli = accept(serv, 0, 0); // tworzy warstwę szyfrującą i wykonuje handshake SSL *encryption_layer = SSL_new(ctx); SSL_set_fd(encryption_layer, cli); if (SSL_accept(encryption_layer) != 1) { fprintf(stderr, "TLS handshake failed!\n"); continue; } std::string answer = craftHttpAnswer(); // przesyła odpowiedź if (SSL_write(encryption_layer, answer.c_str(), answer.length()) <= 0) { fprintf(stderr, "Problems with write!\n"); continue; } // zamyka warstwę szyfrującą SSL_shutdown(encryption_layer); // zamyka gniazdo shutdown(cli, SHUT_RDWR); close(cli); SSL_free(encryption_layer); } } #include <chrono> #include <format> std::string craftHttpAnswer(void) { std::string content = std::format( "{:%Y.%m.%d %H:%M:%S}\r\n", std::chrono::system_clock::now()); return std::format( "HTTP/2 200\r\n" "content-type: text/plain\r\n" "content-length: {}\r\n" "\r\n" "{}", content.length(), content); } </code> ~~Zadanie.#~~ Powtórz poprzednie zadanie używając kodu tego serwera. === Klient z obsługą błędów === Poniższy kod klienta: * obsługuje błędy, wyświetlając adekwatne komunikaty * weryfikuje poprawność certyfikatu przedstawionego przez serwer * odczytuje naraz dane ze standardowego wejścia i z połączenia TLS używając zdarzeń * obsługuje przypadki brzegowe wartości zwracanych przez funkcje ''SSL_…'' <code cpp tls_client.cpp> #include <cstdio> #include <fcntl.h> #include <netdb.h> #include <poll.h> #include <string> #include <sys/socket.h> #include <unistd.h> #include <openssl/err.h> #include <openssl/ssl.h> void die(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(1); } int main(int argc, char **argv) { if (argc != 3) die("Usage: %s <ip> <port>\n", argv[0]); // tworzy gniazdo i nawiązuje połączenie TCP addrinfo *res; if (getaddrinfo(argv[1], argv[2], 0, &res)) die("getaddrinfo failed\n"); int cli = socket(res->ai_family, SOCK_STREAM, 0); if (connect(cli, res->ai_addr, res->ai_addrlen) != 0) die("connect failed\n"); freeaddrinfo(res); /* ───────────────────────────────────────────────────────────────────── */ // wybiera konfigurację warstwy szyfrującej 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 // żeby przetestować weryfikację certyfikatu bez instalowania certyfikatu CA w systemie: // ustaw zmienną środowiskową MY_ROGUE_CA ma ścieżkę do własnego certyfikatu CA if (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 SSL *encryption_layer = SSL_new(ctx); SSL_set_fd(encryption_layer, cli); // informuje bibliotekę jakiej nazwie powinien odpowiadać certyfikat SSL_set1_host(encryption_layer, argv[1]); // ustanawia komunikację szyfrowaną TLS (na wskazanym połączeniu TCP) wykonując handshake if (SSL_connect(encryption_layer) != 1) die("TLS handshake failed!\n"); // odbiera wyniki weryfikacji poprawności otrzymanego od serwera certyfikatu long serverCertStatus = SSL_get_verify_result(encryption_layer); if (serverCertStatus != X509_V_OK) fprintf(stderr, "[Warning: server certificate verification failed - %s!]\n", X509_verify_cert_error_string(serverCertStatus)); /* ───────────────────────────────────────────────────────────────────── */ // fragment switcha sprawdzającego kody błędów, wspólna cześć dla read i write #define MY_ERROR_HANDLING_SWITCH_MISC \ case SSL_ERROR_ZERO_RETURN: \ fprintf(stderr, "[Server terminated encryption layer]\n"); \ goto close_connection; \ default: \ fprintf(stderr, "[Some fatal error]\n"); \ ERR_print_errors_fp(stderr); \ goto close_connection; pollfd pfds[2]{ {.fd = 0, .events = POLLIN}, {.fd = cli, .events = POLLIN}, }; // 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 int writeNeeds = 0; // uwaga: TLS może potrzebować zrobić odczyt do gniazda żeby wysłać dane // bufor na odbierane dane char recvBuffer[255]; // bufory na wysłane dane std::string dataBeingWritten; std::string dataWaitingToBeWritten; // 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 // ustawia gniazdo w tryb nieblokujący (!) fcntl(cli, F_SETFL, fcntl(cli, F_GETFL) | O_NONBLOCK); // funkcje SSL_read i SSL_write mogą wielokrotnie wywoływać send i recv na // 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 while (1) { poll(pfds, 2, -1); if (pfds[0].revents) { char buf[256]; int c = read(0, buf, 255); if (c > 0) { buf[c] = 0; dataWaitingToBeWritten += buf; writeNeeds |= POLLOUT; // dane będą wysłane dopiero w następnej iteracji pętli zdarzeń (to upraszcza kod) } else { // koniec pliku lub błąd na standardowym wejściu if (dataBeingWritten.empty() && dataWaitingToBeWritten.empty()) { fcntl(cli, F_SETFL, fcntl(cli, F_GETFL) & ~O_NONBLOCK); SSL_shutdown(encryption_layer); // SSL_shutdown zamyka tylko wysyłanie danych; dane można // odbierać aż druga strona nie zrobi SSL_shutdown fcntl(cli, F_SETFL, fcntl(cli, F_GETFL) | O_NONBLOCK); pfds[0].fd = -1; } else usleep(1000); // aktywne czekanie (powtarzana próba odczytu z zamkniętego standardowego // 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 // pozostawione dla uproszczenia kodu } } /* ───────────────────────────────────────────────────────────────────── */ bool isThereAnyDataToWrite = (!dataBeingWritten.empty() || !dataWaitingToBeWritten.empty()); if (pfds[1].revents & writeNeeds && isThereAnyDataToWrite) { writeNeeds = 0; if (dataBeingWritten.empty()) std::swap(dataBeingWritten, dataWaitingToBeWritten); int result = SSL_write(encryption_layer, dataBeingWritten.c_str(), dataBeingWritten.length()); // obsługa niepowodzenia funkcji SSL_write switch (SSL_get_error(encryption_layer, result)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: writeNeeds = POLLIN; break; case SSL_ERROR_WANT_WRITE: writeNeeds = POLLOUT; break; MY_ERROR_HANDLING_SWITCH_MISC } ERR_clear_error(); // obsługa jeśli udało się wysłać dane if (result > 0) { // domyślnie SSL_write kończy się sukcesem dopiero jeśli wysłano // wszystkie dane, nawet jeśli gniazdo jest w trybie nieblokującym dataBeingWritten.clear(); if (!dataWaitingToBeWritten.empty()) writeNeeds = POLLOUT; } } /* ───────────────────────────────────────────────────────────────────── */ if (pfds[1].revents & readNeeds) { readNeeds = POLLIN; int result = SSL_read(encryption_layer, recvBuffer, 255); if (result > 0) { // odczyt się powiódł == SSL odczytał blok danych; rozmiar bloku // może być mniejszy bądź większy niż bufor w SSL_read. write(1, recvBuffer, result); // Jeżeli blok był większy niż bufor, to reszta danych czeka // w buforach warstwy szyfrującej - funkcja SSL_read może zwrócić // kolejną porcję danych bez wywoływania recv na gnieździe while (int remaining = SSL_pending(encryption_layer)) { result = SSL_read(encryption_layer, recvBuffer, remaining < 255 ? remaining : 255); write(1, recvBuffer, result); } } else { // obsługa niepowodzenia funkcji SSL_read switch (SSL_get_error(encryption_layer, result)) { case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: break; case SSL_ERROR_WANT_WRITE: readNeeds = POLLOUT; break; MY_ERROR_HANDLING_SWITCH_MISC } ERR_clear_error(); } } /* ───────────────────────────────────────────────────────────────────── */ pfds[1].events = readNeeds | writeNeeds; } close_connection: shutdown(cli, SHUT_RDWR); close(cli); return 0; } </code> ~~Zadanie.#~~ Przeczytaj kod klienta. Zwróć szczególnie uwagę na komentarze – przybliżają działanie API biblioteki OpenSSL. Przetestuj kod. === [ekstra] przykład użycia BIO w kliencie TLS === <code cpp ssl_client_bio.cpp> #include <cstdio> #include <openssl/ssl.h> // Uwaga - kod dla uproszczenia jest pozbawiony obsługi błędów! int main(int argc, char **argv) { // przygotowuje obiekt SSL ustawia do użycia w połączeniu klienta TLS SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX_set_default_verify_paths(ssl_ctx); SSL *ssl = SSL_new (ssl_ctx); SSL_set_connect_state(ssl); // tworzy obiekt 'Basic Input/Output' typu połączenie sieciowe BIO* tcp_connection = BIO_new_connect(argv[1]); // łączy się do podanego adresu (jako "host:port") BIO_do_connect(tcp_connection); // tworzy obiekt 'Basic Input/Output' typu filtr TLS na istniejącym BIO BIO *tls_connection = BIO_new(BIO_f_ssl()); // wiąże ten obiekt ze wskazanym, wcześniej skonfigurowanym obiektem SSL BIO_set_ssl (tls_connection, ssl, BIO_NOCLOSE); // podkłada pod spód tego filtra obiekt połączenia TCP BIO_push(tls_connection, tcp_connection); // wykonuje połączenie TLS BIO_do_handshake(tls_connection); // sprawdza stan certyfikatu serwera if(long err = SSL_get_verify_result(ssl)) fprintf(stderr, "[cert error: %s]\n", X509_verify_cert_error_string(err)); // wymienia dane po połączeniu TLS BIO_puts(tls_connection, "Hello World!\n"); char buf[1024]; BIO_get_line(tls_connection, buf, 1024); printf("%s", buf); // zamyka połączenie TLS BIO_ssl_shutdown(tls_connection); BIO_free(tls_connection); // połączenie TCP można tutaj dalej używać do przesyłania (nieszyfrowanych) // danych - w BIO_set_ssl podano żeby nie zamykać gniazda TCP (BIO_NOCLOSE) // zamyka połączenie TCP BIO_free(tcp_connection); return 0; } </code> ==== Zadania ==== ~~Zadanie.#~~ Poniżej znajduje się program realizujący prostą sieciową [[https://en.wikipedia.org/wiki/Key%E2%80%93value_database|bazę klucz-wartość]] obsługujący sekwencyjnie klientów. Program przyjmuje od klientów dwa rodzaje żądań: ustawienie klucza (format żądania: ''klucz wartość'') i pobranie poprzednio ustawionej wartości (format żądania: ''klucz''). \\ Przerób program tak, by używał połączeń TLS. <code cpp key-value_database.cpp> #include <cstdio> #include <netdb.h> #include <netinet/in.h> #include <string> #include <sys/socket.h> #include <unistd.h> #include <unordered_map> #include <openssl/ssl.h> std::unordered_map<std::string, std::string> key_value_database; int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <port>\n", argv[0]); return 1; } int serv = socket(AF_INET, SOCK_STREAM, 0); const int one = 1; setsockopt(serv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); sockaddr_in sa = {}; sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[1])); if (bind(serv, (sockaddr *)&sa, sizeof(sa))) { perror("bind failed"); return 1; } listen(serv, 1); while (1) { sockaddr_in ca; socklen_t l = sizeof(ca); int cli = accept(serv, (sockaddr *)&ca, &l); char clientName[NI_MAXHOST], clientPort[NI_MAXSERV]; getnameinfo((sockaddr *)&ca, l, clientName, NI_MAXHOST, clientPort, NI_MAXSERV, 0); printf("New client [%s:%s]\n", clientName, clientPort); while (1) { char request[1025]{}; int count = read(cli, request, 1024); if (count <= 0) break; if (request[count - 1] == '\n') request[count - 1] = '\0'; if (!strlen(request)) continue; char *firstSpace = strchr(request, ' '); if (firstSpace != nullptr) { *firstSpace = '\0'; char *key = request; char *value = firstSpace + 1; key_value_database[key] = value; printf("[%s:%s] assigned to '%s' value '%s'\n", clientName, clientPort, key, value); } else { char *key = request; std::string value; if (key_value_database.count(key)) value = key_value_database[key]; else value = "(null)"; std::string response = std::string("[") + key + "] " + value + "\n"; write(cli, response.data(), response.length()); printf("[%s:%s] read from '%s' value '%s'\n", clientName, clientPort, key, value.data()); } } printf("Client [%s:%s] gone\n", clientName, clientPort); shutdown(cli, SHUT_RDWR); close(cli); } } </code> ~~Zadanie.#~~ Zmień program z poprzedniego zadania tak, żeby program obsługiwał wielu klientów naraz używając zdarzeń. Załóż że po zgłoszeniu odpowiedniego zdarzenia przez select/pool/epoll_wait wszystkie funkcje ''SSL_…'' kończą się bez czekania (sukcesem lub błędem uniemożliwiającym dalszą komunikację). <small> ~~Zadanie.#~~ Zmień program z poprzedniego zadania tak, by działał bez założenia o natychmiastowym zakończeniu funkcji ''SSL_…'' po zgłoszeniu odpowiedniego zdarzenia. Zauważ że w tym zadaniu musisz używać gniazd w trybie nieblokującym i analizować błędy na stosie błędów biblioteki OpensSSL (patrz przykładowy kod klienta wyżej). </small>
sk2/openssl_ssl_api.txt
· ostatnio zmienione: 2024/11/22 10:32 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry