Skip to content

Certificate-verification TLS path is unconditionally compiled into open()'s future (~10 KB static RAM even for TlsVerify::None/Psk-only users) #139

Description

@ft-filancore

In the spirit of #127, there is a TLS-related binary size issue we encounter because we use reqwless on a resource-constrained device with TLS but without certificate verification (for a few setup-internal reasons). Unfortunately, the certificate-verification path is expensive and always compiled into the binary accounting for quite a sizeable chunk of our resulting firmware.

The key point is that reqwless' embedded-tls backend always carries a CertVerifier<'_, Aes128GcmSha256, NoClock, 4096> and a buffer for OwnedCertificate<4096> even if never used. Proposal: gate the certificate-verification path behind an off-by-default feature (e.g. client-cert) so consumers that don't verify certificates don't pay for it.

Measured concretely on an STM32F767 (Cortex-M7, thumbv7em) firmware that uses reqwless + embedded-tls with TlsVerify::None:

  • ~+10 KB static RAM (.bss), entirely attributable to reqwless' request future growing through the certificate branch (it is stored in a statically-allocated task pool)
  • ~+30 KB flash (the p256 / certificate-parsing / signature-verification code that is always linked due to certificate handling)

For small MCUs this can be the difference between fitting a RAM budget and not. The baseline against this was measured was by introducing an off-by-default client-cert feature and gate the certificate-verification path with it. Items to gate (line numbers at the time of opening this issue):

Item src/client.rs
cert-only imports (CertVerifier, SignatureScheme, TlsVerifier; p256::ecdsa::*; rand_core::CryptoRngCore) import block (~L12)
struct Provider<'a> + impl CryptoProvider for Provider<'a> L60, L67
TlsVerify::Certificate enum variant L106
the TlsVerify::Certificate => { .. } match arm in open() L226

Sketch (#[cfg(feature = "embedded-tls")]#[cfg(all(feature = "embedded-tls", feature = "client-cert"))] on the cert-only items, #[cfg(feature = "client-cert")] on the enum variant):

 [features]
 default = ["embedded-tls"]
+client-cert = []
-#[cfg(feature = "embedded-tls")]
+#[cfg(all(feature = "embedded-tls", feature = "client-cert"))]
 struct Provider<'a> { rng: .., verifier: CertVerifier<'a, Aes128GcmSha256, NoClock, 4096>, .. }

 pub enum TlsVerify<'a> {
     None,
     Psk { .. },
+    #[cfg(feature = "client-cert")]
     Certificate { ca: &'a [u8], cert: Option<&'a [u8]>, key: Option<&'a [u8]> },
 }

                     TlsVerify::Psk { .. } => { .. }
+                    #[cfg(all(feature = "embedded-tls", feature = "client-cert"))]
                     TlsVerify::Certificate { ca, cert, key } => { .. }

If you are open to such a change, I'd be happy to open a PR for it.

Disclaimer: snippet suggestion generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions