pw_tls_client#

Pigweed AI summary: The pw_tls_client module provides a facade for establishing TLS sessions over arbitrary transports. It offers a class, pw::tls_client::Session, with Open(), Read(), Write(), and Close() methods for TLS communication. The module also provides mechanisms/APIs for users to specify sources of trust anchors, time, and entropy. The module is under construction and not ready for use, and the documentation is incomplete. The module requires the following dependencies: entropy, chromium verifier, date time, and crlset

This module provides a facade that defines the public APIs for establishing TLS sessions over arbitrary transports. Two options of backends, pw_tls_client_mbedtls and pw_tls_client_boringssl, which are based on BoringSSL and MbedTLS libraries, are under construction.

The facade provides a class pw::tls_client::Session with Open(), Read(), Write() and Close() methods for TLS communication. An instance is created by pw::tls_client::Session::Create method. The method takes a pw::tls_client::SessionOptions object, which is used to configure TLS connection options. The list of supported configurations currently include:

1. Host name of the target server. This will be used as the Server Name Indication(SNI) extension during TLS handshake.

2. User-implemented transport. The underlying transport for the TLS communication. It is an object that implements the interface of pw::stream::ReaderWriter.

The module will also provide mechanisms/APIs for users to specify sources of trust anchors, time and entropy. These are under construction.

Warning

This module is under construction, not ready for use, and the documentation is incomplete.

Prerequisites#

Pigweed AI summary: This module has several prerequisites that need to be met in order to function properly. These include an entropy source for generating random bytes, the use of a chromium verifier for certification verification if BoringSSL is used as the backend, a trustworthy source of wall clock time for checking expiration, and a CRLSet file for certificate revocation checks during TLS handshake. Downstream projects must provide these prerequisites at build time, and toolings are being developed for generating custom CRLSet files from user-provided certificate

This module requires the following dependencies:

1. Entropy#

Pigweed AI summary: TLS needs a source of entropy to generate random bytes. Users must implement a backend to the pw_tls_client:entropy facade to provide this source. If no backend is provided, the default is pw_tls_client:fake_entropy, which does nothing.

TLS requires an entropy source for generating random bytes. Users of this module should provide one by implementing a backend to the pw_tls_client:entropy facade. The backend defaults to pw_tls_client:fake_entropy that does nothing.

2. Chromium Verifier#

Pigweed AI summary: The BoringSSL backend uses the chromium verifier for certification verification. If a downstream project uses BoringSSL as the backend, the sources of the verifier need to be downloaded from the chromium sources in order to build. It is recommended to use pw_package for downloading a compatible and tested version of the verifier. The user should then follow instructions for setting the directory path to the downloaded repository.

BoringSSL backend uses chromium verifier for certication verification. If the downstream project uses BoringSSL as the backend, the sources of the verifier, which is part of the chorimum sources, needs to be downloaded in order for //third_party/chromium_verifier to build. It is recommended to use our support in pw_package for downloading compatible and tested version:

pw package install chromium_verifier

Then follow instruction for setting dir_pw_third_party_chromium_verifier to the path of the downloaded repo.

3. Date time#

Pigweed AI summary: This section discusses the importance of a trustworthy source of wall clock time for TLS communication and how it is specific to the TLS library in use. Common TLS libraries like BoringSSL and MbedTLS support the use of C APIs time() and getimtofday() for obtaining date time, and a facade target pw_tls_client:time is added to accommodate the use of these libraries. The backend target can be specified with variable pw_tls_client_TIME_BACKEND for GN builds, which defaults to the pw

TLS needs a trust-worthy source of wall clock time in order to check expiration. Provisioning of time source for TLS communication is very specific to the TLS library in use. However, common TLS libraires, such as BoringSSL and MbedTLS, support the use of C APIs time() and getimtofday() for obtaining date time. To accomodate the use of these libraries, a facade target pw_tls_client:time is added that wraps these APIs. For GN builds, specify the backend target with variable pw_tls_client_TIME_BACKEND. pw_tls_client_TIME_BACKEND defaults to the pw_tls_client::build_time backend that returns build time.

If downstream project chooses to use other TLS libraires that handle time source differently, then it needs to be investigated separately.

4. CRLSet#

Pigweed AI summary: The CRLSet module supports revocation checks for certificates using a list of blocked or revoked X509 certificates. Downstream projects must provide a CRLSet file at build time, which is converted into source code and generates APIs for querying certificate status. Chromium maintains its own CRLSet for the general internet, which can be downloaded with the "pw package install crlset --force" command. Toolings are being developed for generating custom CRLSet files from user-provided certificate files.

The module supports CRLSet based revocation check for certificates. A CRLSet file specifies a list of X509 certificates that either need to be blocked, or have been revoked by the issuer. It is introduced by chromium and primarily used for certificate verification/revocation checks during TLS handshake. The format of a CRLSet file is available in https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/cert/crl_set.cc#24.

Downstream projects need to provide a CRLSet file at build time. For GN builds, specify the path of the CRLSet file with the GN variable pw_tls_client_CRLSET_FILE. This module converts the CRLSet file into source code at build time and generates APIs for querying certificate block/revocation status. See pw_tls_client/crlset.h for more detail.

