Yecheng Zhao | 08dd6a5 | 2021-05-10 15:50:22 -0700 | [diff] [blame] | 1 | .. _module-pw_tls_client: |
| 2 | |
| 3 | -------------- |
| 4 | pw_tls_client |
| 5 | -------------- |
| 6 | |
| 7 | This module provides a facade that defines the public APIs for establishing TLS |
| 8 | sessions over arbitrary transports. Two options of backends, |
| 9 | pw_tls_client_mbedtls and pw_tls_client_boringssl, which are based on BoringSSL |
| 10 | and MbedTLS libraries, are under construction. |
| 11 | |
| 12 | The facade provides a class ``pw::tls_client::Session`` with Open(), Read(), |
| 13 | Write() and Close() methods for TLS communication. An instance is created by |
| 14 | ``pw::tls_client::Session::Create`` method. The method takes a |
| 15 | ``pw::tls_client::SessionOptions`` object, which is used to configure TLS |
| 16 | connection options. The list of supported configurations currently include: |
| 17 | |
| 18 | 1. Host name of the target server. This will be used as the Server Name |
| 19 | Indication(SNI) extension during TLS handshake. |
| 20 | |
| 21 | 2. User-implemented transport. The underlying transport for the TLS |
| 22 | communication. It is an object that implements the interface of |
| 23 | ``pw::stream::ReaderWriter``. |
| 24 | |
| 25 | The module will also provide mechanisms/APIs for users to specify sources of |
| 26 | trust anchors, time and entropy. These are under construction. |
| 27 | |
| 28 | .. warning:: |
| 29 | This module is under construction, not ready for use, and the documentation |
| 30 | is incomplete. |
| 31 | |
Yecheng Zhao | 4ee81d7 | 2021-06-16 22:19:38 -0700 | [diff] [blame] | 32 | Prerequisites |
| 33 | ============= |
Yecheng Zhao | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame] | 34 | This module requires the following dependencies: |
| 35 | |
| 36 | 1. Entropy |
Yecheng Zhao | 4ee81d7 | 2021-06-16 22:19:38 -0700 | [diff] [blame] | 37 | ----------- |
Yecheng Zhao | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame] | 38 | TLS requires an entropy source for generating random bytes. Users of this |
| 39 | module should provide one by implementing a backend to the |
Yecheng Zhao | 3e69252 | 2021-06-28 09:09:51 -0700 | [diff] [blame] | 40 | ``pw_tls_client:entropy`` facade. The backend defaults to |
| 41 | ``pw_tls_client:fake_entropy`` that does nothing. |
Yecheng Zhao | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame] | 42 | |
Yecheng Zhao | 4ee81d7 | 2021-06-16 22:19:38 -0700 | [diff] [blame] | 43 | 2. Chromium Verifier |
| 44 | --------------------- |
| 45 | BoringSSL backend uses chromium verifier for certication verification. If the |
| 46 | downstream project uses BoringSSL as the backend, the sources of the verifier, |
| 47 | which is part of the chorimum sources, needs to be downloaded in order for |
| 48 | ``//third_party/chromium_verifier`` to build. It is recommended to use our |
| 49 | support in pw_package for downloading compatible and tested version: |
| 50 | |
| 51 | .. code-block:: sh |
| 52 | |
| 53 | pw package install chromium_verifier |
| 54 | |
| 55 | Then follow instruction for setting ``dir_pw_third_party_chromium_verifier`` to |
| 56 | the path of the downloaded repo. |
| 57 | |
Yecheng Zhao | b8b2261 | 2021-06-21 15:16:35 -0700 | [diff] [blame] | 58 | 3. Date time |
| 59 | ------------- |
| 60 | TLS needs a trust-worthy source of wall clock time in order to check |
| 61 | expiration. Provisioning of time source for TLS communication is very specific |
| 62 | to the TLS library in use. However, common TLS libraires, such as BoringSSL |
| 63 | and MbedTLS, support the use of C APIs ``time()`` and ``getimtofday()`` for |
| 64 | obtaining date time. To accomodate the use of these libraries, a facade target |
Yecheng Zhao | 3cdb68e | 2021-06-09 15:36:05 -0700 | [diff] [blame] | 65 | ``pw_tls_client:time`` is added that wraps these APIs. For GN builds, |
Yecheng Zhao | b8b2261 | 2021-06-21 15:16:35 -0700 | [diff] [blame] | 66 | specify the backend target with variable ``pw_tls_client_C_TIME_BACKEND``. |
Yecheng Zhao | 568e165 | 2021-06-21 15:47:45 -0700 | [diff] [blame] | 67 | ``pw_tls_client_C_TIME_BACKEND`` defaults to the ``pw_tls_client::build_time`` |
| 68 | backend that returns build time. |
Yecheng Zhao | b8b2261 | 2021-06-21 15:16:35 -0700 | [diff] [blame] | 69 | |
| 70 | If downstream project chooses to use other TLS libraires that handle time source |
| 71 | differently, then it needs to be investigated separately. |
| 72 | |
Yecheng Zhao | 3cdb68e | 2021-06-09 15:36:05 -0700 | [diff] [blame] | 73 | 4. CRLSet |
| 74 | ----------- |
| 75 | The module supports CRLSet based revocation check for certificates. A CRLSet |
| 76 | file specifies a list of X509 certificates that either need to be blocked, or |
| 77 | have been revoked by the issuer. It is introduced by chromium and primarily |
| 78 | used for certificate verification/revocation checks during TLS handshake. The |
| 79 | format of a CRLSet file is available in |
| 80 | https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/cert/crl_set.cc#24. |
| 81 | |
| 82 | Downstream projects need to provide a CRLSet file at build time. For GN builds, |
| 83 | specify the path of the CRLSet file with the GN variable |
| 84 | ``pw_tls_client_CRLSET_FILE``. This module converts the CRLSet file into |
| 85 | source code at build time and generates APIs for querying certificate |
| 86 | block/revocation status. See ``pw_tls_client/crlset.h`` for more detail. |
| 87 | |
| 88 | Chromium maintains its own CRLSet that targets at the general Internet. To use it, |
| 89 | run the following command to download the latest version: |
| 90 | |
| 91 | .. code-block:: sh |
| 92 | |
| 93 | pw package install crlset --force |
| 94 | |
| 95 | The `--force` option forces CRLSet to be always re-downloaded so that it is |
| 96 | up-to-date. Project that are concerned about up-to-date CRLSet should always |
| 97 | run the above command before build. |
| 98 | |
| 99 | Toolings will be provided for generating custom CRLSet files from user-provided |
| 100 | certificate files. The functionality is under construction. |
| 101 | |
Yecheng Zhao | 08dd6a5 | 2021-05-10 15:50:22 -0700 | [diff] [blame] | 102 | Setup |
| 103 | ===== |
| 104 | This module requires the following setup: |
| 105 | |
| 106 | 1. Choose a ``pw_tls_client`` backend, or write one yourself. |
| 107 | 2. If using GN build, Specify the ``pw_tls_client_BACKEND`` GN build arg to |
Yecheng Zhao | fb66655 | 2021-06-22 10:02:43 -0700 | [diff] [blame] | 108 | point the library that provides a ``pw_tls_client`` backend. To use the |
| 109 | MbedTLS backend, set variable ``pw_tls_client_BACKEND`` to |
| 110 | ``//pw_tls_client_mbedtls``. To use the BoringSSL backend, set it to |
| 111 | ``//pw_tls_client_boringssl``. |
Yecheng Zhao | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame] | 112 | 3. Provide a `pw_tls_client:entropy` backend. If using GN build, specify the |
| 113 | backend with variable ``pw_tls_client_ENTROPY_BACKEND``. |
Yecheng Zhao | 08dd6a5 | 2021-05-10 15:50:22 -0700 | [diff] [blame] | 114 | |
| 115 | Module usage |
| 116 | ============ |
| 117 | For GN build, add ``//pw_tls_client`` to the dependency list. |
| 118 | |
| 119 | The following gives an example code for using the module on host platform. |
| 120 | The example uses a Pigweed socket stream as the transport and performs TLS |
| 121 | connection to www.google.com: |
| 122 | |
| 123 | .. code-block:: cpp |
| 124 | |
| 125 | // Host domain name |
| 126 | constexpr char kHost[] = "www.google.com"; |
| 127 | |
| 128 | constexpr int kPort = 443; |
| 129 | |
| 130 | // Server Name Indication. |
| 131 | constexpr const char* kServerNameIndication = kHost; |
| 132 | |
| 133 | // An example message to send. |
| 134 | constexpr char kHTTPRequest[] = "GET / HTTP/1.1\r\n\r\n"; |
| 135 | |
| 136 | // pw::stream::SocketStream doesn't accept host domain name as input. Thus we |
| 137 | // introduce this helper function for getting the IP address |
| 138 | pw::Status GetIPAddrFromHostName(std::string_view host, std::span<char> ip) { |
| 139 | char null_terminated_host_name[256] = {0}; |
| 140 | auto host_copy_status = pw::string::Copy(host, null_terminated_host_name); |
| 141 | if (!host_copy_status.ok()) { |
| 142 | return host_copy_status.status(); |
| 143 | } |
| 144 | |
| 145 | struct hostent* ent = gethostbyname(null_terminated_host_name); |
| 146 | if (ent == NULL) { |
| 147 | return PW_STATUS_INTERNAL; |
| 148 | } |
| 149 | |
| 150 | in_addr** addr_list = reinterpret_cast<in_addr**>(ent->h_addr_list); |
| 151 | if (addr_list[0] == nullptr) { |
| 152 | return PW_STATUS_INTERNAL; |
| 153 | } |
| 154 | |
| 155 | auto ip_copy_status = pw::string::Copy(inet_ntoa(*addr_list[0]), ip); |
| 156 | if (!ip_copy_status.ok()) { |
| 157 | return ip_copy_status.status(); |
| 158 | } |
| 159 | |
| 160 | return pw::OkStatus(); |
| 161 | } |
| 162 | |
| 163 | int main() { |
| 164 | // Get the IP address of the target host. |
| 165 | char ip_address[64] = {0}; |
| 166 | auto get_ip_status = GetIPAddrFromHostName(kHost, ip_address); |
| 167 | if (!get_ip_status.ok()) { |
| 168 | return 1; |
| 169 | } |
| 170 | |
| 171 | // Use a socket stream as the transport. |
| 172 | pw::stream::SocketStream socket_stream; |
| 173 | |
| 174 | // Connect the socket to the remote host. |
| 175 | auto socket_connect_status = socket_stream.Connect(ip_address, kPort); |
| 176 | if (!socket_connect_status.ok()) { |
| 177 | return 1; |
| 178 | } |
| 179 | |
| 180 | // Create a TLS session. Register the transport. |
| 181 | auto options = pw::tls_client::SessionOptions() |
| 182 | .set_server_name(kServerNameIndication) |
| 183 | .set_transport(socket_stream); |
| 184 | auto tls_conn = pw::tls_client::Session::Create(options); |
| 185 | if (!tls_conn.ok()) { |
| 186 | // Handle errors. |
| 187 | return 1; |
| 188 | } |
| 189 | |
| 190 | auto open_status = tls_conn.value()->Open(); |
| 191 | if (!open_status.ok()) { |
| 192 | // Inspect/handle error with open_status.code() and |
| 193 | // tls_conn.value()->GetLastTLSStatus(). |
| 194 | return 1; |
| 195 | } |
| 196 | |
| 197 | auto write_status = tls_conn.value()->Write(std::as_bytes(std::span{kHTTPRequest})); |
| 198 | if (!write_status.ok()) { |
| 199 | // Inspect/handle error with write_status.code() and |
| 200 | // tls_conn.value()->GetLastTLSStatus(). |
| 201 | return 0; |
| 202 | } |
| 203 | |
| 204 | // Listen for incoming data. |
| 205 | std::array<std::byte, 4096> buffer; |
| 206 | while (true) { |
| 207 | auto res = tls_conn.value()->Read(buffer); |
| 208 | if (!res.ok()) { |
| 209 | // Inspect/handle error with res.status().code() and |
| 210 | // tls_conn.value()->GetLastTLSStatus(). |
| 211 | return 1; |
| 212 | } |
| 213 | |
| 214 | // Process data in |buffer|. res.value() gives the span of read bytes. |
| 215 | // The following simply print to console. |
| 216 | if (res.value().size()) { |
| 217 | auto print_status = pw::sys_io::WriteBytes(res.value()); |
| 218 | if (!print_status.ok()) { |
| 219 | return 1; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | A list of other demos will be provided in ``//pw_tls_client/examples/`` |
| 227 | |
| 228 | Warning |
| 229 | ============ |
| 230 | |
| 231 | Open()/Read() APIs are synchronous for now. Support for |
| 232 | non-blocking/asynchronous usage will be added in the future. |