środa, 22 kwietnia 2026

STM32H723 - HTTPS

W tym poście opiszę sposób uruchomienia HTTPS z wykorzystaniem mbedtls, lwip oraz freertos na układzie STM32H7.

Projekt polega na wykonaniu małego serwera HTTPS działającego na bibliotece mbedTLS. Serwer ma za zadanie obsługiwanie statycznej strony WWW, logowanie użytkownika zapisu i odczytu konfiguracji urządzenia. W projekcie nie korzystałem z lwip https z warstwą TLS. Implementacja polega na przygotowaniu serwera opartego bezpośrednio o sokety LwIP oraz mbedTLS. 

Przepływ danych jest następujący:

  • Uruchomienie zadania HTTPS przez FreeRTOS. 
  • Oczekiwanie na gotowość stosu LwIP.
  • Inicjalizacja mbedTLS, generatora liczb losowych, certyfikatu oraz klucza prywatnego. 
  • Nasłuchiwanie na porcie HTTPS. 
  • Po akceptacji połączenia wykonywany jest handshake dla TLS 1.2.
  • Odczyt żądania HTTP z kanału TLS.
  • Rozpoznanie metody, ścieżki i nagłówków przez parser. 
  • Wywołanie odpowiedniej funkcji API w celu zwrócenia odpowiedniej informacji.
  • W przypadku zapisu danych aktualizowane są struktury robocze oraz ustawiane są odpowiednie flagi informujące system o konieczności zapisania danych do pamięci trwałej. 

Dla TLS 1.2. w mbedTLS należy uruchomić następujące flagi:

  • MBEDTLS_SSL_PROTO_TLS1_2
  • MBEDTLS_CTR_DRBG_C
  • MBEDTLS_ENTROPY_C
  • MBEDTLS_SSL_SRV_C
  • MBEDTLS_X509_USE_C
  • MBEDTLS_X509_CRT_PARSE_C
Dodatkowo lista obsługiwanych szyfrów została ograniczona do:

  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
  • MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

Teraz inicjalizacja TLS. Za to odpowiada funkcja tls_init():

  1. static int tls_init(void)
  2. {
  3.     mbedtls_net_init(&listen_fd); //Inicjalizacja dla gniazda nasłuchującego
  4.  
  5.     mbedtls_ssl_config_init(&conf); //Init struktury konfiguracji TLS
  6.     mbedtls_entropy_init(&entropy);
  7.     mbedtls_ctr_drbg_init(&ctr_drbg);
  8.     mbedtls_x509_crt_init(&server_cert); //Init struktury
  9.     mbedtls_pk_init(&server_key);
  10.  
  11.     return mbedtls_ctr_drbg_seed(&ctr_drbg,
  12.                                  mbedtls_entropy_func,
  13.                                  &entropy,
  14.                                  (const unsigned char*)pers,
  15.                                  strlen(pers));
  16. }

Następne w kolejności jest ładowanie certyfikatów:

  1. static int tls_load_cert(void)
  2. {
  3.     int ret;
  4.  
  5.     ret = mbedtls_x509_crt_parse(&server_cert,
  6.                                  (const unsigned char*)mbedtls_server_certificate_https,
  7.                                  strlen(mbedtls_server_certificate_https) + 1);
  8.     if (ret != 0) {
  9.         return ret;
  10.     }
  11.  
  12.     ret = mbedtls_pk_parse_key(&server_key,
  13.         (const unsigned char*)mbedtls_certificate_server_key_pass_https,
  14.         strlen(mbedtls_certificate_server_key_pass_https) + 1,
  15.         NULL, 0);
  16.  
  17.     if (ret != 0) {
  18.         return ret;
  19.     }
  20.  
  21.     return 0;
  22. }

