W tym poście opiszę sposób uruchomienia HTTPS z wykorzystaniem mbedtls, lwip oraz freertos na układzie STM32H7.
- 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.
- 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
- 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
- static int tls_init(void)
- {
- mbedtls_net_init(&listen_fd); //Inicjalizacja dla gniazda nasłuchującego
- mbedtls_ssl_config_init(&conf); //Init struktury konfiguracji TLS
- mbedtls_entropy_init(&entropy);
- mbedtls_ctr_drbg_init(&ctr_drbg);
- mbedtls_x509_crt_init(&server_cert); //Init struktury
- mbedtls_pk_init(&server_key);
- return mbedtls_ctr_drbg_seed(&ctr_drbg,
- mbedtls_entropy_func,
- &entropy,
- (const unsigned char*)pers,
- strlen(pers));
- }
- static int tls_load_cert(void)
- {
- int ret;
- ret = mbedtls_x509_crt_parse(&server_cert,
- (const unsigned char*)mbedtls_server_certificate_https,
- strlen(mbedtls_server_certificate_https) + 1);
- if (ret != 0) {
- return ret;
- }
- ret = mbedtls_pk_parse_key(&server_key,
- (const unsigned char*)mbedtls_certificate_server_key_pass_https,
- strlen(mbedtls_certificate_server_key_pass_https) + 1,
- NULL, 0);
- if (ret != 0) {
- return ret;
- }
- return 0;
- }
- static const int https_ciphersuites[] = {
- MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
- MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
- MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
- 0
- };
- static int tls_configure(void)
- {
- //domyślna konfiguracja
- int ret = mbedtls_ssl_config_defaults(&conf,
- MBEDTLS_SSL_IS_SERVER,
- MBEDTLS_SSL_TRANSPORT_STREAM,
- MBEDTLS_SSL_PRESET_DEFAULT);
- if (ret != 0) {
- return ret;
- }
- //Ustawienie generatora liczb losowych
- mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
- //Wylaczenie weryfikacji klienta
- //Można ustawić:
- //MBEDTLS_SSL_VERIFY_NONE
- //MBEDTLS_SSL_VERIFY_OPTIONAL
- //MBEDTLS_SSL_VERIFY_REQUIRED
- //MBEDTLS_SSL_VERIFY_UNSET
- mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
- //Ustawienie listy szyfrów
- mbedtls_ssl_conf_ciphersuites(&conf, https_ciphersuites);
- //Protokół TLS 1.2
- mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
- mbedtls_ssl_conf_max_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
- //Podłączenie certyfikatu
- ret = mbedtls_ssl_conf_own_cert(&conf, &server_cert, &server_key);
- return ret;
- }
- static int https_listen(void)
- {
- char port_str[6];
- uint16_t port = Get_Port_Http_From_Settings();
- //Zabezpieczenie gdy port niepoprawny
- if (port == 0 || port > 65535) {
- port = 443;
- }
- //Konwersja portu na string
- (void)snprintf(port_str, sizeof(port_str), "%u", (unsigned)port);
- //Rozpoczęcie nasłuchiwania
- return mbedtls_net_bind(&listen_fd, NULL, port_str, MBEDTLS_NET_PROTO_TCP);
- }
- static int https_accept_and_handshake(mbedtls_net_context *pclient_fd, mbedtls_ssl_context *pssl)
- {
- int ret;
- uint32_t t0 = xTaskGetTickCount();
- mbedtls_net_init(pclient_fd);
- //Akceptacja nowego połączenia TCP
- ret = mbedtls_net_accept(&listen_fd, pclient_fd, NULL, 0, NULL);
- if (ret != 0) {
- mbedtls_net_free(pclient_fd);
- return ret;
- }
- mbedtls_ssl_init(pssl);
- ret = mbedtls_ssl_setup(pssl, &conf);
- if (ret != 0) {
- mbedtls_ssl_free(pssl);
- mbedtls_net_free(pclient_fd);
- return ret;
- }
- //Podłączenie TCP do TLS
- mbedtls_ssl_set_bio(pssl, pclient_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
- //Handshake wykonuje się w pętli, z dodatkowym timeoutem
- for (;;) {
- ret = mbedtls_ssl_handshake(pssl);
- if (ret == 0) return 0;
- if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
- if ((xTaskGetTickCount() - t0) > pdMS_TO_TICKS(5000)) {
- ret = MBEDTLS_ERR_SSL_TIMEOUT;
- break;
- }
- vTaskDelay(pdMS_TO_TICKS(1));
- continue;
- }
- break;
- }
- mbedtls_ssl_free(pssl);
- mbedtls_net_free(pclient_fd);
- return ret;
- }
- const uint8_t index_html[] =
- "<!doctype html><html><head><meta charset=\"utf-8\">"
- "<title>Panel</title><link rel=\"stylesheet\" href=\"/style.css\"></head>"
- "<body><h1>HTTPS PANEL</h1>"
- "<p><a href=\"/cgilogin.html\">Login</a></p>"
- "</body></html>";
- const unsigned int logo_png_len = sizeof(logo_png);
- const size_t index_html_len = sizeof(index_html)-1;
- typedef struct {
- const char *path;
- const char *mime; //Rodzaj danych np. "text/html; charset=utf-8", "text/css",
- const uint8_t *data;
- size_t len;
- uint8_t auth_required;
- } https_file_t;
- static void init_https_files(void)
- {
- https_files[0] = (https_file_t){ "/index.html", "text/html; charset=utf-8", index_html, index_html_len, 0 };
- //...
- //...
- }
- #define HTTPS_RX_BUFFER 3072
- static uint8_t rx_buf[HTTPS_RX_BUFFER];
- static uint32_t g_session_sid = 0;
- static const https_file_t *file_find(const char *path)
- {
- for (size_t i = 0; i < (sizeof(https_files)/sizeof(https_files[0])); i++) {
- if (strcmp(path, https_files[i].path) == 0) return &https_files[i];
- }
- return NULL;
- }
- static void route_get(mbedtls_ssl_context *pssl, const char *req, const char *path)
- {
- if (strcmp(path, "/") == 0 || strcmp(path, "/index.html") == 0) {
- if (is_authorized(req)) {
- http_send_redirect(pssl, "/cgidata.html", NULL);
- } else {
- http_send_redirect(pssl, "/cgilogin.html", NULL);
- }
- return;
- }
- if (strcmp(path, "/login.cgi") == 0) {
- http_send_redirect(pssl, "/cgilogin.html", NULL);
- return;
- }
- if (strcmp(path, "/logout.cgi") == 0) {
- g_session_sid = 0;
- http_send_redirect(pssl, "/cgilogin.html",
- "SID=0; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax");
- return;
- }
- if (strcmp(path, "/api/get") == 0) { handle_get_api_get(pssl, req); return; }
- //..
- //..
- const https_file_t *f = file_find(path);
- if (f) {
- if (f->auth_required && !is_authorized(req)) {
- http_send_redirect(pssl, "/cgilogin.html", NULL);
- return;
- }
- http_send_file_200(pssl, f);
- return;
- }
- http_send_404(pssl);
- }
- static void route_post(mbedtls_ssl_context *pssl,
- const char *req,
- const char *path,
- const char *body,
- int cl)
- {
- if (!body || cl < 0) {
- http_send_404(pssl);
- return;
- }
- if (strcmp(path, "/login.cgi") == 0) {
- handle_post_login(pssl, req, body, cl);
- return;
- }
- //...
- //...
- http_send_404(pssl);
- }
- static void handle_post_login(mbedtls_ssl_context *pssl, const char *req, const char *body, int cl)
- {
- (void)req;
- char user[32] = {0};
- char pass[32] = {0};
- //Odczytanie danych pname o ppass
- if (form_get_value(body, cl, "pname", user, sizeof(user)) != 0 ||
- form_get_value(body, cl, "ppass", pass, sizeof(pass)) != 0)
- {
- http_send_redirect(pssl, "/loginfail.html", NULL);
- return;
- }
- //dedkowanie znaków
- url_decode_inplace(user);
- url_decode_inplace(pass);
- //Sprawdzenie poprawności loginu i hasła
- if (cgi_login_handler(user, pass)) {
- //generowanie SID
- g_session_sid = weak_rand32();
- //Ustawienie cookie
- char cookie[80];
- snprintf(cookie, sizeof(cookie),
- "SID=%lu; Path=/; HttpOnly; Secure; SameSite=Lax",
- (unsigned long)g_session_sid);
- //Odesłanie strony dostępnej po zalogowaniu
- http_send_redirect(pssl, "/cgidata.html", cookie);
- } else {
- //Błędne logowanie
- http_send_redirect(pssl, "/loginfail.html", NULL);
- }
- }
- Przykładowy JSON POST:
- {
- "dhcp": 0,
- "ip": "175.254.24.157",
- "mask": "255.255.255.0",
- "gw": "175.254.1.1",
- "server_ip": "175.254.1.100",
- "port": 8080
- }
- Przykładowy JSON GET:
- {
- "dhcp": 1,
- "ip": "175.254.24.157",
- "mask": "255.255.0.0",
- "gw": "175.254.1.1",
- "server_ip": "175.254.1.100",
- "port": 8080,
- "mac": "00:80:E1:00:00:01",
- "device_name": "NAZWA URZADZENIA",
- "description": "OPIS",
- "description_in": "OPIS1"
- }
- http_send_json_200 - wysyłka JSON 200 OK
- http_send_json_200_cstr - wysyłka JSON jako łańcuch znaków C
- http_send_json_401 - zwrócenie 401 Unauthorized
- http_send_400 - zwrócenie 400 Bad Request
- http_send_404 - zwrócenie 404 Not Found
- http_send_413 - 413 Payload Too Large
- http_send_text_200 - odpowiedź tekstowa
- http_send_file_200 - zwracanie pliku statycznego
- http_send_redirect - zwrócenie 302 Found
- static int ssl_read_request(mbedtls_ssl_context *pssl,
- uint8_t *buf,
- size_t max_len,
- int timeout_ms)
- {
- if (!pssl || !buf || max_len < 16 || timeout_ms <= 0) return -2;
- size_t total = 0;
- uint32_t start = xTaskGetTickCount();
- buf[0] = 0;
- for (;;) {
- if (total >= max_len - 1) {
- buf[max_len - 1] = 0;
- //Brak końca nagłówka
- if (strstr((const char*)buf, "\r\n\r\n") == NULL) {
- return -3;
- }
- //Bufor pełny
- return -2;
- }
- int r = mbedtls_ssl_read(pssl, buf + total, (max_len - 1) - total);
- if (r > 0) {
- total += (size_t)r;
- buf[total] = 0;
- const char *body = find_body((const char*)buf);
- if (!body) {
- //dalszy odczyt
- continue;
- }
- int cl = find_content_length((const char*)buf);
- if (cl < 0) {
- return (int)total;
- }
- if (cl == 0) {
- return (int)total;
- }
- size_t hdr_len = (size_t)(body - (const char*)buf);
- if (hdr_len + (size_t)cl > max_len - 1) {
- return -4; // body za duże na nasz bufor
- }
- if (hdr_len + (size_t)cl <= total) {
- return (int)total;
- }
- //Brak całości czytamy dalej
- continue;
- }
- if (r == MBEDTLS_ERR_SSL_WANT_READ || r == MBEDTLS_ERR_SSL_WANT_WRITE) {
- if ((xTaskGetTickCount() - start) > pdMS_TO_TICKS(timeout_ms)) {
- return -1; // timeout
- }
- vTaskDelay(pdMS_TO_TICKS(1));
- continue;
- }
- if (r == 0) {
- return 0;
- }
- if (!tls_is_normal_close(r)) {
- log_mbedtls_err("ssl_read", r);
- }
- return r;
- }
- return 0;
- }
- static int ssl_write_all(mbedtls_ssl_context *pssl, const unsigned char *p, size_t len)
- {
- while (len > 0) {
- int ret = mbedtls_ssl_write(pssl, p, len);
- if (ret > 0) {
- p += (size_t)ret;
- len -= (size_t)ret;
- continue;
- }
- if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
- vTaskDelay(pdMS_TO_TICKS(1));
- continue;
- }
- return ret;
- }
- return 0;
- }
- void HTTPS_Server_Task(void *argument)
- {
- (void)argument;
- init_https_files();
- wait_for_lwip();
- if (tls_init() != 0) {
- vTaskDelete(NULL);
- }
- if (tls_load_cert() != 0) {
- vTaskDelete(NULL);
- }
- if (tls_configure() != 0) {
- vTaskDelete(NULL);
- }
- int ret = https_listen();
- if (ret != 0) {
- log_mbedtls_err("net_bind", ret);
- vTaskDelete(NULL);
- }
- for (;;) {
- mbedtls_net_context client_fd;
- mbedtls_ssl_context ssl;
- int hr = https_accept_and_handshake(&client_fd, &ssl);
- if (hr != 0) {
- continue;
- }
- for (;;) {
- int r = ssl_read_request(&ssl, rx_buf, sizeof(rx_buf), 8000);
- if (r == -1) { /* timeout */ break; }
- if (r == -3) { http_send_413(&ssl); break; }
- if (r == -4) { http_send_413(&ssl); break; }
- if (r <= 0) { break; }
- const char *req = (const char *)rx_buf;
- char line[160];
- char path[128];
- get_first_line(line, sizeof(line), req);
- if (parse_path_from_line(line, path, sizeof(path)) != 0) {
- http_send_404(&ssl);
- break;
- }
- int close_after = client_wants_close(req);
- g_conn_hdr = close_after ? "close" : "keep-alive";
- if (!strncmp(line, "POST ", 5)) {
- const char *body = find_body(req);
- int cl = find_content_length(req);
- route_post(&ssl, req, path, body, cl);
- } else {
- route_get(&ssl, req, path);
- }
- if (close_after) break;
- }
- https_close_client(&client_fd, &ssl);
- }
- }
- $ openssl s_client -connect <ip>:<port> -tls1_2 -servername <name> -state
- Connecting to 169.254.24.157
- CONNECTED(000000C4)
- SSL_connect:before SSL initialization
- SSL_connect:SSLv3/TLS write client hello
- SSL_connect:SSLv3/TLS write client hello
- SSL_connect:SSLv3/TLS read server hello
- depth=0 O=<nazwa>, CN=<ip>
- verify error:num=20:unable to get local issuer certificate
- verify return:1
- depth=0 O=<nazwa>, CN=<ip>
- verify error:num=21:unable to verify the first certificate
- verify return:1
- depth=0 O=<nazwa>, CN=<ip>
- verify return:1
- SSL_connect:SSLv3/TLS read server certificate
- SSL_connect:SSLv3/TLS read server key exchange
- SSL_connect:SSLv3/TLS read server done
- SSL_connect:SSLv3/TLS write client key exchange
- SSL_connect:SSLv3/TLS write change cipher spec
- SSL_connect:SSLv3/TLS write finished
- SSL_connect:SSLv3/TLS write finished
- SSL_connect:SSLv3/TLS read change cipher spec
- SSL_connect:SSLv3/TLS read finished
- ---
- Certificate chain
- 0 s:O=<nazwa>, CN=<ip>
- i:O=<nazwa>, CN=<nazwa> CA
- a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
- v:NotBefore: Jan 16 21:11:48 2026 GMT; NotAfter: Jan 14 21:11:48 2036 GMT
- ---
- Server certificate
- -----BEGIN CERTIFICATE-----
- <certyfikat>
- -----END CERTIFICATE-----
- subject=O=<nazwa>, CN=<ip>
- issuer=O=<nazwa>, CN=<nazwa>CA
- ---
- No client certificate CA names sent
- Peer signing digest: SHA256
- Peer signature type: RSA
- Server Temp Key: ECDH, prime256v1, 256 bits
- ---
- SSL handshake has read 1367 bytes and written 334 bytes
- Verification error: unable to verify the first certificate
- ---
- New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
- Server public key is 2048 bit
- Secure Renegotiation IS supported
- Compression: NONE
- Expansion: NONE
- No ALPN negotiated
- SSL-Session:
- Protocol : TLSv1.2
- Cipher : ECDHE-RSA-AES128-GCM-SHA256
- Session-ID: <Session-ID>
- Session-ID-ctx:
- Master-Key: <Master-Key>
- PSK identity: None
- PSK identity hint: None
- SRP username: None
- Start Time: 1776849386
- Timeout : 7200 (sec)
- Verify return code: 21 (unable to verify the first certificate)
- Extended master secret: no
- ---
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:
- curl.exe -vk https://<ip>:<port>/
- * Trying <ip>:<port>...
- * schannel: disabled automatic use of client certificate
- * schannel: using IP address, SNI is not supported by OS.
- * Connected to <ip> (<ip>) port <port>
- * using HTTP/1.x
- > GET / HTTP/1.1
- > Host: <ip>:<port>
- > User-Agent: curl/8.12.1
- > Accept: */*
- >
- < HTTP/1.1 302 Found
- < Location: /cgilogin.html
- < Content-Length: 0
- < Connection: keep-alive
- <
- * 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.