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 | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame^] | 32 | System Dependencies |
| 33 | =================== |
| 34 | This module requires the following dependencies: |
| 35 | |
| 36 | 1. Entropy |
| 37 | TLS requires an entropy source for generating random bytes. Users of this |
| 38 | module should provide one by implementing a backend to the |
| 39 | ``pw_tls_client:entropy`` facade. |
| 40 | |
Yecheng Zhao | 08dd6a5 | 2021-05-10 15:50:22 -0700 | [diff] [blame] | 41 | Setup |
| 42 | ===== |
| 43 | This module requires the following setup: |
| 44 | |
| 45 | 1. Choose a ``pw_tls_client`` backend, or write one yourself. |
| 46 | 2. If using GN build, Specify the ``pw_tls_client_BACKEND`` GN build arg to |
| 47 | point the library that provides a ``pw_tls_client`` backend. |
Yecheng Zhao | e5dbfc0 | 2021-06-07 16:38:48 -0700 | [diff] [blame^] | 48 | 3. Provide a `pw_tls_client:entropy` backend. If using GN build, specify the |
| 49 | backend with variable ``pw_tls_client_ENTROPY_BACKEND``. |
Yecheng Zhao | 08dd6a5 | 2021-05-10 15:50:22 -0700 | [diff] [blame] | 50 | |
| 51 | Module usage |
| 52 | ============ |
| 53 | For GN build, add ``//pw_tls_client`` to the dependency list. |
| 54 | |
| 55 | The following gives an example code for using the module on host platform. |
| 56 | The example uses a Pigweed socket stream as the transport and performs TLS |
| 57 | connection to www.google.com: |
| 58 | |
| 59 | .. code-block:: cpp |
| 60 | |
| 61 | // Host domain name |
| 62 | constexpr char kHost[] = "www.google.com"; |
| 63 | |
| 64 | constexpr int kPort = 443; |
| 65 | |
| 66 | // Server Name Indication. |
| 67 | constexpr const char* kServerNameIndication = kHost; |
| 68 | |
| 69 | // An example message to send. |
| 70 | constexpr char kHTTPRequest[] = "GET / HTTP/1.1\r\n\r\n"; |
| 71 | |
| 72 | // pw::stream::SocketStream doesn't accept host domain name as input. Thus we |
| 73 | // introduce this helper function for getting the IP address |
| 74 | pw::Status GetIPAddrFromHostName(std::string_view host, std::span<char> ip) { |
| 75 | char null_terminated_host_name[256] = {0}; |
| 76 | auto host_copy_status = pw::string::Copy(host, null_terminated_host_name); |
| 77 | if (!host_copy_status.ok()) { |
| 78 | return host_copy_status.status(); |
| 79 | } |
| 80 | |
| 81 | struct hostent* ent = gethostbyname(null_terminated_host_name); |
| 82 | if (ent == NULL) { |
| 83 | return PW_STATUS_INTERNAL; |
| 84 | } |
| 85 | |
| 86 | in_addr** addr_list = reinterpret_cast<in_addr**>(ent->h_addr_list); |
| 87 | if (addr_list[0] == nullptr) { |
| 88 | return PW_STATUS_INTERNAL; |
| 89 | } |
| 90 | |
| 91 | auto ip_copy_status = pw::string::Copy(inet_ntoa(*addr_list[0]), ip); |
| 92 | if (!ip_copy_status.ok()) { |
| 93 | return ip_copy_status.status(); |
| 94 | } |
| 95 | |
| 96 | return pw::OkStatus(); |
| 97 | } |
| 98 | |
| 99 | int main() { |
| 100 | // Get the IP address of the target host. |
| 101 | char ip_address[64] = {0}; |
| 102 | auto get_ip_status = GetIPAddrFromHostName(kHost, ip_address); |
| 103 | if (!get_ip_status.ok()) { |
| 104 | return 1; |
| 105 | } |
| 106 | |
| 107 | // Use a socket stream as the transport. |
| 108 | pw::stream::SocketStream socket_stream; |
| 109 | |
| 110 | // Connect the socket to the remote host. |
| 111 | auto socket_connect_status = socket_stream.Connect(ip_address, kPort); |
| 112 | if (!socket_connect_status.ok()) { |
| 113 | return 1; |
| 114 | } |
| 115 | |
| 116 | // Create a TLS session. Register the transport. |
| 117 | auto options = pw::tls_client::SessionOptions() |
| 118 | .set_server_name(kServerNameIndication) |
| 119 | .set_transport(socket_stream); |
| 120 | auto tls_conn = pw::tls_client::Session::Create(options); |
| 121 | if (!tls_conn.ok()) { |
| 122 | // Handle errors. |
| 123 | return 1; |
| 124 | } |
| 125 | |
| 126 | auto open_status = tls_conn.value()->Open(); |
| 127 | if (!open_status.ok()) { |
| 128 | // Inspect/handle error with open_status.code() and |
| 129 | // tls_conn.value()->GetLastTLSStatus(). |
| 130 | return 1; |
| 131 | } |
| 132 | |
| 133 | auto write_status = tls_conn.value()->Write(std::as_bytes(std::span{kHTTPRequest})); |
| 134 | if (!write_status.ok()) { |
| 135 | // Inspect/handle error with write_status.code() and |
| 136 | // tls_conn.value()->GetLastTLSStatus(). |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | // Listen for incoming data. |
| 141 | std::array<std::byte, 4096> buffer; |
| 142 | while (true) { |
| 143 | auto res = tls_conn.value()->Read(buffer); |
| 144 | if (!res.ok()) { |
| 145 | // Inspect/handle error with res.status().code() and |
| 146 | // tls_conn.value()->GetLastTLSStatus(). |
| 147 | return 1; |
| 148 | } |
| 149 | |
| 150 | // Process data in |buffer|. res.value() gives the span of read bytes. |
| 151 | // The following simply print to console. |
| 152 | if (res.value().size()) { |
| 153 | auto print_status = pw::sys_io::WriteBytes(res.value()); |
| 154 | if (!print_status.ok()) { |
| 155 | return 1; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | A list of other demos will be provided in ``//pw_tls_client/examples/`` |
| 163 | |
| 164 | Warning |
| 165 | ============ |
| 166 | |
| 167 | Open()/Read() APIs are synchronous for now. Support for |
| 168 | non-blocking/asynchronous usage will be added in the future. |