Konfiguracja TLS:

  1. static const int https_ciphersuites[] = {
  2.     MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  3.     MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
  4.     MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
  5.     MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
  6.     0
  7. };
  8.  
  9. static int tls_configure(void)
  10. {
  11.     //domyślna konfiguracja
  12.     int ret = mbedtls_ssl_config_defaults(&conf,
  13.                                          MBEDTLS_SSL_IS_SERVER,
  14.                                          MBEDTLS_SSL_TRANSPORT_STREAM,
  15.                                          MBEDTLS_SSL_PRESET_DEFAULT);
  16.     if (ret != 0) {
  17.         return ret;
  18.     }
  19.    
  20.     //Ustawienie generatora liczb losowych
  21.     mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
  22.     //Wylaczenie weryfikacji klienta
  23.     //Można ustawić:
  24.     //MBEDTLS_SSL_VERIFY_NONE              
  25.     //MBEDTLS_SSL_VERIFY_OPTIONAL
  26.     //MBEDTLS_SSL_VERIFY_REQUIRED
  27.     //MBEDTLS_SSL_VERIFY_UNSET
  28.     mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
  29.     //Ustawienie listy szyfrów
  30.     mbedtls_ssl_conf_ciphersuites(&conf, https_ciphersuites);
  31.  
  32.     //Protokół TLS 1.2
  33.     mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
  34.     mbedtls_ssl_conf_max_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
  35.  
  36.     //Podłączenie certyfikatu
  37.     ret = mbedtls_ssl_conf_own_cert(&conf, &server_cert, &server_key);
  38.     return ret;
  39. }

Teraz nasłuchiwanie na wybranym porcie:

  1. static int https_listen(void)
  2. {
  3.     char port_str[6];
  4.     uint16_t port = Get_Port_Http_From_Settings();
  5.    
  6.     //Zabezpieczenie gdy port niepoprawny
  7.     if (port == 0 || port > 65535) {
  8.         port = 443;
  9.     }
  10.     //Konwersja portu na string
  11.     (void)snprintf(port_str, sizeof(port_str), "%u", (unsigned)port);
  12.    
  13.     //Rozpoczęcie nasłuchiwania
  14.     return mbedtls_net_bind(&listen_fd, NULL, port_str, MBEDTLS_NET_PROTO_TCP);
  15. }

Za akceptowanie połączenia TLS odpowiada funkcja https_accept_and_handshake:

  1. static int https_accept_and_handshake(mbedtls_net_context *pclient_fd, mbedtls_ssl_context *pssl)
  2. {
  3.     int ret;
  4.     uint32_t t0 = xTaskGetTickCount();
  5.  
  6.     mbedtls_net_init(pclient_fd);
  7.    
  8.     //Akceptacja nowego połączenia TCP
  9.     ret = mbedtls_net_accept(&listen_fd, pclient_fd, NULL, 0, NULL);
  10.     if (ret != 0) {
  11.         mbedtls_net_free(pclient_fd);
  12.         return ret;
  13.     }
  14.  
  15.     mbedtls_ssl_init(pssl);
  16.     ret = mbedtls_ssl_setup(pssl, &conf);
  17.     if (ret != 0) {
  18.         mbedtls_ssl_free(pssl);
  19.         mbedtls_net_free(pclient_fd);
  20.         return ret;
  21.     }
  22.    
  23.     //Podłączenie TCP do TLS
  24.     mbedtls_ssl_set_bio(pssl, pclient_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
  25.    
  26.     //Handshake wykonuje się w pętli, z dodatkowym timeoutem
  27.     for (;;) {
  28.            ret = mbedtls_ssl_handshake(pssl);
  29.            if (ret == 0) return 0;
  30.  
  31.            if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
  32.                if ((xTaskGetTickCount() - t0) > pdMS_TO_TICKS(5000)) {
  33.                    ret = MBEDTLS_ERR_SSL_TIMEOUT;
  34.                    break;
  35.                }
  36.                vTaskDelay(pdMS_TO_TICKS(1));
  37.                continue;
  38.            }
  39.            break;
  40.        }
  41.  
  42.        mbedtls_ssl_free(pssl);
  43.        mbedtls_net_free(pclient_fd);
  44.        return ret;
  45.  }

Strony, obrazy oraz css przechowywane są jako tablice w pamięci. 

  1. const uint8_t index_html[] =
  2. "<!doctype html><html><head><meta charset=\"utf-8\">"
  3. "<title>Panel</title><link rel=\"stylesheet\" href=\"/style.css\"></head>"
  4. "<body><h1>HTTPS PANEL</h1>"
  5. "<p><a href=\"/cgilogin.html\">Login</a></p>"
  6. "</body></html>";

Wielkość tablicy odczytywana jest w następujący sposób:

  1. const unsigned int logo_png_len = sizeof(logo_png);
  2. const size_t index_html_len = sizeof(index_html)-1;

Dostęp do zmiennych realizowany jest przez strukturę https_file_t:

  1. typedef struct {
  2.     const char  *path;
  3.     const char  *mime; //Rodzaj danych np. "text/html; charset=utf-8", "text/css",
  4.     const uint8_t *data;
  5.     size_t len;
  6.     uint8_t auth_required; 
  7. } https_file_t;
  8.  
  9. static void init_https_files(void)
  10. {
  11.     https_files[0] = (https_file_t){ "/index.html",      "text/html; charset=utf-8", index_html,      index_html_len,      0 };
  12.     //...
  13.     //...
  14. }

