W tym poście chciałbym opisać sposób komunikacji pomiędzy układem STM32H7 działającym jako Client TCP z serwerem po SSL.
[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]
Generowanie certyfikatów:
Obsługę generowania przykładowych certyfikatów opisałem w poście dotyczącym programu C# - TCP Client oraz TCP Server. Tutaj zasada jest identyczna. Natomiast do programu klienta umieszczonego w układzie STM32H7 nie będę wykorzystywał certyfikatu w formacie *.pfx tylko jako PEM.
Połączenie SSL
W związku z tym, że korzystamy z biblioteki MBEDTLS warto się zapoznać z połączeniem SSL. Ogólnie warto poznać temat przed jego wykonaniem, natomiast w tym przypadku jest dużo elementów konfiguracyjnych wewnątrz biblioteki. Programowanie nie jest tak proste jak w przypadku ustanowienia połączenia SSL np. w C#, gdzie jest wykorzystywana dużo bardziej rozbudowana biblioteka z mniejszą możliwością wygenerowania błędów.
Komunikacja odbywa się w następujący sposób:
Client Hello - wysłanie startowej wiadomości od klienta do serwera. W przypadku opisywanego rozwiązania wiadomość będzie zawierała informację z obsługiwaną wersją TLS, wygenerowanymi losowymi informacjami, identyfikatorem sesji oraz metod szyfrowania oraz sposobu wymiany kluczy przez klienta.
Server Hello - serwer wysyła wybrany rodzaj komunikacji TLS, wygenerowane losowe dane, identyfikator sesji, wybrane algorytmy szyfrowania, metodę wymiany kluczy, funkcję do uzyskania wartości MAC. Dodatkowo przesyła certyfikat wraz z żądaniem pobrania certyfikatu klienta.
Po tej wiadomości następuje weryfikacja certyfikatu przez klienta.
Client Certyfikat - kolejnym elementem jest przesłanie certyfikatu klienta.
Client Key Exchange - przesłanie kluczy klienta do serwera.
Change Cipher Spec - wiadomości przesyłane pomiędzy klientem a serwerem. W wiadomości przesyłana jest informacja (wartość 1) o ustanowieniu wynegocjowanej sesji.
Po tych operacjach połączenie jest poprawnie ustanowione. Poniżej zrzut ekranu z przesłanymi danymi pomiędzy klientem a serwerem.
Program:
W projekcie wykorzystałem FREERTOS, LWIP oraz MBEDTLS.
Przy wykonywaniu połączenia należy pamiętać o wyłączeniu zapory, lub ustawieniu odpowiedniego wyjątku. W innym przypadku może nie udać się nawiązać połączenia pomiędzy klientem a serwerem.
Poniżej plik lwipopts.h z którego usunąłem komentarze. Plik ten jest generowany na podstawie ustawień przekazanych przez CubeMx:
- #ifndef __LWIPOPTS__H__
- #define __LWIPOPTS__H__
- #include "main.h"
- /*-----------------------------------------------------------------------------*/
- /* Current version of LwIP supported by CubeMx: 2.1.2 -*/
- /*-----------------------------------------------------------------------------*/
- #ifdef __cplusplus
- extern "C" {
- #endif
- #define WITH_RTOS 1
- #define WITH_MBEDTLS 1
- #define CHECKSUM_BY_HARDWARE 1
- #define LWIP_DHCP 1
- #define ETH_RX_BUFFER_SIZE 2144
- #define LWIP_DNS 1
- #define MEM_ALIGNMENT 4
- #define LWIP_RAM_HEAP_POINTER 0x30004000
- #define LWIP_SUPPORT_CUSTOM_PBUF 1
- #define LWIP_ETHERNET 1
- #define LWIP_DNS_SECURE 7
- #define TCP_SND_QUEUELEN 9
- #define TCP_SNDLOWAT 1071
- #define TCP_SNDQUEUELOWAT 5
- #define TCP_WND_UPDATE_THRESHOLD 536
- #define LWIP_NETIF_LINK_CALLBACK 1
- #define TCPIP_THREAD_STACKSIZE 1024
- #define TCPIP_THREAD_PRIO osPriorityNormal
- #define TCPIP_MBOX_SIZE 6
- #define SLIPIF_THREAD_STACKSIZE 1024
- #define SLIPIF_THREAD_PRIO 3
- #define DEFAULT_THREAD_STACKSIZE 1024
- #define DEFAULT_THREAD_PRIO 3
- #define DEFAULT_UDP_RECVMBOX_SIZE 6
- #define DEFAULT_TCP_RECVMBOX_SIZE 6
- #define DEFAULT_ACCEPTMBOX_SIZE 6
- #define RECV_BUFSIZE_DEFAULT 2000000000
- #define LWIP_USE_EXTERNAL_MBEDTLS 1
- #define LWIP_STATS 0
- #define CHECKSUM_GEN_IP 0
- #define CHECKSUM_GEN_UDP 0
- #define CHECKSUM_GEN_ICMP6 0
- #define CHECKSUM_CHECK_IP 0
- #define CHECKSUM_CHECK_UDP 0
- #define CHECKSUM_CHECK_TCP 0
- #define CHECKSUM_CHECK_ICMP6 0
- #ifdef __cplusplus
- }
- #endif
- #endif /*__LWIPOPTS__H__ */
Teraz parametry biblioteki MBEDTLS. Zostały one zapisane w wygenerowanym pliku mbedtls_config.h. Podobnie jak poprzednio z pliku usunąłem komentarze oraz nieuruchomione części biblioteki wszystkie dodatkowe informacje pojawią się w tym pliku po wygenerowaniu projektu.
- #ifndef MBEDTLS_CONFIG_H
- #define MBEDTLS_CONFIG_H
- #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_DEPRECATE)
- #define _CRT_SECURE_NO_DEPRECATE 1
- #endif
- #define MBEDTLS_HAVE_ASM
- #define MBEDTLS_NO_UDBL_DIVISION
- #define MBEDTLS_HAVE_TIME
- #define MBEDTLS_PLATFORM_MEMORY
- #define MBEDTLS_ENTROPY_HARDWARE_ALT
- #define MBEDTLS_AES_ROM_TABLES
- #define MBEDTLS_CIPHER_MODE_CBC
- #define MBEDTLS_CIPHER_MODE_OFB
- #define MBEDTLS_CIPHER_MODE_XTS
- #define MBEDTLS_REMOVE_3DES_CIPHERSUITES
- #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
- #define MBEDTLS_ECP_DP_SECP384R1_ENABLED
- #define MBEDTLS_ECP_DP_CURVE448_ENABLED
- #define MBEDTLS_ECP_NIST_OPTIM
- #define MBEDTLS_ECP_RESTARTABLE
- #define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED
- #define MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED
- #define MBEDTLS_PK_PARSE_EC_EXTENDED
- #define MBEDTLS_NO_PLATFORM_ENTROPY
- #define MBEDTLS_ENTROPY_FORCE_SHA256
- #define MBEDTLS_PKCS1_V15
- #define MBEDTLS_PKCS1_V21
- #define MBEDTLS_SSL_RENEGOTIATION
- #define MBEDTLS_SSL_PROTO_TLS1_2
- #define MBEDTLS_SSL_SERVER_NAME_INDICATION
- #define MBEDTLS_AES_C
- #define MBEDTLS_ASN1_PARSE_C
- #define MBEDTLS_ASN1_WRITE_C
- #define MBEDTLS_BASE64_C
- #define MBEDTLS_BIGNUM_C
- #define MBEDTLS_BLOWFISH_C
- #define MBEDTLS_CAMELLIA_C
- #define MBEDTLS_CERTS_C
- #define MBEDTLS_CHACHA20_C
- #define MBEDTLS_CHACHAPOLY_C
- #define MBEDTLS_CIPHER_C
- #define MBEDTLS_CTR_DRBG_C
- #define MBEDTLS_DEBUG_C
- #define MBEDTLS_DES_C
- #define MBEDTLS_DHM_C
- #define MBEDTLS_ECDH_C
- #define MBEDTLS_ECDSA_C
- #define MBEDTLS_ECP_C
- #define MBEDTLS_ENTROPY_C
- #define MBEDTLS_GCM_C
- #define MBEDTLS_HKDF_C
- #define MBEDTLS_HMAC_DRBG_C
- #define MBEDTLS_MD_C
- #define MBEDTLS_MD5_C
- #define MBEDTLS_NET_C
- #define MBEDTLS_OID_C
- #define MBEDTLS_PEM_PARSE_C
- #define MBEDTLS_PK_C
- #define MBEDTLS_PK_PARSE_C
- #define MBEDTLS_PK_WRITE_C
- #define MBEDTLS_PKCS5_C
- #define MBEDTLS_PLATFORM_C
- #define MBEDTLS_POLY1305_C
- #define MBEDTLS_RSA_C
- #define MBEDTLS_SHA1_C
- #define MBEDTLS_SHA256_C
- #define MBEDTLS_SHA512_C
- #define MBEDTLS_SSL_CLI_C
- #define MBEDTLS_SSL_TLS_C
- #define MBEDTLS_X509_USE_C
- #define MBEDTLS_X509_CRT_PARSE_C
- #define MBEDTLS_X509_CRL_PARSE_C
- #define MBEDTLS_X509_CSR_PARSE_C
- #define MBEDTLS_X509_CREATE_C
- #define MBEDTLS_X509_CRT_WRITE_C
- #define MBEDTLS_X509_CSR_WRITE_C
- #define MBEDTLS_MPI_WINDOW_SIZE 6
- #define MBEDTLS_MPI_MAX_SIZE 1024
- #define MBEDTLS_ECP_MAX_BITS 384
- #define MBEDTLS_ECP_WINDOW_SIZE 2
- #define MBEDTLS_ECP_FIXED_POINT_OPTIM 0
- #define MBEDTLS_ENTROPY_MAX_SOURCES 2
- #define MBEDTLS_PLATFORM_PRINTF_MACRO printf
- #define MBEDTLS_SSL_MAX_CONTENT_LEN 4096
- #define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA
- #define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE
- #if defined(TARGET_LIKE_MBED) && defined(YOTTA_CFG_MBEDTLS_TARGET_CONFIG_FILE)
- #include YOTTA_CFG_MBEDTLS_TARGET_CONFIG_FILE
- #endif
- #if defined(YOTTA_CFG_MBEDTLS_USER_CONFIG_FILE)
- #include YOTTA_CFG_MBEDTLS_USER_CONFIG_FILE
- #elif defined(MBEDTLS_USER_CONFIG_FILE)
- #include MBEDTLS_USER_CONFIG_FILE
- #endif
- #include "mbedtls/check_config.h"
- #endif /* MBEDTLS_CONFIG_H */
Zgodnie z utworzonym certyfikatem należy ustawić odpowiedni rodzaj algorytmów do wykorzystywania podczas uwierzytelniania i wykonywania połączenia:
- //Domyślne wartości
- #define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- //Zgodnie z certyfikatem parametr zmieniłem na
- #define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA
Następnie należy pamiętać o ustawieniu MBED_MPI_MAX_SIZE na 1024.
W celu uruchomienia wątku klienta należy uruchomić następujący wątek:
- osThreadDef(sslTask, StartSSLClientTask, osPriorityIdle, 0, 2048);
- sslTaskHandle = osThreadCreate(osThread(sslTask), NULL);
Jak wspomniałem wcześniej certyfikaty należy umieścić bezpośrednio w programie:
- const char mbedtls_client_certificate[] =
- "-----BEGIN CERTIFICATE-----\r\n"
- "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\r\n"
- "NIrzbeAz1g==\r\n"
- "-----END CERTIFICATE-----\r\n";
- const char mbedtls_certificate_key_pass[] = "test5432!";
- const char mbedtls_certificate_key[] =
- "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n"
- "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\r\n"
- "5e/ibghY4Lc8gQtcLXtOKQ==\r\n"
- "-----END ENCRYPTED PRIVATE KEY-----\r\n"
- const size_t mbedtls_client_certificate_len = strlen(mbedtls_client_certificate);
- const size_t mbedtls_certificate_key_len = sizeof(mbedtls_certificate_key);
- const size_t mbedtls_certificate_key_pass_len = strlen(mbedtls_certificate_key_pass);
Na początku wątku obsługującego połączenie SSL należy zainicjalizować zmienne RNG oraz wszystkie struktury obsługujące wykonywane połączenie:
- static void Initialize_SessionData(void)
- {
- mbedtls_net_init(&server_fd);
- mbedtls_ssl_init(&ssl);
- mbedtls_ssl_config_init(&conf);
- mbedtls_x509_crt_init(&cacert);
- mbedtls_ctr_drbg_init(&ctr_drbg);
- mbedtls_pk_init(&pkey);
- mbedtls_entropy_init(&entropy);
- }
- static int Drbg_Seed(void)
- {
- int opStatus = 0;
- if((opStatus = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*) pers, strlen(pers))) != 0) {
- mbedtls_printf(" failed\r\n ! mbedtls_ctr_drbg_seed returned %d\r\n ", opStatus);
- return opStatus;
- }
- return opStatus;
- }
Następnie obsługa certyfikatu:
- ret = mbedtls_pk_parse_key( &pkey,
- (const unsigned char*)mbedtls_certificate_key,
- mbedtls_certificate_key_len,
- (const unsigned char*)mbedtls_certificate_key_pass,
- mbedtls_certificate_key_pass_len);
- if(ret < 0) {
- mbedtls_printf("mbedtls_pk_parse_key failed returned -0x%x\r\n \r\n ", (unsigned int) -ret);
- }
- ret = mbedtls_x509_crt_parse(&cacert,
- (const unsigned char*)mbedtls_client_certificate,
- mbedtls_client_certificate_len + 1);
- if(ret < 0)
- {
- mbedtls_printf("mbedtls_x509_crt_parse returned -0x%x\r\n \r\n ", (unsigned int) -ret);
- goto exit;
- }
- ret = mbedtls_ssl_conf_own_cert(&conf, &cacert, &pkey);
- if(ret < 0) {
- mbedtls_printf("mbedtls_ssl_conf_own_cert returned -0x%x\r\n \r\n ", (unsigned int) -ret);
- }
Na samym początku należy wprowadzić klucz do certyfikatu, następnie przechodzę do obsługi certyfikatu.
- static int InitializeCertificate(void)
- {
- int opStatus = 0;
- fflush( stdout);
- opStatus = mbedtls_pk_parse_key( &pkey,
- (const unsigned char*)mbedtls_certificate_key,
- mbedtls_certificate_key_len,
- (const unsigned char*)mbedtls_certificate_key_pass,
- mbedtls_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 opStatus;
- }
- opStatus = mbedtls_x509_crt_parse(&cacert, (const unsigned char*)mbedtls_client_certificate, mbedtls_client_certificate_len + 1);
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_x509_crt_parse returned -0x%x\r\n \r\n ", (unsigned int) -opStatus);
- return opStatus;
- }
- opStatus = mbedtls_ssl_conf_own_cert(&conf, &cacert, &pkey);
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_ssl_conf_own_cert returned -0x%x\r\n \r\n ", (unsigned int) -opStatus);
- return opStatus;
- }
- return opStatus;
- }
Następny element oczekuje na uzyskanie połączenia:
- static void ValidateIpAddress(void)
- {
- while(1)
- {
- if(gnetif.ip_addr.addr == 0 || gnetif.netmask.addr == 0 || gnetif.gw.addr == 0)
- {
- osDelay(1000);
- continue;
- }
- else
- {
- printf("DHCP/Static IP O.K.\r\n ");
- break;
- }
- }
- }
Następnie przechodzę do nawiązania połączenia po TCP:
- static void ConnectToServerTcp(void)
- {
- int opStatus = 0;
- while(1)
- {
- if((opStatus = mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) == 0)
- {
- mbedtls_printf("mbedtls_net_connect OK\r\n ");
- break;
- }
- else
- {
- mbedtls_printf(" failed\r\n ! mbedtls_net_connect returned %d\r\n \r\n ", opStatus);
- osDelay(100);
- }
- }
- }
Kolejnym elementem jest załadowanie domyślnej konfiguracji dla biblioteki mbedtls:
- static uint8_t LoadDefaultConfiguration(void)
- {
- int opStatus = 0;
- if((opStatus = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_config_defaults returned %d\r\n \r\n ", opStatus);
- return opStatus;
- }
- return opStatus;
- }
Konfiguracja połączenia SSL:
- static uint8_t ConnectionSSL_Configuration(void)
- {
- int opStatus = 0;
- mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
- mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
- mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
- mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);
- if((opStatus = mbedtls_ssl_setup(&ssl, &conf)) != 0)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_setup returned %d\r\n \r\n ", opStatus);
- return opStatus;
- }
- return opStatus;
- }
Ustawienie nazwy serwera, do którego będziemy się łączyć. Ta informacja znajduje się w przygotowywanym certyfikacie. Drugim elementem jest ustawienie wywołań zwrotnych dla nadawania, odbierania.
- static uint8_t SetHostname_BioCallbacks(void)
- {
- int opStatus = 0;
- if((opStatus = mbedtls_ssl_set_hostname(&ssl, "server_name")) != 0)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_set_hostname returned %d\r\n \r\n ", opStatus);
- return opStatus;
- }
- mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
- return opStatus;
- }
W następnym kroku rozpoczynam procedurę handshake gdzie są negocjowane parametry połączenia pomiędzy klientem oraz serwerem.
- static uint8_t Handshake(void)
- {
- int opStatus = 0;
- while((opStatus = mbedtls_ssl_handshake(&ssl)) != 0)
- {
- if(opStatus != MBEDTLS_ERR_SSL_WANT_READ && opStatus != MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_handshake returned -0x%x\r\n \r\n ", opStatus);
- return opStatus;
- }
- }
- return opStatus;
- }
Lista kroków zdefiniowana podczas procedury komunikacji wykonywana jest w funkcji mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl);
- switch( ssl->state )
- {
- case MBEDTLS_SSL_HELLO_REQUEST:
- ssl->state = MBEDTLS_SSL_CLIENT_HELLO;
- break;
- case MBEDTLS_SSL_CLIENT_HELLO:
- ret = ssl_write_client_hello( ssl );
- break;
- case MBEDTLS_SSL_SERVER_HELLO:
- ret = ssl_parse_server_hello( ssl );
- break;
- case MBEDTLS_SSL_SERVER_CERTIFICATE:
- ret = mbedtls_ssl_parse_certificate( ssl );
- break;
- case MBEDTLS_SSL_SERVER_KEY_EXCHANGE:
- ret = ssl_parse_server_key_exchange( ssl );
- break;
- case MBEDTLS_SSL_CERTIFICATE_REQUEST:
- ret = ssl_parse_certificate_request( ssl );
- break;
- case MBEDTLS_SSL_SERVER_HELLO_DONE:
- ret = ssl_parse_server_hello_done( ssl );
- break;
- case MBEDTLS_SSL_CLIENT_CERTIFICATE:
- ret = mbedtls_ssl_write_certificate( ssl );
- break;
- case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE:
- ret = ssl_write_client_key_exchange( ssl );
- break;
- case MBEDTLS_SSL_CERTIFICATE_VERIFY:
- ret = ssl_write_certificate_verify( ssl );
- break;
- case MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC:
- ret = mbedtls_ssl_write_change_cipher_spec( ssl );
- break;
- case MBEDTLS_SSL_CLIENT_FINISHED:
- ret = mbedtls_ssl_write_finished( ssl );
- break;
- #if defined(MBEDTLS_SSL_SESSION_TICKETS)
- case MBEDTLS_SSL_SERVER_NEW_SESSION_TICKET:
- ret = ssl_parse_new_session_ticket( ssl );
- break;
- #endif
- case MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC:
- ret = mbedtls_ssl_parse_change_cipher_spec( ssl );
- break;
- case MBEDTLS_SSL_SERVER_FINISHED:
- ret = mbedtls_ssl_parse_finished( ssl );
- break;
- case MBEDTLS_SSL_FLUSH_BUFFERS:
- ssl->state = MBEDTLS_SSL_HANDSHAKE_WRAPUP;
- break;
- case MBEDTLS_SSL_HANDSHAKE_WRAPUP:
- mbedtls_ssl_handshake_wrapup( ssl );
- break;
- default:
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) );
- return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
- }
Po poprawnym wykonaniu połączeniu następuje wysłanie testowej wiadomości do serwera.
- static int SendTestMsg(void)
- {
- int opStatus = 0;
- int msgLenToSend = 0;
- //buf is global buffer
- msgLenToSend = sprintf((char*)buf, "testmsg,123,76\r");
- while((opStatus = mbedtls_ssl_write(&ssl, buf, msgLenToSend)) <= 0)
- {
- if(opStatus != MBEDTLS_ERR_SSL_WANT_READ && opStatus != MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_write returned %d\n\n", opStatus);
- return -1;
- }
- }
- return msgLenToSend;
- }
Kolejnym elementem jest odbieranie i wysyłanie danych:
- mbedtls_printf("\r\n Read/write data from/to server:\r\n ");
- fflush( stdout);
- do {
- if(ReadWriteData() < 0) { break; }
- } while(1);
- static int ReadWriteData(void)
- {
- int msgLen = 0;
- int opStatus = 0;
- msgLen = sizeof(buf) - 1;
- memset(buf, 0, sizeof(buf));
- opStatus = mbedtls_ssl_read(&ssl, buf, msgLen);
- if(opStatus == MBEDTLS_ERR_SSL_WANT_READ || opStatus == MBEDTLS_ERR_SSL_WANT_WRITE) {
- mbedtls_printf("write/read ok");
- fflush( stdout);
- }
- if(opStatus == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
- return -1;
- }
- if(opStatus < 0) {
- mbedtls_printf("ERROR - mbedtls_ssl_read returned %d\r\n \r\n ", opStatus);
- return -1;
- }
- if(opStatus == 0) {
- mbedtls_printf("ret == 0 return %d\r\n \r\n ", opStatus);
- return -1;
- }
- if(opStatus > 0) {
- msgLen = opStatus;
- mbedtls_printf(" %d bytes read\n\n %s", msgLen, (char*)buf);
- fflush( stdout); //Bez fflush wysyla z bufora wczesniej odebrana wiadomosc.
- if(msgLen > 0) {
- while((opStatus = mbedtls_ssl_write(&ssl, buf, msgLen)) <= 0)
- {
- if(opStatus != MBEDTLS_ERR_SSL_WANT_READ && opStatus != MBEDTLS_ERR_SSL_WANT_WRITE)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_write returned %d\r\n \r\n ", opStatus);
- }
- }
- }
- }
- return 0;
- }
W celu poprawnego otrzymywania danych wysyłanych na UART podczas procedury wykonywania połączenia oraz sprawdzania otrzymywanych informacji należy zastosować funkcję fflush. Jest ona konieczna ponieważ wiadomości przesyłane przez mbedtls_printf korzysta z funkcji printf. FFLUSH pozwala na opróżnienie bufora z danymi gromadzonymi przez funkcję printf.
Dodatkowo w celu ograniczenia zużycia pamięci oraz przyśpieszenia działania programu, jeśli będzie to konieczne, należy zastąpić funkcję printf własnoręcznie przygotowaną funkcją działającą bezpośrednio na interfejsie UART.
Cała funkcja nawiązująca połączenie po SSL wygląda następująco:
- void StartSSLClientTask(void const *argument)
- {
- int ret = 1;
- #if defined(MBEDTLS_DEBUG_C)
- mbedtls_debug_set_threshold(DEBUG_LEVEL);
- #endif
- Initialize_SessionData();
- ret = Drbg_Seed();
- if(ret != 0) {
- mbedtls_printf("ERROR\r\n");
- goto exit;
- }
- ret = InitializeCertificate();
- if(ret != 0) {
- mbedtls_printf("ERROR\r\n");
- goto exit;
- }
- ValidateIpAddress();
- while(1)
- {
- /* Start the connection */
- mbedtls_printf(" . Connecting to tcp/%s/%s...\r\n ", SERVER_NAME, SERVER_PORT);
- fflush( stdout);
- ConnectToServerTcp();
- mbedtls_printf(" . Setting up the SSL/TLS structure...\r\n ");
- fflush( stdout);
- if(LoadDefaultConfiguration() != 0) { goto exit; }
- if(ConnectionSSL_Configuration() != 0) { goto exit; }
- if(SetHostname_BioCallbacks() != 0) { goto exit; }
- SetHostname_BioCallbacks();
- if(Handshake() != 0) { goto exit; }
- VarifyCertificate();
- mbedtls_printf("\r\n Write data to server:\r\n ");
- fflush( stdout);
- if(SendTestMsg() < 0) { goto exit; }
- mbedtls_printf("\r\n Read/write data from/to server:\r\n ");
- fflush( stdout);
- do {
- if(ReadWriteData() < 0) { break; }
- } while(1);
- /* Close session */
- do {
- mbedtls_ssl_close_notify(&ssl);
- } while(ret == MBEDTLS_ERR_SSL_WANT_WRITE);
- ret = mbedtls_ssl_session_reset(&ssl);
- if(ret != 0)
- {
- mbedtls_printf("ERROR - mbedtls_ssl_session_reset returned %d\r\n \r\n ", ret);
- break;
- }
- mbedtls_net_free(&server_fd);
- mbedtls_printf("xPortGetFreeHeapSize: %d\r\n \r\n ", xPortGetFreeHeapSize());
- osDelay(5000);
- }
- exit:
- mbedtls_printf("ERROR on mbedtls...\r\n \r\n ");
- ClearData();
- #if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C)
- mbedtls_memory_buffer_alloc_free();
- #endif
- vTaskDelete(NULL);
- }
Program będzie generował błąd x509_verify_cert() -0x2700. Po ustawieniu flag debugowania na 4 okaże się, że błąd wynika bezpośrednio w funkcji:
- int mbedtls_x509_crt_verify_restartable( mbedtls_x509_crt *crt,
- mbedtls_x509_crt *trust_ca,
- mbedtls_x509_crl *ca_crl,
- const mbedtls_x509_crt_profile *profile,
- const char *cn, uint32_t *flags,
- int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *),
- void *p_vrfy,
- mbedtls_x509_crt_restart_ctx *rs_ctx )
- {
- int ret;
- mbedtls_pk_type_t pk_type;
- mbedtls_x509_crt_verify_chain ver_chain;
- uint32_t ee_flags;
- *flags = 0;
- ee_flags = 0;
- x509_crt_verify_chain_reset( &ver_chain );
- if( profile == NULL )
- {
- ret = MBEDTLS_ERR_X509_BAD_INPUT_DATA;
- goto exit;
- }
- /* check name if requested */
- if( cn != NULL )
- x509_crt_verify_name( crt, cn, &ee_flags );
- /* Check the type and size of the key */
- pk_type = mbedtls_pk_get_type( &crt->pk );
- if( x509_profile_check_pk_alg( profile, pk_type ) != 0 )
- ee_flags |= MBEDTLS_X509_BADCERT_BAD_PK;
- if( x509_profile_check_key( profile, &crt->pk ) != 0 )
- ee_flags |= MBEDTLS_X509_BADCERT_BAD_KEY;
- /* Check the chain */
- ret = x509_crt_verify_chain( crt, trust_ca, ca_crl, profile,
- &ver_chain, rs_ctx );
- if( ret != 0 )
- goto exit;
- /* Merge end-entity flags */
- ver_chain.items[0].flags |= ee_flags;
- /* Build final flags, calling callback on the way if any */
- ret = x509_crt_merge_flags_with_cb( flags, &ver_chain, f_vrfy, p_vrfy );
- exit:
- #if defined(MBEDTLS_ECDSA_C) && defined(MBEDTLS_ECP_RESTARTABLE)
- if( rs_ctx != NULL && ret != MBEDTLS_ERR_ECP_IN_PROGRESS )
- mbedtls_x509_crt_restart_free( rs_ctx );
- #endif
- /* prevent misuse of the vrfy callback - VERIFY_FAILED would be ignored by
- * the SSL module for authmode optional, but non-zero return from the
- * callback means a fatal error so it shouldn't be ignored */
- if( ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED )
- ret = MBEDTLS_ERR_X509_FATAL_ERROR;
- if( ret != 0 )
- {
- *flags = (uint32_t) -1;
- return( ret );
- }
- if( *flags != 0 )
- return( MBEDTLS_ERR_X509_CERT_VERIFY_FAILED );
- return( 0 );
- }
Przedostatnia instrukcja warunkowa ustawia wartość zmiennej flags na 8. Oznacza ona, że certyfikat nie jest podpisany przez odpowiednie instytucje. Jest to całkowicie normalny błąd w przypadku korzystania z własnoręcznie wygenerowanych certyfikatów.
Po wysłaniu czasem jednego czasem kilku pakietów pojawia się błąd:
../Middlewares/Third_Party/mbedTLS/library/ssl_tls.c:1794: => decrypt buf
../Middlewares/Third_Party/mbedTLS/library/ssl_tls.c:4256: ssl_decrypt_buf() returned -29056 (-0x7180)
Może on oczywiście wynikać z kilku czynników. W moim przypadku wynikał on z ustawienia za małego bufora RX. Zmiana parametru ETH_RX_BUFFER_SIZE na 2144 rozwiązała problem.
Pełny projekt wraz z certyfikatami można pobrać z dysku Google pod tym linkiem.