Skip to content

Commit 8649633

Browse files
Add tls_client example (#305)
* Add tls_client example This requires pico_mbedtls in the pico-sdk Connects to worldtimeapi.org and retrieves a web page Originally written by Floris Bos @maxnet
1 parent e2a389c commit 8649633

File tree

6 files changed

+375
-0
lines changed

6 files changed

+375
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ App|Description
121121
[picow_ntp_client](pico_w/ntp_client)| Connects to an NTP server to fetch and display the current time.
122122
[picow_tcp_client](pico_w/tcp_client)| A simple TCP client. You can run [python_test_tcp_server.py](pico_w/python_test_tcp/python_test_tcp_server.py) for it to connect to.
123123
[picow_tcp_server](pico_w/tcp_server)| A simple TCP server. You can use [python_test_tcp_client.py](pico_w/python_test_tcp/python_test_tcp_client.py) to connect to it.
124+
[picow_tls_client](pico_w/tls_client)| Demonstrates how to make a HTTPS request using TLS.
124125
[picow_wifi_scan](pico_w/wifi_scan)| Scans for WiFi networks and prints the results.
125126
[picow_udp_beacon](pico_w/udp_beacon)| A simple UDP transmitter.
126127

pico_w/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ if (PICO_CYW43_SUPPORTED) # set by PICO_BOARD=pico_w
3333
add_subdirectory(tcp_server)
3434
add_subdirectory(freertos)
3535
add_subdirectory(udp_beacon)
36+
37+
if (NOT PICO_MBEDTLS_PATH)
38+
message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined")
39+
else()
40+
add_subdirectory(tls_client)
41+
endif()
3642
endif()
3743
endif()
3844
endif()

pico_w/tls_client/CMakeLists.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
add_executable(picow_tls_client_background
2+
picow_tls_client.c
3+
)
4+
target_compile_definitions(picow_tls_client_background PRIVATE
5+
WIFI_SSID=\"${WIFI_SSID}\"
6+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
7+
)
8+
target_include_directories(picow_tls_client_background PRIVATE
9+
${CMAKE_CURRENT_LIST_DIR}
10+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
11+
)
12+
target_link_libraries(picow_tls_client_background
13+
pico_cyw43_arch_lwip_threadsafe_background
14+
pico_lwip_mbedtls
15+
pico_mbedtls
16+
pico_stdlib
17+
)
18+
pico_add_extra_outputs(picow_tls_client_background)
19+
20+
add_executable(picow_tls_client_poll
21+
picow_tls_client.c
22+
)
23+
target_compile_definitions(picow_tls_client_poll PRIVATE
24+
WIFI_SSID=\"${WIFI_SSID}\"
25+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
26+
)
27+
target_include_directories(picow_tls_client_poll PRIVATE
28+
${CMAKE_CURRENT_LIST_DIR}
29+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
30+
)
31+
target_link_libraries(picow_tls_client_poll
32+
pico_cyw43_arch_lwip_poll
33+
pico_lwip_mbedtls
34+
pico_mbedtls
35+
pico_stdlib
36+
)
37+
pico_add_extra_outputs(picow_tls_client_poll)
38+
39+
# Ignore warnings from lwip code
40+
set_source_files_properties(
41+
${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
42+
PROPERTIES
43+
COMPILE_OPTIONS "-Wno-unused-result"
44+
)

pico_w/tls_client/lwipopts.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef _LWIPOPTS_H
2+
#define _LWIPOPTS_H
3+
4+
#include "lwipopts_examples_common.h"
5+
6+
/* TCP WND must be at least 16 kb to match TLS record size
7+
or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
8+
#undef TCP_WND
9+
#define TCP_WND 16384
10+
11+
#define LWIP_ALTCP 1
12+
#define LWIP_ALTCP_TLS 1
13+
#define LWIP_ALTCP_TLS_MBEDTLS 1
14+
15+
#define LWIP_DEBUG 1
16+
#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON
17+
18+
#endif
19+

pico_w/tls_client/mbedtls_config.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* Workaround for some mbedtls source files using INT_MAX without including limits.h */
2+
#include <limits.h>
3+
4+
#define MBEDTLS_NO_PLATFORM_ENTROPY
5+
#define MBEDTLS_ENTROPY_HARDWARE_ALT
6+
7+
#define MBEDTLS_SSL_OUT_CONTENT_LEN 2048
8+
9+
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
10+
#define MBEDTLS_HAVE_TIME
11+
12+
#define MBEDTLS_CIPHER_MODE_CBC
13+
#define MBEDTLS_ECP_DP_SECP192R1_ENABLED
14+
#define MBEDTLS_ECP_DP_SECP224R1_ENABLED
15+
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
16+
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
17+
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
18+
#define MBEDTLS_ECP_DP_SECP192K1_ENABLED
19+
#define MBEDTLS_ECP_DP_SECP224K1_ENABLED
20+
#define MBEDTLS_ECP_DP_SECP256K1_ENABLED
21+
#define MBEDTLS_ECP_DP_BP256R1_ENABLED
22+
#define MBEDTLS_ECP_DP_BP384R1_ENABLED
23+
#define MBEDTLS_ECP_DP_BP512R1_ENABLED
24+
#define MBEDTLS_ECP_DP_CURVE25519_ENABLED
25+
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
26+
#define MBEDTLS_PKCS1_V15
27+
#define MBEDTLS_SHA256_SMALLER
28+
#define MBEDTLS_SSL_SERVER_NAME_INDICATION
29+
#define MBEDTLS_AES_C
30+
#define MBEDTLS_ASN1_PARSE_C
31+
#define MBEDTLS_BIGNUM_C
32+
#define MBEDTLS_CIPHER_C
33+
#define MBEDTLS_CTR_DRBG_C
34+
#define MBEDTLS_ENTROPY_C
35+
#define MBEDTLS_ERROR_C
36+
#define MBEDTLS_MD_C
37+
#define MBEDTLS_MD5_C
38+
#define MBEDTLS_OID_C
39+
#define MBEDTLS_PKCS5_C
40+
#define MBEDTLS_PK_C
41+
#define MBEDTLS_PK_PARSE_C
42+
#define MBEDTLS_PLATFORM_C
43+
#define MBEDTLS_RSA_C
44+
#define MBEDTLS_SHA1_C
45+
#define MBEDTLS_SHA224_C
46+
#define MBEDTLS_SHA256_C
47+
#define MBEDTLS_SHA512_C
48+
#define MBEDTLS_SSL_CLI_C
49+
#define MBEDTLS_SSL_SRV_C
50+
#define MBEDTLS_SSL_TLS_C
51+
#define MBEDTLS_X509_CRT_PARSE_C
52+
#define MBEDTLS_X509_USE_C
53+
#define MBEDTLS_AES_FEWER_TABLES
54+
55+
/* TLS 1.2 */
56+
#define MBEDTLS_SSL_PROTO_TLS1_2
57+
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
58+
#define MBEDTLS_GCM_C
59+
#define MBEDTLS_ECDH_C
60+
#define MBEDTLS_ECP_C
61+
#define MBEDTLS_ECDSA_C
62+
#define MBEDTLS_ASN1_WRITE_C
63+

pico_w/tls_client/picow_tls_client.c

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <string.h>
8+
#include <time.h>
9+
10+
#include "pico/stdlib.h"
11+
#include "pico/cyw43_arch.h"
12+
#include "lwip/pbuf.h"
13+
#include "lwip/altcp_tcp.h"
14+
#include "lwip/altcp_tls.h"
15+
#include "lwip/dns.h"
16+
17+
#define TLS_CLIENT_SERVER "worldtimeapi.org"
18+
#define TLS_CLIENT_HTTP_REQUEST "GET /api/ip HTTP/1.1\r\n" \
19+
"Host: " TLS_CLIENT_SERVER "\r\n" \
20+
"Connection: close\r\n" \
21+
"\r\n"
22+
#define TLS_CLIENT_TIMEOUT_SECS 15
23+
24+
25+
typedef struct TLS_CLIENT_T_ {
26+
struct altcp_pcb *pcb;
27+
bool complete;
28+
} TLS_CLIENT_T;
29+
30+
static struct altcp_tls_config *tls_config = NULL;
31+
32+
static err_t tls_client_close(void *arg) {
33+
TLS_CLIENT_T *state = (TLS_CLIENT_T*)arg;
34+
err_t err = ERR_OK;
35+
36+
state->complete = true;
37+
if (state->pcb != NULL) {
38+
altcp_arg(state->pcb, NULL);
39+
altcp_poll(state->pcb, NULL, 0);
40+
altcp_recv(state->pcb, NULL);
41+
altcp_err(state->pcb, NULL);
42+
err = altcp_close(state->pcb);
43+
if (err != ERR_OK) {
44+
printf("close failed %d, calling abort\n", err);
45+
altcp_abort(state->pcb);
46+
err = ERR_ABRT;
47+
}
48+
state->pcb = NULL;
49+
}
50+
return err;
51+
}
52+
53+
static err_t tls_client_connected(void *arg, struct altcp_pcb *pcb, err_t err) {
54+
TLS_CLIENT_T *state = (TLS_CLIENT_T*)arg;
55+
if (err != ERR_OK) {
56+
printf("connect failed %d\n", err);
57+
return tls_client_close(state);
58+
}
59+
60+
printf("connected to server, sending request\n");
61+
err = altcp_write(state->pcb, TLS_CLIENT_HTTP_REQUEST, strlen(TLS_CLIENT_HTTP_REQUEST), TCP_WRITE_FLAG_COPY);
62+
if (err != ERR_OK) {
63+
printf("error writing data, err=%d", err);
64+
return tls_client_close(state);
65+
}
66+
67+
return ERR_OK;
68+
}
69+
70+
static err_t tls_client_poll(void *arg, struct altcp_pcb *pcb) {
71+
printf("timed out");
72+
return tls_client_close(arg);
73+
}
74+
75+
static void tls_client_err(void *arg, err_t err) {
76+
TLS_CLIENT_T *state = (TLS_CLIENT_T*)arg;
77+
printf("tls_client_err %d\n", err);
78+
state->pcb = NULL; /* pcb freed by lwip when _err function is called */
79+
}
80+
81+
static err_t tls_client_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) {
82+
TLS_CLIENT_T *state = (TLS_CLIENT_T*)arg;
83+
if (!p) {
84+
printf("connection closed\n");
85+
return tls_client_close(state);
86+
}
87+
88+
if (p->tot_len > 0) {
89+
/* For simplicity this examples creates a buffer on stack the size of the data pending here,
90+
and copies all the data to it in one go.
91+
Do be aware that the amount of data can potentially be a bit large (TLS record size can be 16 KB),
92+
so you may want to use a smaller fixed size buffer and copy the data to it using a loop, if memory is a concern */
93+
char buf[p->tot_len + 1];
94+
95+
pbuf_copy_partial(p, buf, p->tot_len, 0);
96+
buf[p->tot_len] = 0;
97+
98+
printf("***\nnew data received from server:\n***\n\n%s\n", buf);
99+
100+
altcp_recved(pcb, p->tot_len);
101+
}
102+
pbuf_free(p);
103+
104+
return ERR_OK;
105+
}
106+
107+
static void tls_client_connect_to_server_ip(const ip_addr_t *ipaddr, TLS_CLIENT_T *state)
108+
{
109+
err_t err;
110+
u16_t port = 443;
111+
112+
printf("connecting to server IP %s port %d\n", ipaddr_ntoa(ipaddr), port);
113+
err = altcp_connect(state->pcb, ipaddr, port, tls_client_connected);
114+
if (err != ERR_OK)
115+
{
116+
fprintf(stderr, "error initiating connect, err=%d\n", err);
117+
tls_client_close(state);
118+
}
119+
}
120+
121+
static void tls_client_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
122+
{
123+
if (ipaddr)
124+
{
125+
printf("DNS resolving complete\n");
126+
tls_client_connect_to_server_ip(ipaddr, (TLS_CLIENT_T *) arg);
127+
}
128+
else
129+
{
130+
printf("error resolving hostname %s\n", hostname);
131+
tls_client_close(arg);
132+
}
133+
}
134+
135+
136+
static bool tls_client_open(const char *hostname, void *arg) {
137+
err_t err;
138+
ip_addr_t server_ip;
139+
TLS_CLIENT_T *state = (TLS_CLIENT_T*)arg;
140+
141+
state->pcb = altcp_tls_new(tls_config, IPADDR_TYPE_ANY);
142+
if (!state->pcb) {
143+
printf("failed to create pcb\n");
144+
return false;
145+
}
146+
147+
altcp_arg(state->pcb, state);
148+
altcp_poll(state->pcb, tls_client_poll, TLS_CLIENT_TIMEOUT_SECS * 2);
149+
altcp_recv(state->pcb, tls_client_recv);
150+
altcp_err(state->pcb, tls_client_err);
151+
152+
/* Set SNI */
153+
mbedtls_ssl_set_hostname(altcp_tls_context(state->pcb), hostname);
154+
155+
printf("resolving %s\n", hostname);
156+
157+
// cyw43_arch_lwip_begin/end should be used around calls into lwIP to ensure correct locking.
158+
// You can omit them if you are in a callback from lwIP. Note that when using pico_cyw_arch_poll
159+
// these calls are a no-op and can be omitted, but it is a good practice to use them in
160+
// case you switch the cyw43_arch type later.
161+
cyw43_arch_lwip_begin();
162+
163+
err = dns_gethostbyname(hostname, &server_ip, tls_client_dns_found, state);
164+
if (err == ERR_OK)
165+
{
166+
/* host is in DNS cache */
167+
tls_client_connect_to_server_ip(&server_ip, state);
168+
}
169+
else if (err != ERR_INPROGRESS)
170+
{
171+
printf("error initiating DNS resolving, err=%d\n", err);
172+
tls_client_close(state->pcb);
173+
}
174+
175+
cyw43_arch_lwip_end();
176+
177+
return err == ERR_OK || err == ERR_INPROGRESS;
178+
}
179+
180+
// Perform initialisation
181+
static TLS_CLIENT_T* tls_client_init(void) {
182+
TLS_CLIENT_T *state = calloc(1, sizeof(TLS_CLIENT_T));
183+
if (!state) {
184+
printf("failed to allocate state\n");
185+
return NULL;
186+
}
187+
188+
return state;
189+
}
190+
191+
void run_tls_client_test(void) {
192+
/* No CA certificate checking */
193+
tls_config = altcp_tls_create_config_client(NULL, 0);
194+
195+
TLS_CLIENT_T *state = tls_client_init();
196+
if (!state) {
197+
return;
198+
}
199+
if (!tls_client_open(TLS_CLIENT_SERVER, state)) {
200+
return;
201+
}
202+
while(!state->complete) {
203+
// the following #ifdef is only here so this same example can be used in multiple modes;
204+
// you do not need it in your code
205+
#if PICO_CYW43_ARCH_POLL
206+
// if you are using pico_cyw43_arch_poll, then you must poll periodically from your
207+
// main loop (not from a timer) to check for WiFi driver or lwIP work that needs to be done.
208+
cyw43_arch_poll();
209+
sleep_ms(1);
210+
#else
211+
// if you are not using pico_cyw43_arch_poll, then WiFI driver and lwIP work
212+
// is done via interrupt in the background. This sleep is just an example of some (blocking)
213+
// work you might be doing.
214+
sleep_ms(1000);
215+
#endif
216+
}
217+
free(state);
218+
altcp_tls_free_config(tls_config);
219+
}
220+
221+
int main() {
222+
stdio_init_all();
223+
224+
if (cyw43_arch_init()) {
225+
printf("failed to initialise\n");
226+
return 1;
227+
}
228+
cyw43_arch_enable_sta_mode();
229+
230+
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
231+
printf("failed to connect\n");
232+
return 1;
233+
}
234+
run_tls_client_test();
235+
236+
/* sleep a bit to let usb stdio write out any buffer to host */
237+
sleep_ms(100);
238+
239+
cyw43_arch_deinit();
240+
return 0;
241+
}
242+

0 commit comments

Comments
 (0)