W celu ograniczenia zużycia pamięci zdefiniowana jest jedna globalna sesja połączenia. Takie podejście nie komplikuje architektury całego serwera ponieważ serwer nie tworzy osobnych zadań dla każdego połączenia. W programie jest jeden bufor HTTPS_RX_BUFFER, który przechowuje żądania od HTTP. 

  1. #define HTTPS_RX_BUFFER 3072
  2. static uint8_t rx_buf[HTTPS_RX_BUFFER];
  3.  
  4. static uint32_t g_session_sid = 0;

Zapytania są przekazywane do prostej funkcji obsługującej żądania GET oraz POST.

  1. static const https_file_t *file_find(const char *path)
  2. {
  3.     for (size_t i = 0; i < (sizeof(https_files)/sizeof(https_files[0])); i++) {
  4.         if (strcmp(path, https_files[i].path) == 0) return &https_files[i];
  5.     }
  6.     return NULL;
  7. }
  8.  
  9. static void route_get(mbedtls_ssl_context *pssl, const char *req, const char *path)
  10. {
  11.     if (strcmp(path, "/") == 0 || strcmp(path, "/index.html") == 0) {
  12.         if (is_authorized(req)) {
  13.             http_send_redirect(pssl, "/cgidata.html", NULL);
  14.         } else {
  15.             http_send_redirect(pssl, "/cgilogin.html", NULL);
  16.         }
  17.         return;
  18.     }
  19.  
  20.     if (strcmp(path, "/login.cgi") == 0) {
  21.         http_send_redirect(pssl, "/cgilogin.html", NULL);
  22.         return;
  23.     }
  24.  
  25.     if (strcmp(path, "/logout.cgi") == 0) {
  26.         g_session_sid = 0;
  27.         http_send_redirect(pssl, "/cgilogin.html",
  28.             "SID=0; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax");
  29.         return;
  30.     }
  31.  
  32.     if (strcmp(path, "/api/get") == 0)    { handle_get_api_get(pssl, req);    return; }
  33.     //..
  34.     //..
  35.  
  36.     const https_file_t *f = file_find(path);
  37.     if (f) {
  38.         if (f->auth_required && !is_authorized(req)) {
  39.             http_send_redirect(pssl, "/cgilogin.html", NULL);
  40.             return;
  41.         }
  42.         http_send_file_200(pssl, f);
  43.         return;
  44.     }
  45.  
  46.     http_send_404(pssl);
  47. }
  48.  
  49. static void route_post(mbedtls_ssl_context *pssl,
  50.                        const char *req,
  51.                        const char *path,
  52.                        const char *body,
  53.                        int cl)
  54. {
  55.     if (!body || cl < 0) {
  56.         http_send_404(pssl);
  57.         return;
  58.     }
  59.  
  60.     if (strcmp(path, "/login.cgi") == 0) {
  61.         handle_post_login(pssl, req, body, cl);
  62.         return;
  63.     }
  64.    
  65.     //...
  66.     //...
  67.  
  68.     http_send_404(pssl);
  69. }

Obsługa logowania:

  1. static void handle_post_login(mbedtls_ssl_context *pssl, const char *req, const char *body, int cl)
  2. {
  3.     (void)req;
  4.  
  5.     char user[32] = {0};
  6.     char pass[32] = {0};
  7.  
  8.     //Odczytanie danych pname o ppass
  9.     if (form_get_value(body, cl, "pname", user, sizeof(user)) != 0 ||
  10.         form_get_value(body, cl, "ppass", pass, sizeof(pass)) != 0)
  11.     {
  12.         http_send_redirect(pssl, "/loginfail.html", NULL);
  13.         return;
  14.     }
  15.  
  16.     //dedkowanie znaków
  17.     url_decode_inplace(user);
  18.     url_decode_inplace(pass);
  19.    
  20.     //Sprawdzenie poprawności loginu i hasła
  21.     if (cgi_login_handler(user, pass)) {
  22.         //generowanie SID
  23.         g_session_sid = weak_rand32();
  24.        
  25.         //Ustawienie cookie
  26.         char cookie[80];
  27.         snprintf(cookie, sizeof(cookie),
  28.                  "SID=%lu; Path=/; HttpOnly; Secure; SameSite=Lax",
  29.                  (unsigned long)g_session_sid);
  30.  
  31.         //Odesłanie strony dostępnej po zalogowaniu
  32.         http_send_redirect(pssl, "/cgidata.html", cookie);
  33.     } else {
  34.         //Błędne logowanie
  35.         http_send_redirect(pssl, "/loginfail.html", NULL);
  36.     }
  37. }

