Skip to content

Commit

Permalink
fix(modem): Fix ota-test to verify server cert and CN
Browse files Browse the repository at this point in the history
  • Loading branch information
david-cermak committed Nov 27, 2023
1 parent 1bdf25c commit e281503
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Tls {
int read(unsigned char *buf, size_t len);
[[nodiscard]] bool set_own_cert(const_buf crt, const_buf key);
[[nodiscard]] bool set_ca_cert(const_buf crt);
bool set_hostname(const char *name);
virtual int send(const unsigned char *buf, size_t len) = 0;
virtual int recv(unsigned char *buf, size_t len) = 0;
size_t get_available_bytes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ bool Tls::set_ca_cert(const_buf crt)
return true;
}

bool Tls::set_hostname(const char *name)
{
int ret = mbedtls_ssl_set_hostname(&ssl_, name);
if (ret < 0) {
print_error("mbedtls_ssl_set_hostname", ret);
return false;
}
return true;
}

Tls::Tls()
{
mbedtls_x509_crt_init(&public_cert_);
Expand Down
6 changes: 6 additions & 0 deletions components/esp_modem/test/target_ota/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem loc
```
* MQTT broker: Running mosquitto in the default config is enough, configuring the broker's URL to the local PPP address: `config.broker.address.uri = "mqtt://192.168.11.1";`
* HTTP server: Need to support HTTP/1.1 (to support ranges). You can use the script `http_server.py` and configure the OTA endpoint as `"https://192.168.11.1:1234/esp32.bin"`

## Potential issues

When running this test it is expected to experience some buffer overflows or connection interruption.
The modem library should recover from these failure modes and continue and complete OTA update.
These issues are expected, since UART ISR is deliberately not placed into IRAM in the test configuration to exhibit some minor communication glitches.
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ bool manual_ota::begin()
ESP_LOGE(TAG, "Invalid state");
return false;
}
status = state::INIT;
esp_transport_handle_t tcp = esp_transport_tcp_init();
ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_);

esp_http_client_config_t config = { };
config.skip_cert_common_name_check = true;
config.url = uri_;
config.transport = ssl_;
const esp_partition_t *configured = esp_ota_get_boot_partition();
const esp_partition_t *running = esp_ota_get_running_partition();

Expand All @@ -36,38 +28,36 @@ bool manual_ota::begin()
return false;
}

http_ = esp_http_client_init(&config);
if (http_ == nullptr) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
return false;
}
esp_http_client_set_method(http_, HTTP_METHOD_HEAD);
esp_err_t err = esp_http_client_perform(http_);
if (err == ESP_OK) {
int http_status = esp_http_client_get_status_code(http_);
if (http_status != HttpStatus_Ok) {
ESP_LOGE(TAG, "Received incorrect http status %d", http_status);
return false;
status = state::INIT;
max_buffer_size_ = size_ * 1024;
if (mode_ == mode::BATCH) {
esp_transport_handle_t tcp = esp_transport_tcp_init();
ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_);
http_.config_.transport = ssl_;
if (!esp_transport_batch_set_ca_cert(ssl_, http_.config_.cert_pem, 0)) {
return fail();
}
} else {
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
return false;
if (!esp_transport_batch_set_cn(ssl_, common_name_)) {
return fail();
}

}

if (!http_.init()) {
return fail();
}

image_length_ = http_.get_image_len();
if (image_length_ <= 0) {
return fail();
}
image_length_ = esp_http_client_get_content_length(http_);
ESP_LOGI(TAG, "image_length = %lld", image_length_);
esp_http_client_close(http_);

if (image_length_ > size_) {
char *header_val = nullptr;
asprintf(&header_val, "bytes=0-%d", max_buffer_size_ - 1);
if (header_val == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
return false;
if (!http_.set_range(0, max_buffer_size_ - 1)) {
return fail();
}
esp_http_client_set_header(http_, "Range", header_val);
free(header_val);
}
esp_http_client_set_method(http_, HTTP_METHOD_GET);
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);

partition_ = esp_ota_get_next_update_partition(nullptr);
if (partition_ == nullptr) {
Expand All @@ -89,37 +79,39 @@ bool manual_ota::perform()
ESP_LOGE(TAG, "Invalid state");
return false;
}
esp_err_t err = esp_http_client_open(http_, 0);
esp_err_t err = esp_http_client_open(http_.handle_, 0);
if (err != ESP_OK) {
if (image_length_ == file_length_) {
status = state::END;
return false;
}

esp_http_client_close(http_);
esp_http_client_close(http_.handle_);
ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
if (reconnect_attempts_++ < max_reconnect_attempts_) {
if (prepare_reconnect()) {
return true; // will retry in the next iteration
}
return true; // will retry in the next iteration
}
return fail_cleanup();
return fail();
}
esp_http_client_fetch_headers(http_);
esp_http_client_fetch_headers(http_.handle_);

int batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000);
if (batch_len < 0) {
ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!");
return fail_cleanup();
int batch_len = max_buffer_size_;
if (mode_ == mode::BATCH) {
batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000);
if (batch_len < 0) {
ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!");
fail();
return false;
}
}

int data_read = esp_http_client_read(http_, buffer_.data(), batch_len);
int data_read = esp_http_client_read(http_.handle_, buffer_.data(), batch_len);

if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
return fail_cleanup();
return fail();
} else if (data_read > 0) {
esp_http_client_close(http_);
esp_http_client_close(http_.handle_);

if (status == state::IMAGE_CHECK) {
esp_app_desc_t new_app_info;
Expand All @@ -146,28 +138,26 @@ bool manual_ota::perform()
ESP_LOGW(TAG, "New version is the same as invalid version.");
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
return fail_cleanup();
return fail();
}
}

status = state::START;
err = esp_ota_begin(partition_, OTA_WITH_SEQUENTIAL_WRITES, &update_handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
esp_ota_abort(update_handle_);
return fail_cleanup();
return fail();
}
ota_begin = true;
ESP_LOGI(TAG, "esp_ota_begin succeeded");
} else {
ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor");
esp_ota_abort(update_handle_);
return fail_cleanup();
return fail();
}
}
err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read);
if (err != ESP_OK) {
esp_ota_abort(update_handle_);
return fail_cleanup();
return fail();
}
file_length_ += data_read;
ESP_LOGI(TAG, "Written image length %d", file_length_);
Expand All @@ -178,28 +168,22 @@ bool manual_ota::perform()
}

if (!prepare_reconnect()) {
esp_ota_abort(update_handle_);
return fail_cleanup();
return fail();
}

} else if (data_read == 0) {
if (file_length_ == 0) {
// Try to handle possible HTTP redirections
int status_code = esp_http_client_get_status_code(http_);
ESP_LOGW(TAG, "Status code: %d", status_code);
err = esp_http_client_set_redirection(http_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "URL redirection Failed");
esp_ota_abort(update_handle_);
return fail_cleanup();
if (!http_.handle_redirects()) {
return fail();
}

err = esp_http_client_open(http_, 0);
err = esp_http_client_open(http_.handle_, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return fail_cleanup();
return fail();
}
esp_http_client_fetch_headers(http_);
esp_http_client_fetch_headers(http_.handle_);
}
}

Expand All @@ -208,36 +192,23 @@ bool manual_ota::perform()

bool manual_ota::prepare_reconnect()
{
esp_http_client_set_method(http_, HTTP_METHOD_GET);
char *header_val = nullptr;
if ((image_length_ - file_length_) > max_buffer_size_) {
asprintf(&header_val, "bytes=%d-%d", file_length_, (file_length_ + max_buffer_size_ - 1));
} else {
asprintf(&header_val, "bytes=%d-", file_length_);
}
if (header_val == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
return false;
}
esp_http_client_set_header(http_, "Range", header_val);
free(header_val);
return true;
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);
return http_.set_range(file_length_,
(image_length_ - file_length_) > max_buffer_size_ ? (file_length_ + max_buffer_size_ - 1) : 0);
}

bool manual_ota::fail_cleanup()
bool manual_ota::fail()
{
esp_http_client_close(http_);
esp_http_client_cleanup(http_);
status = state::FAIL;
return false;
}

bool manual_ota::end()
{
if (status == state::END) {
if (!esp_http_client_is_complete_data_received(http_)) {
if (!http_.is_data_complete()) {
ESP_LOGE(TAG, "Error in receiving complete file");
return fail_cleanup();
return fail();
}
esp_err_t err = esp_ota_end(update_handle_);
if (err != ESP_OK) {
Expand All @@ -246,14 +217,97 @@ bool manual_ota::end()
} else {
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
}
return fail_cleanup();
return fail();
}
ota_begin = false;
err = esp_ota_set_boot_partition(partition_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
return fail_cleanup();
return fail();
}
return true;
}
return false;
}

manual_ota::~manual_ota()
{
if (ota_begin) {
esp_ota_abort(update_handle_);
}
}

bool manual_ota::http_client::handle_redirects()
{
int status_code = esp_http_client_get_status_code(handle_);
ESP_LOGW(TAG, "Status code: %d", status_code);
esp_err_t err = esp_http_client_set_redirection(handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "URL redirection Failed");
return false;
}

err = esp_http_client_open(handle_, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return false;
}
esp_http_client_fetch_headers(handle_);
return true;
}

bool manual_ota::http_client::set_range(size_t from, size_t to)
{
char *header_val = nullptr;
if (to != 0) {
asprintf(&header_val, "bytes=%d-%d", from, to);
} else {
asprintf(&header_val, "bytes=%d-", from);
}
if (header_val == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
return false;
}
esp_http_client_set_header(handle_, "Range", header_val);
free(header_val);
return true;
}

bool manual_ota::http_client::is_data_complete()
{
return esp_http_client_is_complete_data_received(handle_);
}

manual_ota::http_client::~http_client()
{
if (handle_) {
esp_http_client_close(handle_);
esp_http_client_cleanup(handle_);
}
}

bool manual_ota::http_client::init()
{
handle_ = esp_http_client_init(&config_);
return handle_ != nullptr;
}

int64_t manual_ota::http_client::get_image_len()
{
esp_http_client_set_method(handle_, HTTP_METHOD_HEAD);
esp_err_t err = esp_http_client_perform(handle_);
if (err == ESP_OK) {
int http_status = esp_http_client_get_status_code(handle_);
if (http_status != HttpStatus_Ok) {
ESP_LOGE(TAG, "Received incorrect http status %d", http_status);
return -1;
}
} else {
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
return -1;
}
int64_t image_length = esp_http_client_get_content_length(handle_);
ESP_LOGI(TAG, "image_length = %lld", image_length);
esp_http_client_close(handle_);
return image_length;
}
Loading

0 comments on commit e281503

Please sign in to comment.