W tym poście chciałbym opisać implementację serwera SSL. Testy wykonałem na płycie z układem STM32H725 oraz układem LAN8720A.
Plik LWIP
Poniżej konfiguracja biblioteki LWIP do projektu:
- #define LWIP_DEBUG 2
- #define NETIF_DEBUG LWIP_DBG_ON
- #define DHCP_DEBUG LWIP_DBG_ON
- #define UDP_DEBUG LWIP_DBG_ON
- #define MEMP_DEBUG LWIP_DBG_ON
- #define MEM_DEBUG LWIP_DBG_ON
- #define ICMP_DEBUG LWIP_DBG_ON
- /* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
- /* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
- /*----- WITH_RTOS enabled (Since FREERTOS is set) -----*/
- #define WITH_RTOS 1
- /* Temporary workaround to avoid conflict on errno defined in STM32CubeIDE and lwip sys_arch.c errno */
- #undef LWIP_PROVIDE_ERRNO
- /*----- WITH_MBEDTLS enabled (Since MBEDTLS and FREERTOS are set) -----*/
- #define WITH_MBEDTLS 1
- /*----- CHECKSUM_BY_HARDWARE enabled -----*/
- #define CHECKSUM_BY_HARDWARE 1
- /*-----------------------------------------------------------------------------*/
- /* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
- /* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
- /*----- Default value in ETH configuration GUI in CubeMx: 1524 -----*/
- //#define ETH_RX_BUFFER_SIZE 1536
- #define ETH_RX_BUFFER_SIZE 2048
- /*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
- #define MEM_ALIGNMENT 4
- /*----- Default Value for MEM_SIZE: 1600 ---*/
- #define MEM_SIZE 32232
- /*----- Value in opt.h for LWIP_DNS: 0 -----*/
- #define LWIP_DNS 1
- /*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
- #define MEM_ALIGNMENT 4
- /*----- Default Value for H7 devices: 0x30044000 -----*/
- #define LWIP_RAM_HEAP_POINTER 0x30000200
- /*----- Value supported for H7 devices: 1 -----*/
- #define LWIP_SUPPORT_CUSTOM_PBUF 1
- /*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
- #define LWIP_ETHERNET 1
- /*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
- #define LWIP_DNS_SECURE 7
- /*----- Default Value for TCP_MSS: 536 ---*/
- #define TCP_MSS 1460
- /*----- Default Value for TCP_SND_BUF: 2920 ---*/
- #define TCP_SND_BUF 2920//5840
- /*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
- #define TCP_SND_QUEUELEN 16
- /*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
- #define TCP_SNDLOWAT 1071
- /*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
- #define TCP_SNDQUEUELOWAT 5
- /*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
- #define TCP_WND_UPDATE_THRESHOLD 536
- /*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
- #define LWIP_NETIF_LINK_CALLBACK 1
- /*----- Value in opt.h for TCPIP_THREAD_STACKSIZE: 0 -----*/
- #define TCPIP_THREAD_STACKSIZE 1024
- /*----- Value in opt.h for TCPIP_THREAD_PRIO: 1 -----*/
- #define TCPIP_THREAD_PRIO osPriorityNormal
- /*----- Value in opt.h for TCPIP_MBOX_SIZE: 0 -----*/
- #define TCPIP_MBOX_SIZE 6
- /*----- Value in opt.h for SLIPIF_THREAD_STACKSIZE: 0 -----*/
- #define SLIPIF_THREAD_STACKSIZE 1024
- /*----- Value in opt.h for SLIPIF_THREAD_PRIO: 1 -----*/
- #define SLIPIF_THREAD_PRIO 3
- /*----- Value in opt.h for DEFAULT_THREAD_STACKSIZE: 0 -----*/
- #define DEFAULT_THREAD_STACKSIZE 1024
- /*----- Value in opt.h for DEFAULT_THREAD_PRIO: 1 -----*/
- #define DEFAULT_THREAD_PRIO 3
- /*----- Value in opt.h for DEFAULT_UDP_RECVMBOX_SIZE: 0 -----*/
- #define DEFAULT_UDP_RECVMBOX_SIZE 6
- /*----- Value in opt.h for DEFAULT_TCP_RECVMBOX_SIZE: 0 -----*/
- #define DEFAULT_TCP_RECVMBOX_SIZE 6
- /*----- Value in opt.h for DEFAULT_ACCEPTMBOX_SIZE: 0 -----*/
- #define DEFAULT_ACCEPTMBOX_SIZE 6
- /*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
- #define RECV_BUFSIZE_DEFAULT 2000000000
- /*----- Value in opt.h for LWIP_USE_EXTERNAL_MBEDTLS: 0 -----*/
- #define LWIP_USE_EXTERNAL_MBEDTLS 1
- /*----- Default Value for LWIP_HTTPD: 0 ---*/
- #define LWIP_HTTPD 1
- #define HTTPD_USE_CUSTOM_FSDATA 0
- #define LWIP_HTTPD_SUPPORT_POST 1
- #define LWIP_HTTPD_CGI 1
- #define LWIP_HTTPD_SSI 1
- /*----- Value in opt.h for LWIP_STATS: 1 -----*/
- #define LWIP_STATS 0
- /*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
- #define CHECKSUM_GEN_IP 0
- /*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
- #define CHECKSUM_GEN_UDP 0
- /*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
- #define CHECKSUM_GEN_TCP 0
- /*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
- #define CHECKSUM_GEN_ICMP6 0
- /*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
- #define CHECKSUM_CHECK_IP 0
- /*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
- #define CHECKSUM_CHECK_UDP 0
- /*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
- #define CHECKSUM_CHECK_TCP 0
- /*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
- #define CHECKSUM_CHECK_ICMP6 0
- /*-----------------------------------------------------------------------------*/
- /* USER CODE BEGIN 1 */
- #define LWIP_SOCKET 1
- #define LWIP_DNS 1
- #define SO_REUSE 1
- /* USER CODE END 1 */
Konfiguracja serwera:
Inicjalizacja zmiennych:
- static void MBEDTLS_Init(void){
- #ifdef MBEDTLS_MEMORY_BUFFER_ALLOC_C
- mbedtls_memory_buffer_alloc_init(memory_buf, sizeof(memory_buf));
- #endif
- mbedtls_net_init( &listen_fd );
- mbedtls_net_init( &client_fd );
- mbedtls_ssl_init( &ssl );
- mbedtls_ssl_config_init( &conf );
- #if defined(MBEDTLS_SSL_CACHE_C)
- mbedtls_ssl_cache_init( &cache );
- #endif
- mbedtls_x509_crt_init( &srvcert );
- mbedtls_pk_init( &pkey );
- mbedtls_entropy_init( &entropy );
- mbedtls_ctr_drbg_init( &ctr_drbg );
- }
Ustawienie certyfikatów:
- static uint8_t MBEDTLS_ParseCerificateKey(void) {
- int opStatus = 0;
- opStatus = mbedtls_pk_parse_key(&pkey, (const unsigned char*)mbedtls_certificate_key_ssl_server, mbedtls_server_certificate_key_len, (const unsigned char*)mbedtls_certificate_key_pass_ssl_server, mbedtls_server_certificate_key_pass_len);
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_pk_parse_key returned -0x%x\r\n \r\n ", (unsigned int) -opStatus);
- return 1;
- }
- opStatus = mbedtls_x509_crt_parse(&srvcert, (const unsigned char*)mbedtls_server_certificate, mbedtls_server_certificate_len + 1);
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_x509_crt_parse returned -0x%x\r\n \r\n ", (unsigned int) -opStatus);
- return 1;
- }
- opStatus = mbedtls_ssl_conf_own_cert(&conf, &srvcert, &pkey);
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_ssl_conf_own_cert returned -0x%x\r\n \r\n ", (unsigned int) -opStatus);
- return 1;
- }
- return 0;
- }
Certyfikaty musza byc zdefiniowane w następujący sposób:
- const char mbedtls_server_certificate[] =
- "-----BEGIN CERTIFICATE-----\r\n"
- "CERTYFIKAT\r\n"
- "CERTYFIKAT\r\n"
- "-----END CERTIFICATE-----\r\n";
- const char mbedtls_certificate_key_pass_ssl_server[] = "sH$kd@t@104";
- const char mbedtls_certificate_key_ssl_server[] =
- "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n"
- "CERTYFIKAT KLUCZ\r\n"
- "CERTYFIKAT KLUCZ\r\n"
- "-----END ENCRYPTED PRIVATE KEY-----\r\n";
- const size_t mbedtls_server_certificate_len = strlen(mbedtls_server_certificate);
- const size_t mbedtls_server_certificate_key_len = sizeof(mbedtls_certificate_key_ssl_server);
- const size_t mbedtls_server_certificate_key_pass_len = strlen(mbedtls_certificate_key_pass_ssl_server);
Należy pamiętać aby w certyfikatach oraz kluczach nie znajdowałuy się spacje. Każda linia musi być zakończona znakiem \r\n.
Ustawienie soketu TCP:
- static uint8_t SetupTCPSocket(void) {
- int ret;
- mbedtls_printf( " . Bind on https://localhost:%s/ ...", SERVER_PORT );
- fflush(stdout);
- if((ret = mbedtls_net_bind(&listen_fd, NULL, SERVER_PORT , MBEDTLS_NET_PROTO_TCP )) != 0)
- {
- mbedtls_printf( " failed\n ! mbedtls_net_bind returned %d -0x%x\n\n", ret, (unsigned int) -ret);
- return 1;
- }
- return 0;
- }
Inicjalizacja generatora liczb losowych. Pozwala to na zapewnienie entropii czyli generowania naprawdę losowych liczb dla RNG. Dzięki temu można uzyskać prawdziwie losowe wartości dla każdej sesji SSL.
- static uint8_t SeedRNG(void) {
- int ret;
- mbedtls_printf( " . Seeding the random number generator..." );
- if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen( (char *)pers))) != 0)
- {
- mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret );
- return 1;
- }
- mbedtls_printf( " ok\n" );
- return 0;
- }
Konfiguracja połączenia SSL:
- static uint8_t SetupConfig(void) {
- int ret;
- mbedtls_printf( " . Setting up the SSL data...." );
- if((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
- return 1;
- }
- mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
- #if defined(MBEDTLS_SSL_CACHE_C)
- mbedtls_ssl_conf_session_cache(&conf, &cache, mbedtls_ssl_cache_get, mbedtls_ssl_cache_set);
- #endif
- mbedtls_ssl_conf_ca_chain(&conf, srvcert.next, NULL);
- if((ret = mbedtls_ssl_conf_own_cert(&conf, &srvcert, &pkey)) != 0) {
- return 2;
- }
- if((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) {
- return 3;
- }
- mbedtls_printf( " ok\n" );
- return 0;
- }
Kolejnym krokiem jest oczekiwanie na połączenie klienta:
- static uint8_t WaitForClientConnection(void) {
- int ret;
- if((ret = WaitForConnection()) != 0) {
- mbedtls_printf(" failed\n ! mbedtls_net_accept returned %d\n\n", ret);
- return 1;
- }
- mbedtls_printf("Client Conn ok\n");
- fflush(stdout);
- return 0;
- }
Następnie wykonujemy procedurę Handshake:
- static uint8_t PerformHandshake(void) {
- int ret;
- if((ret = Handshake()) != 0)
- {
- mbedtls_printf(" failed\n ! mbedtls_ssl_handshake returned %d\n\n", ret);
- return 1;
- }
- mbedtls_printf("Handshake ok\n");
- fflush(stdout);
- return 0;
- }
Następnie odczytujemy rządanie od klienta do serwera:
- mbedtls_printf(" < Read from client:");
- do
- {
- len = sizeof(buf) - 1;
- memset(buf, 0, sizeof(buf));
- ret = mbedtls_ssl_read(&ssl, buf, len);
- if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- continue;
- }
- if(ret <= 0)
- {
- switch(ret)
- {
- case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
- mbedtls_printf(" connection was closed gracefully\n");
- HAL_Delay(100);
- break;
- case MBEDTLS_ERR_NET_CONN_RESET:
- mbedtls_printf(" connection was reset by peer\n");
- HAL_Delay(100);
- break;
- default:
- mbedtls_printf(" mbedtls_ssl_read returned -0x%x\n", -ret);
- HAL_Delay(100);
- break;
- }
- HAL_Delay(100);
- break;
- }
- len = ret;
- mbedtls_printf(" %d bytes read\n\n%s", len, (char *) buf);
- if(ret > 0)
- {
- break;
- }
- } while(1);
Po odczytaniu odpowiedzi od klienta należy sprawdzić kody odpowiedzi. Po odebraniu danych można przejść do przesłania odpowiedzi.
Z serwera odsyłamy odpowiedź:
- mbedtls_printf( " > Write to client:" );
- for(uint32_t i=0; i<sizeof(WebSide_LoginPage_buff); i++) {
- buf[i] = WebSide_LoginPage_buff[i];
- }
- len = sizeof(WebSide_LoginPage_buff);
- while((ret = mbedtls_ssl_write(&ssl, buf, len)) <= 0)
- {
- if(ret == MBEDTLS_ERR_NET_CONN_RESET)
- {
- mbedtls_printf(" failed\n ! peer closed the connection\n\n");
- goto reset;
- }
- if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- mbedtls_printf( " failed\n ! mbedtls_ssl_write returned %d\n\n", ret );
- goto exit;
- }
- }
- len = ret;
- mbedtls_printf(" %d bytes written\n\n%s\n", len, (char *) buf);
- while((ret = mbedtls_ssl_close_notify(&ssl)) < 0)
- {
- if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- mbedtls_printf( " failed\n ! mbedtls_ssl_close_notify returned %d\n\n", ret );
- goto reset;
- }
- }
Z urządzenia przesyłam standardowy kod odpowiedzi:
- const unsigned char WebSide_Response_buff[] =
- "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"
- "<h2>mbed TLS Test Server</h2>\r\n"
- "<p>Successful connection using: %s</p>\r\n"
Testowałęm też przesyłanie następującej strony:
- const unsigned char WebSide_LoginPage_buff[] =
- "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"
- "<!DOCTYPE html>\r\n"
- "<html lang=\"pl\">\r\n"
- "<head>\r\n"
- "<meta charset=\"UTF-8\">\r\n"
- "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n"
- "<title>TestWeb</title>\r\n"
- "<style>\r\n"
- "body {\r\n"
- "background-color: #9f9da7;\r\n"
- "font-size: 1.6rem;\r\n"
- "font-family: \"Open Sans\", sans-serif;\r\n"
- "color: #2b3e51;\r\n"
- "margin: 0;\r\n"
- "padding: 0;\r\n"
- "display: flex;\r\n"
- "align-items: center;\r\n"
- "justify-content: center;\r\n"
- "height: 100vh;\r\n"
- "}\r\n"
- "#login-form-wrap {\r\n"
- "background-color: #fff;\r\n"
- "width: 35%;\r\n"
- "text-align: center;\r\n"
- "padding: 20px;\r\n"
- "border-radius: 4px;\r\n"
- "box-shadow: 0px 30px 50px 0px rgba(0, 0, 0, 0.2);\r\n"
- "}\r\n"
- "h2 {\r\n"
- "font-weight: 300;\r\n"
- "text-align: center;\r\n"
- "}\r\n"
- "form {\r\n"
- "padding: 0 20px;\r\n"
- "}\r\n"
- "input {\r\n"
- "display: block;\r\n"
- "box-sizing: border-box;\r\n"
- "width: 100%;\r\n"
- "outline: none;\r\n"
- "height: 60px;\r\n"
- "line-height: 60px;\r\n"
- "border-radius: 4px;\r\n"
- "margin-bottom: 15px;\r\n"
- "border: 1px solid #c2c0ca;\r\n"
- "font-style: normal;\r\n"
- "font-size: 16px;\r\n"
- "padding: 0 10px;\r\n"
- "color: #8a8b8e;\r\n"
- "}\r\n"
- "input:focus {\r\n"
- "border-color: #3ca9e2;\r\n"
- "}\r\n"
- "input:invalid:focus {\r\n"
- "color: #cc1e2b;\r\n"
- "border-color: #cc1e2b;\r\n"
- "}\r\n"
- "input[type=\"submit\"] {\r\n"
- "border: none;\r\n"
- "display: block;\r\n"
- "background-color: #3ca9e2;\r\n"
- "color: #fff;\r\n"
- "font-weight: bold;\r\n"
- "text-transform: uppercase;\r\n"
- "cursor: pointer;\r\n"
- "transition: background-color 0.2s ease;\r\n"
- "font-size: 18px;\r\n"
- "}\r\n"
- "input[type=\"submit\"]:hover {\r\n"
- "background-color: #329dd5;\r\n"
- "}\r\n"
- "#create-account-wrap {\r\n"
- "background-color: #eeedf1;\r\n"
- "color: #8a8b8e;\r\n"
- "font-size: 14px;\r\n"
- "padding: 10px 0;\r\n"
- "border-radius: 0 0 4px 4px;\r\n"
- "}\r\n"
- "</style>\r\n"
- "</head>\r\n"
- "<body>\r\n"
- "<div id=\"login-form-wrap\">\r\n"
- "<h2>Logowanie</h2>\r\n"
- "<form id=\"login-form\" action=\"cgilogin.html\" method=\"post\">\r\n"
- "<input type=\"text\" id=\"pname\" name=\"pname\" placeholder=\"Użytkownik\" required>\r\n"
- "<input type=\"password\" id=\"ppass\" name=\"ppass\" placeholder=\"Hasło\" required>\r\n"
- "<input type=\"submit\" id=\"login\" value=\"Logowanie\">\r\n"
- "</form>\r\n"
- "<div id=\"create-account-wrap\"></div>\r\n"
- "</div>\r\n"
- "</body>\r\n"
- "</html>";
Obsługę serwera konczymy przez zwolnienie zasobów obsługujących działanie serwera. Czyli zamknięcie otwartych połączeń, zwolnienie certyfikatów, kluczy, konfiguracji itp:
- exit:
- mbedtls_net_free( &client_fd );
- mbedtls_net_free( &listen_fd );
- mbedtls_x509_crt_free( &srvcert );
- mbedtls_pk_free( &pkey );
- mbedtls_ssl_free( &ssl );
- mbedtls_ssl_config_free( &conf );
- #if defined(MBEDTLS_SSL_CACHE_C)
- mbedtls_ssl_cache_free( &cache );
- #endif
- mbedtls_ctr_drbg_free( &ctr_drbg );
- mbedtls_entropy_free( &entropy );
Dodatkowo bibliotekę LWIP uruchamiam w innym procesie:
- void StartDefaultTask(void const * argument)
- {
- /* USER CODE BEGIN 5 */
- MX_LWIP_Init();
- for(;;)
- {
- if((CheckIfCableIsConnected() == 0)
- && (flagSSLTask == 0) && (netifIsUpFlag == 1))
- {
- flagSSLTask = 1;
- }
- if(flagSSLTask == 1)
- {
- flagSSLTask = 2;
- xSemaphoreGive(sslTaskSemaphore);
- }
- osDelay(250);
- }
- /* USER CODE END 5 */
- }
Na samym początku procesu SSL_Server oczekuje na odblokowanie semafora, który zapewni, że biblioteka LWIP i interfejs sieciowy są poprawnie zainicjalizowane.
- while(xSemaphoreTake(sslTaskSemaphore, portMAX_DELAY) == 0) {
- osDelay(3000);
- }