W procedurze POST i GET obsługiwane są JSON. Przykładowa struktura takich danych to:

  1. Przykładowy JSON POST:
  2. {
  3.   "dhcp": 0,
  4.   "ip": "175.254.24.157",
  5.   "mask": "255.255.255.0",
  6.   "gw": "175.254.1.1",
  7.   "server_ip": "175.254.1.100",
  8.   "port": 8080
  9. }
  10.  
  11. Przykładowy JSON GET:
  12. {
  13.   "dhcp": 1,
  14.   "ip": "175.254.24.157",
  15.   "mask": "255.255.0.0",
  16.   "gw": "175.254.1.1",
  17.   "server_ip": "175.254.1.100",
  18.   "port": 8080,
  19.   "mac": "00:80:E1:00:00:01",
  20.   "device_name": "NAZWA URZADZENIA",
  21.   "description": "OPIS",
  22.   "description_in": "OPIS1"
  23. }

Struktury JSON są celowo uproszczone i dostosowane do obsługi przez ręcznego parsera, który obsługuje operacje na ciągach znaków. 

Odpowiedni HTTP generowane są przez funkcje:

  1. http_send_json_200 - wysyłka JSON 200 OK
  2. http_send_json_200_cstr - wysyłka JSON jako łańcuch znaków C
  3. http_send_json_401 - zwrócenie 401 Unauthorized
  4. http_send_400 - zwrócenie 400 Bad Request
  5. http_send_404 - zwrócenie 404 Not Found
  6. http_send_413 - 413 Payload Too Large
  7. http_send_text_200 - odpowiedź tekstowa
  8. http_send_file_200 - zwracanie pliku statycznego
  9. http_send_redirect - zwrócenie 302 Found

Powyższe funkcje odpowiadają za obsługę żądań oraz zwracania odpowiednich danych jako JSON bądź zwrócenie odpowiedniej strony. 

Odczytanie danych:

  1. static int ssl_read_request(mbedtls_ssl_context *pssl,
  2.                             uint8_t *buf,
  3.                             size_t max_len,
  4.                             int timeout_ms)
  5. {
  6.     if (!pssl || !buf || max_len < 16 || timeout_ms <= 0) return -2;
  7.  
  8.     size_t total = 0;
  9.     uint32_t start = xTaskGetTickCount();
  10.  
  11.     buf[0] = 0;
  12.  
  13.     for (;;) {
  14.  
  15.         if (total >= max_len - 1) {
  16.             buf[max_len - 1] = 0;
  17.             //Brak końca nagłówka
  18.             if (strstr((const char*)buf, "\r\n\r\n") == NULL) {
  19.                 return -3;
  20.             }
  21.             //Bufor pełny
  22.             return -2;
  23.         }
  24.  
  25.         int r = mbedtls_ssl_read(pssl, buf + total, (max_len - 1) - total);
  26.  
  27.         if (r > 0) {
  28.             total += (size_t)r;
  29.             buf[total] = 0;
  30.  
  31.             const char *body = find_body((const char*)buf);
  32.             if (!body) {
  33.                 //dalszy odczyt
  34.                 continue;
  35.             }
  36.  
  37.             int cl = find_content_length((const char*)buf);
  38.  
  39.             if (cl < 0) {
  40.                 return (int)total;
  41.             }
  42.  
  43.             if (cl == 0) {
  44.                 return (int)total;
  45.             }
  46.  
  47.             size_t hdr_len = (size_t)(body - (const char*)buf);
  48.  
  49.             if (hdr_len + (size_t)cl > max_len - 1) {
  50.                 return -4; // body za duże na nasz bufor
  51.             }
  52.  
  53.             if (hdr_len + (size_t)cl <= total) {
  54.                 return (int)total;
  55.             }
  56.  
  57.             //Brak całości czytamy dalej
  58.             continue;
  59.         }
  60.  
  61.         if (r == MBEDTLS_ERR_SSL_WANT_READ || r == MBEDTLS_ERR_SSL_WANT_WRITE) {
  62.             if ((xTaskGetTickCount() - start) > pdMS_TO_TICKS(timeout_ms)) {
  63.                 return -1; // timeout
  64.             }
  65.             vTaskDelay(pdMS_TO_TICKS(1));
  66.             continue;
  67.         }
  68.  
  69.         if (r == 0) {
  70.             return 0;
  71.         }
  72.  
  73.         if (!tls_is_normal_close(r)) {
  74.             log_mbedtls_err("ssl_read", r);
  75.         }
  76.         return r;
  77.     }
  78.     return 0;
  79. }