Chromium maintains its own CRLSet that targets at the general Internet. To use it, run the following command to download the latest version:

pw package install crlset --force

The –force option forces CRLSet to be always re-downloaded so that it is up-to-date. Project that are concerned about up-to-date CRLSet should always run the above command before build.

Toolings will be provided for generating custom CRLSet files from user-provided certificate files. The functionality is under construction.

Setup#

Pigweed AI summary: This paragraph outlines the setup requirements for a module, which includes choosing or creating a backend for pw_tls_client, specifying the backend in the GN build, and providing a pw_tls_client:entropy backend. The module offers options for using either the MbedTLS or BoringSSL backend.

This module requires the following setup:

  1. Choose a pw_tls_client backend, or write one yourself.

  2. If using GN build, Specify the pw_tls_client_BACKEND GN build arg to point the library that provides a pw_tls_client backend. To use the MbedTLS backend, set variable pw_tls_client_BACKEND to //pw_tls_client_mbedtls. To use the BoringSSL backend, set it to //pw_tls_client_boringssl.

  3. Provide a pw_tls_client:entropy backend. If using GN build, specify the backend with variable pw_tls_client_ENTROPY_BACKEND.

Module usage#

Pigweed AI summary: This section provides instructions for using the GN build and an example code for using the Pigweed TLS client module on a host platform. The example code demonstrates how to perform a TLS connection to www.google.com using a socket stream as the transport and includes helper functions for getting the IP address of the target host. The code also shows how to create a TLS session, register the transport, and handle errors. The section concludes by mentioning that other demos are available in the //pw_tls_client/examples/ directory.

For GN build, add //pw_tls_client to the dependency list.

The following gives an example code for using the module on host platform. The example uses a Pigweed socket stream as the transport and performs TLS connection to www.google.com:

// Host domain name
constexpr char kHost[] = "www.google.com";

constexpr int kPort = 443;

// Server Name Indication.
constexpr const char* kServerNameIndication = kHost;

// An example message to send.
constexpr char kHTTPRequest[] = "GET / HTTP/1.1\r\n\r\n";

// pw::stream::SocketStream doesn't accept host domain name as input. Thus we
// introduce this helper function for getting the IP address
pw::Status GetIPAddrFromHostName(std::string_view host, pw::span<char> ip) {
  char null_terminated_host_name[256] = {0};
  auto host_copy_status = pw::string::Copy(host, null_terminated_host_name);
  if (!host_copy_status.ok()) {
    return host_copy_status.status();
  }

  struct hostent* ent = gethostbyname(null_terminated_host_name);
  if (ent == NULL) {
    return PW_STATUS_INTERNAL;
  }

  in_addr** addr_list = reinterpret_cast<in_addr**>(ent->h_addr_list);
  if (addr_list[0] == nullptr) {
    return PW_STATUS_INTERNAL;
  }

  auto ip_copy_status = pw::string::Copy(inet_ntoa(*addr_list[0]), ip);
  if (!ip_copy_status.ok()) {
    return ip_copy_status.status();
  }

  return pw::OkStatus();
}

int main() {
  // Get the IP address of the target host.
  char ip_address[64] = {0};
  auto get_ip_status = GetIPAddrFromHostName(kHost, ip_address);
  if (!get_ip_status.ok()) {
    return 1;
  }

  // Use a socket stream as the transport.
  pw::stream::SocketStream socket_stream;

  // Connect the socket to the remote host.
  auto socket_connect_status = socket_stream.Connect(ip_address, kPort);
  if (!socket_connect_status.ok()) {
    return 1;
  }

  // Create a TLS session. Register the transport.
  auto options = pw::tls_client::SessionOptions()
          .set_server_name(kServerNameIndication)
          .set_transport(socket_stream);
  auto tls_conn = pw::tls_client::Session::Create(options);
  if (!tls_conn.ok()) {
    // Handle errors.
    return 1;
  }

  auto open_status = tls_conn.value()->Open();
  if (!open_status.ok()) {
    // Inspect/handle error with open_status.code() and
    // tls_conn.value()->GetLastTLSStatus().
    return 1;
  }

  auto write_status = tls_conn.value()->Write(pw::as_bytes(pw::span{kHTTPRequest}));
  if (!write_status.ok()) {
    // Inspect/handle error with write_status.code() and
    // tls_conn.value()->GetLastTLSStatus().
    return 0;
  }

  // Listen for incoming data.
  std::array<std::byte, 4096> buffer;
  while (true) {
    auto res = tls_conn.value()->Read(buffer);
    if (!res.ok()) {
      // Inspect/handle error with res.status().code() and
      // tls_conn.value()->GetLastTLSStatus().
      return 1;
    }

    // Process data in |buffer|. res.value() gives the span of read bytes.
    // The following simply print to console.
    if (res.value().size()) {
      auto print_status = pw::sys_io::WriteBytes(res.value());
      if (!print_status.ok()) {
        return 1;
      }
    }

  }
}

A list of other demos will be provided in //pw_tls_client/examples/

Warning#

Pigweed AI summary: The Open()/Read() APIs are currently synchronous, but there are plans to add support for non-blocking/asynchronous usage in the future.

Open()/Read() APIs are synchronous for now. Support for non-blocking/asynchronous usage will be added in the future.