esp-rs / esp-idf-svc

Type-Safe Rust Wrappers for various ESP-IDF services (WiFi, Network, Httpd, Logging, etc.)
https://docs.esp-rs.org/esp-idf-svc/
Apache License 2.0
330 stars 183 forks source link

TLS/HTTPS error: No server verification option set in esp_tls_cfg_t structure #477

Closed tirithen closed 2 months ago

tirithen commented 2 months ago

I'm trying to make an HTTPS/TLS GET request with this crate and I get stuck on missing TLS setup.

I get the error: E (4482) esp-tls-mbedtls: No server verification option set in esp_tls_cfg_t structure. Check esp_tls API reference

I (3422) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (4402) esp_netif_handlers: sta ip: 192.168.1.14, mask: 255.255.255.0, gw: 192.168.1.1
I (4402) e_calendar: WiFi netif up
I (4402) e_calendar: Fetching data...
E (4482) esp-tls-mbedtls: No server verification option set in esp_tls_cfg_t structure. Check esp_tls API reference
E (4482) esp-tls-mbedtls: Failed to set client configurations, returned [0x8017] (ESP_ERR_MBEDTLS_SSL_SETUP_FAILED)
E (4492) esp-tls: create_ssl_handle failed
E (4492) esp-tls: Failed to open new connection
E (4502) transport_base: Failed to open a new connection
E (4512) HTTP_CLIENT: Connection failed, sock < 0
I (4512) wifi:state: run -> init (0)
I (4512) wifi:pm stop, total sleep time: 729188 us / 1124549 us

I (4522) wifi:<ba-del>idx:0, tid:0
I (4522) wifi:new:<13,0>, old:<13,2>, ap:<255,255>, sta:<13,2>, prof:1
E (4532) wifi:NAN WiFi stop
I (4572) wifi:flush txq
I (4572) wifi:stop sw txq
I (4572) wifi:lmac stop hw txq
I (4572) esp_idf_svc::wifi: EspWifi dropped
I (4572) esp_idf_svc::netif: Dropped
I (4572) esp_idf_svc::netif: Dropped
I (4582) wifi:Deinit lldesc rx mblock:10
I (4592) esp_idf_svc::nvs: NvsDefault dropped
I (4592) esp_idf_svc::eventloop: System event loop dropped
Error: ESP_ERR_HTTP_CONNECT
I (4592) main_task: Returned from app_main()

I have a hard time finding the APIs to enable TLS, even an insecure setup. It seems like the C struct esp_tls_cfg_t needs to be setup, but I'm not sure how to set it up using the safe APIs. I suppose that HTTPClientConfiguration has something to do with it.

The TLS example seem great for a raw TCP connection, but I find it hard to apply to making a HTTPS call. using HttpClient.

This is my code so far:

use anyhow::anyhow;
use embedded_svc::{
    http::{client::Client as HttpClient, Method},
    utils::io,
    wifi::{AuthMethod, ClientConfiguration, Configuration as WiFiConfiguration},
};
use esp_idf_svc::{
    eventloop::EspSystemEventLoop,
    hal::prelude::Peripherals,
    http::client::{Configuration as HTTPClientConfiguration, EspHttpConnection},
    log::EspLogger,
    nvs::EspDefaultNvsPartition,
    sys::{esp_tls_cfg, esp_tls_cfg_t, esp_tls_init_global_ca_store},
    wifi::{BlockingWifi, EspWifi},
};
use log::{error, info};

const WIFI_SSID: &str = env!("WIFI_SSID");
const WIFI_PASSWORD: &str = env!("WIFI_PASSWORD");
const URL: &str = env!("URL");

fn main() -> anyhow::Result<()> {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    info!("Starting...");

    let peripherals = Peripherals::take()?;
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    connect_wifi(&mut wifi)?;

    let http_client_config = HTTPClientConfiguration::default();
    let mut http_client = HttpClient::wrap(EspHttpConnection::new(&http_client_config)?);

    let data = fetch_data(&mut http_client)?;
    info!("{data}");

    Ok(())
}

fn fetch_data(http_client: &mut HttpClient<EspHttpConnection>) -> anyhow::Result<String> {
    info!("Fetching data...");

    let url = URL;

    let request = http_client.get(url)?;
    let mut response = request.submit()?;

    let status = response.status();
    if status != 200 {
        return Err(anyhow!("Expected HTTP response status 200, got {status}"));
    }

    info!("Got 200 response");

    // let mut buffer = [0u8; 100 * 1024];
    // let bytes_read = io::try_read_full(&mut response, &mut buffer).map_err(|e| e.0)?;

    // Drain the remaining response bytes
    // while response.read(&mut buffer)? > 0 {}

    // let body = std::str::from_utf8(&buffer[0..bytes_read])?;

    // Ok(body.to_string())
    Ok(status.to_string())
}

fn connect_wifi(wifi: &mut BlockingWifi<EspWifi<'static>>) -> anyhow::Result<()> {
    let wifi_configuration: WiFiConfiguration = WiFiConfiguration::Client(ClientConfiguration {
        ssid: WIFI_SSID.try_into().unwrap(),
        bssid: None,
        auth_method: AuthMethod::WPA2Personal,
        password: WIFI_PASSWORD.try_into().unwrap(),
        channel: None,
        ..Default::default()
    });

    wifi.set_configuration(&wifi_configuration)?;

    wifi.start()?;
    info!("WiFi started");

    wifi.connect()?;
    info!("WiFi connected to {WIFI_SSID}");

    wifi.wait_netif_up()?;
    info!("WiFi netif up");

    Ok(())
}
ivmarkov commented 2 months ago

Replace this line in your example:

let http_client_config = HTTPClientConfiguration::default();

with:

let http_client_config = HTTPClientConfiguration {
    use_global_ca_store: true,
    crt_bundle_attach: esp_idf_svc::sys::esp_crt_bundle_attach, // Not sure about this one, but I think it was necessary too
    ..HTTPClientConfiguration::default()
}

Then in your sdkconfig.defaults do add:

CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y

... and also make sure you use a URL that starts with https://

tirithen commented 2 months ago

@ivmarkov Thank you! These steps solved the TLS issue. Possibly an example like that could be added to examples/ to help anyone else trying to make HTTP calls with this crate. After all, most of the web requires TLS at this point.

I can create a pull request based on the http.rs example when in a bit.

ivmarkov commented 2 months ago

I'll close this now, but yes - if you could contribute to the HTTP client example - even if just a few comments what needs to be set for the TLS code-path to work, that would be great!

tirithen commented 2 months ago

@ivmarkov I tested again with my esp32 (using build target xtensa-esp32-espidf) and it seems like there where no issue after adding crt_bundle_attach: Some(esp_idf_svc::sys::esp_crt_bundle_attach) as already suggested in the example https://github.com/esp-rs/esp-idf-svc/blob/master/examples/http_client.rs#L42

I got it working also without adding the lines to the sdkconfig.defaults file. So the example should actually be good as is. I must have missed the existing HTTPS comment before.

Thanks anyhow for the help and a good crate.

ivmarkov commented 2 months ago

I got it working also without adding the lines to the sdkconfig.defaults file. So the example should actually be good as is. I must have missed the existing HTTPS comment before.

Keep in mind that the lines are added for you when compiling the examples. But... you might be right that they are not necessary, if they are y by default (I don't remember that, but can be easily checked in the ESP IDF docu).