Powyższa funkcja odczytuje dane przez TLS. Składa pełne żądanie w jednym buforze, dodaktowo wyszukuje końce nagłówków, sprawdza Content-Length oraz pilnuje limitu buforów. 

Przesłanie danych do strumienia:

  1. static int ssl_write_all(mbedtls_ssl_context *pssl, const unsigned char *p, size_t len)
  2. {
  3.     while (len > 0) {
  4.         int ret = mbedtls_ssl_write(pssl, p, len);
  5.         if (ret > 0) {
  6.             p   += (size_t)ret;
  7.             len -= (size_t)ret;
  8.             continue;
  9.         }
  10.         if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
  11.             vTaskDelay(pdMS_TO_TICKS(1));
  12.             continue;
  13.         }
  14.         return ret;
  15.     }
  16.     return 0;
  17. }

Przesyła cały bufor TLS oraz sprowadza czy cały bufor został nadany do końca. 

Task dla serwera HTTPS wygląda następująco:

  1. void HTTPS_Server_Task(void *argument)
  2. {
  3.     (void)argument;
  4.  
  5.     init_https_files();
  6.     wait_for_lwip();
  7.  
  8.     if (tls_init() != 0) {
  9.         vTaskDelete(NULL);
  10.     }
  11.     if (tls_load_cert() != 0) {
  12.         vTaskDelete(NULL);
  13.     }
  14.     if (tls_configure() != 0) {
  15.         vTaskDelete(NULL);
  16.     }
  17.  
  18.     int ret = https_listen();
  19.     if (ret != 0) {
  20.         log_mbedtls_err("net_bind", ret);
  21.         vTaskDelete(NULL);
  22.     }
  23.  
  24.     for (;;) {
  25.         mbedtls_net_context client_fd;
  26.         mbedtls_ssl_context ssl;
  27.  
  28.         int hr = https_accept_and_handshake(&client_fd, &ssl);
  29.         if (hr != 0) {
  30.             continue;
  31.         }
  32.  
  33.         for (;;) {
  34.  
  35.             int r = ssl_read_request(&ssl, rx_buf, sizeof(rx_buf), 8000);
  36.             if (r == -1) { /* timeout */ break; }
  37.             if (r == -3) { http_send_413(&ssl); break; }  
  38.             if (r == -4) { http_send_413(&ssl); break; }  
  39.             if (r <= 0)  { break; }                      
  40.  
  41.             const char *req = (const char *)rx_buf;
  42.             char line[160];
  43.             char path[128];
  44.  
  45.             get_first_line(line, sizeof(line), req);
  46.             if (parse_path_from_line(line, path, sizeof(path)) != 0) {
  47.                 http_send_404(&ssl);
  48.                 break;
  49.             }
  50.  
  51.             int close_after = client_wants_close(req);
  52.             g_conn_hdr = close_after ? "close" : "keep-alive";
  53.  
  54.             if (!strncmp(line, "POST ", 5)) {
  55.                 const char *body = find_body(req);
  56.                 int cl = find_content_length(req);
  57.                 route_post(&ssl, req, path, body, cl);
  58.             } else {
  59.                 route_get(&ssl, req, path);
  60.             }
  61.  
  62.             if (close_after) break;
  63.         }
  64.  
  65.         https_close_client(&client_fd, &ssl);
  66.     }
  67. }

Testowanie połączenie można wykonać w oparciu o biblioteki openSSL zainstalowane np. razem z git:

  1. $ openssl s_client -connect <ip>:<port> -tls1_2 -servername <name> -state
  2. Connecting to 169.254.24.157
  3. CONNECTED(000000C4)
  4. SSL_connect:before SSL initialization
  5. SSL_connect:SSLv3/TLS write client hello
  6. SSL_connect:SSLv3/TLS write client hello
  7. SSL_connect:SSLv3/TLS read server hello
  8. depth=0 O=<nazwa>, CN=<ip>
  9. verify error:num=20:unable to get local issuer certificate
  10. verify return:1
  11. depth=0 O=<nazwa>, CN=<ip>
  12. verify error:num=21:unable to verify the first certificate
  13. verify return:1
  14. depth=0 O=<nazwa>, CN=<ip>
  15. verify return:1
  16. SSL_connect:SSLv3/TLS read server certificate
  17. SSL_connect:SSLv3/TLS read server key exchange
  18. SSL_connect:SSLv3/TLS read server done
  19. SSL_connect:SSLv3/TLS write client key exchange
  20. SSL_connect:SSLv3/TLS write change cipher spec
  21. SSL_connect:SSLv3/TLS write finished
  22. SSL_connect:SSLv3/TLS write finished
  23. SSL_connect:SSLv3/TLS read change cipher spec
  24. SSL_connect:SSLv3/TLS read finished
  25. ---
  26. Certificate chain
  27.  0 s:O=<nazwa>, CN=<ip>
  28.    i:O=<nazwa>, CN=<nazwa> CA
  29.    a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
  30.    v:NotBefore: Jan 16 21:11:48 2026 GMT; NotAfter: Jan 14 21:11:48 2036 GMT
  31. ---
  32. Server certificate
  33. -----BEGIN CERTIFICATE-----
  34. <certyfikat>
  35. -----END CERTIFICATE-----
  36. subject=O=<nazwa>, CN=<ip>
  37. issuer=O=<nazwa>, CN=<nazwa>CA
  38. ---
  39. No client certificate CA names sent
  40. Peer signing digest: SHA256
  41. Peer signature type: RSA
  42. Server Temp Key: ECDH, prime256v1, 256 bits
  43. ---
  44. SSL handshake has read 1367 bytes and written 334 bytes
  45. Verification error: unable to verify the first certificate
  46. ---
  47. New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
  48. Server public key is 2048 bit
  49. Secure Renegotiation IS supported
  50. Compression: NONE
  51. Expansion: NONE
  52. No ALPN negotiated
  53. SSL-Session:
  54.     Protocol  : TLSv1.2
  55.     Cipher    : ECDHE-RSA-AES128-GCM-SHA256
  56.     Session-ID: <Session-ID>
  57.     Session-ID-ctx:
  58.     Master-Key: <Master-Key>
  59.     PSK identity: None
  60.     PSK identity hint: None
  61.     SRP username: None
  62.     Start Time: 1776849386
  63.     Timeout   : 7200 (sec)
  64.     Verify return code: 21 (unable to verify the first certificate)
  65.     Extended master secret: no
  66. ---

Powyższe polecenie potwierdza poprawne zestawienie sesji TLS 1.2. z urządzeniem. Zakończono handshake, odczytano certyfikat i zestawiono szyfr ECDHE-RSA-AES128-GCM-SHA256. Pojawia się ostrzeżenie ale dotyczy ono lokalnego certyfikatu (Unable to verify the first certyficate). Oznacza ono brak odpowiedniego certyfikatu w magazynie zaufanych certyfikatów.  

Można też użyć takiego polecenia:

  1. curl.exe -vk https://<ip>:<port>/
  2. *   Trying <ip>:<port>...
  3. * schannel: disabled automatic use of client certificate
  4. * schannel: using IP address, SNI is not supported by OS.
  5. * Connected to <ip> (<ip>) port <port>
  6. * using HTTP/1.x
  7. > GET / HTTP/1.1
  8. > Host: <ip>:<port>
  9. > User-Agent: curl/8.12.1
  10. > Accept: */*
  11. >
  12. < HTTP/1.1 302 Found
  13. < Location: /cgilogin.html
  14. < Content-Length: 0
  15. < Connection: keep-alive
  16. <
  17. * Connection #0 to host <ip> left intact

Polecenie to również zestawia połączenie TLS, jednak nie wyświetla szczegółów handshake jak opensll_client. Pozwala na potwierdzenie działania warstwy HTTP. Połączenie zostało zestawione. Żądanie get zwróciło 302 Found z przekierowaniem na login. Brak aktywnej sesji użytkownika. 

Obecna implementacja jest lekka i wydaje się dobrze działać w systemach embedded. Ogranicza zużycie pamięci przez jedną sesję użytkownika. Taki serwer HTTPS pozwala na bezpieczne udostępnienie panelu konfiguracyjnego urządzenia, bez korzystania z  ciężkich elementów bibliotek. Dodatkowo zapewnia pełną kontrolę nad implementacją HTTPS. Oczywiście kosztem niektórych mechanizmów jak zarządzania sesjami.