Skip to content

Commit 59873a9

Browse files
committed
Add HTTP client examples
Examples to demonstrate HTTP, HTTPS and TLS validation Uses the lwip HTTP client Fixes #318
1 parent bf0017c commit 59873a9

File tree

7 files changed

+360
-0
lines changed

7 files changed

+360
-0
lines changed

pico_w/wifi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ else()
1616
add_subdirectory(tcp_server)
1717
add_subdirectory(freertos)
1818
add_subdirectory(udp_beacon)
19+
add_subdirectory(http_client)
1920

2021
if (NOT PICO_MBEDTLS_PATH)
2122
message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
add_executable(picow_http_client
2+
picow_http_client.c
3+
http_common.c
4+
)
5+
target_compile_definitions(picow_http_client PRIVATE
6+
WIFI_SSID=\"${WIFI_SSID}\"
7+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
8+
)
9+
target_include_directories(picow_http_client PRIVATE
10+
${CMAKE_CURRENT_LIST_DIR}
11+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
12+
)
13+
target_link_libraries(picow_http_client
14+
pico_cyw43_arch_lwip_threadsafe_background
15+
pico_lwip_http
16+
pico_stdlib
17+
pico_lwip_mbedtls
18+
pico_mbedtls
19+
)
20+
pico_add_extra_outputs(picow_http_client)
21+
22+
add_executable(picow_http_client_verify
23+
picow_http_verify.c
24+
http_common.c
25+
)
26+
target_compile_definitions(picow_http_client_verify PRIVATE
27+
WIFI_SSID=\"${WIFI_SSID}\"
28+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
29+
# By default verification is optional (MBEDTLS_SSL_VERIFY_OPTIONAL)
30+
# Make it required for this test
31+
ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED
32+
)
33+
target_include_directories(picow_http_client_verify PRIVATE
34+
${CMAKE_CURRENT_LIST_DIR}
35+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
36+
)
37+
target_link_libraries(picow_http_client_verify
38+
pico_cyw43_arch_lwip_threadsafe_background
39+
pico_lwip_http
40+
pico_stdlib
41+
pico_lwip_mbedtls
42+
pico_mbedtls
43+
)
44+
pico_add_extra_outputs(picow_http_client_verify)
45+
46+
# Ignore warnings from lwip code
47+
set_source_files_properties(
48+
${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
49+
PROPERTIES
50+
COMPILE_OPTIONS "-Wno-unused-result"
51+
)

pico_w/wifi/http_client/http_common.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include "pico/async_context.h"
9+
#include "lwip/apps/http_client.h"
10+
#include "lwip/altcp_tls.h"
11+
12+
typedef struct HTTP_STATE {
13+
bool complete;
14+
httpc_result_t httpc_result;
15+
struct altcp_tls_config *tls_config;
16+
altcp_allocator_t tls_allocator;
17+
httpc_connection_t settings;
18+
const char *hostname;
19+
} HTTP_STATE_T;
20+
21+
// Gets the content of the headers
22+
static err_t headers_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) {
23+
printf("\nheaders %u\n", hdr_len);
24+
25+
u16_t offset = 0;
26+
while (offset < hdr->tot_len && offset < hdr_len) {
27+
char c = (char)pbuf_get_at(hdr, offset++);
28+
putchar(c);
29+
}
30+
return ERR_OK;
31+
}
32+
33+
// Gets the content of the body
34+
static err_t recv_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) {
35+
printf("\ncontent err %d\n", err);
36+
u16_t offset = 0;
37+
while (offset < p->tot_len) {
38+
char c = (char)pbuf_get_at(p, offset++);
39+
putchar(c);
40+
}
41+
return ERR_OK;
42+
}
43+
44+
static void result_fn(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err) {
45+
HTTP_STATE_T *state = (HTTP_STATE_T*)arg;
46+
printf("result %d len %u server_response %u err %d\n", httpc_result, rx_content_len, srv_res, err);
47+
state->complete = true;
48+
state->httpc_result = httpc_result;
49+
}
50+
51+
// Override altcp_tls_alloc to set sni
52+
static struct altcp_pcb *altcp_tls_alloc_sni(void *arg, u8_t ip_type) {
53+
HTTP_STATE_T *state = (HTTP_STATE_T*)arg;
54+
struct altcp_pcb *pcb = altcp_tls_alloc(state->tls_config, ip_type);
55+
if (!pcb) {
56+
return NULL;
57+
}
58+
mbedtls_ssl_set_hostname(altcp_tls_context(pcb), state->hostname);
59+
return pcb;
60+
}
61+
62+
// Make a http request
63+
bool run_http_client_test(async_context_t *context, bool use_https, const uint8_t *cert, size_t cert_len, const char *hostname, const char *url) {
64+
HTTP_STATE_T state = { 0 };
65+
if (use_https) {
66+
state.tls_config = altcp_tls_create_config_client(cert, cert_len);
67+
state.tls_allocator.alloc = altcp_tls_alloc_sni;
68+
state.tls_allocator.arg = &state;
69+
state.settings.altcp_allocator = &state.tls_allocator;
70+
} else {
71+
// Can't use a cert without https
72+
if (cert || cert_len > 0) {
73+
return false;
74+
}
75+
}
76+
state.settings.headers_done_fn = headers_fn; // can be null
77+
state.settings.result_fn = result_fn;
78+
state.hostname = hostname;
79+
state.httpc_result = HTTPC_RESULT_ERR_UNKNOWN; // make sure we see real success
80+
err_t ret = httpc_get_file_dns(hostname, use_https ? 443 : 80, url, &state.settings, recv_fn, &state, NULL);
81+
if (ret != ERR_OK) {
82+
panic("http request failed: %d", ret);
83+
}
84+
while(!state.complete) {
85+
async_context_poll(context);
86+
async_context_wait_for_work_ms(context, 1000);
87+
}
88+
if (use_https) {
89+
altcp_tls_free_config(state.tls_config);
90+
}
91+
return state.httpc_result == HTTPC_RESULT_OK;
92+
}

pico_w/wifi/http_client/lwipopts.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef _LWIPOPTS_H
2+
#define _LWIPOPTS_H
3+
4+
// Generally you would define your own explicit list of lwIP options
5+
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html)
6+
//
7+
// This example uses a common include to avoid repetition
8+
#include "lwipopts_examples_common.h"
9+
10+
/* TCP WND must be at least 16 kb to match TLS record size
11+
or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
12+
#undef TCP_WND
13+
#define TCP_WND 16384
14+
15+
#define LWIP_ALTCP 1
16+
17+
// If you don't want to use TLS (just a http request) you can avoid linking to mbedtls and remove the following
18+
#define LWIP_ALTCP_TLS 1
19+
#define LWIP_ALTCP_TLS_MBEDTLS 1
20+
21+
// Note bug in lwip with LWIP_ALTCP and LWIP_DEBUG
22+
// https://savannah.nongnu.org/bugs/index.php?62159
23+
//#define LWIP_DEBUG 1
24+
#undef LWIP_DEBUG
25+
#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON
26+
27+
#endif
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
64+
// The following is needed to parse a certificate
65+
#define MBEDTLS_PEM_PARSE_C
66+
#define MBEDTLS_BASE64_C
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include "pico/stdio.h"
9+
#include "pico/cyw43_arch.h"
10+
#include "pico/async_context.h"
11+
12+
#define HOST "worldtimeapi.org"
13+
#define URL_REQUEST "/api/ip"
14+
15+
extern bool run_http_client_test(async_context_t *context, bool use_https, const uint8_t *cert, size_t cert_len, const char *hostname, const char *url);
16+
17+
int main() {
18+
stdio_init_all();
19+
if (cyw43_arch_init()) {
20+
printf("failed to initialise\n");
21+
return 1;
22+
}
23+
cyw43_arch_enable_sta_mode();
24+
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) {
25+
printf("failed to connect\n");
26+
return 1;
27+
}
28+
bool result1 = run_http_client_test(cyw43_arch_async_context(), false, NULL, 0, HOST, URL_REQUEST); // http
29+
bool result2 = run_http_client_test(cyw43_arch_async_context(), true, NULL, 0, HOST, URL_REQUEST); // https
30+
if (!result1 || !result2) {
31+
panic("test failed");
32+
}
33+
cyw43_arch_deinit();
34+
printf("Test passed\n");
35+
sleep_ms(100);
36+
return 0;
37+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include "pico/stdio.h"
9+
#include "pico/cyw43_arch.h"
10+
#include "pico/async_context.h"
11+
12+
// Using this url as we know the root cert won't change for a long time
13+
#define HOST "fw-download-alias1.raspberrypi.com"
14+
#define URL_REQUEST "/net_install/boot.sig"
15+
16+
// This is the PUBLIC root certificate exported from a browser
17+
// Note that the newlines are needed
18+
#define TLS_ROOT_CERT_OK "-----BEGIN CERTIFICATE-----\n\
19+
MIIC+jCCAn+gAwIBAgICEAAwCgYIKoZIzj0EAwIwgbcxCzAJBgNVBAYTAkdCMRAw\n\
20+
DgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQHDAlDYW1icmlkZ2UxHTAbBgNVBAoMFFJh\n\
21+
c3BiZXJyeSBQSSBMaW1pdGVkMRwwGgYDVQQLDBNSYXNwYmVycnkgUEkgRUNDIENB\n\
22+
MR0wGwYDVQQDDBRSYXNwYmVycnkgUEkgUm9vdCBDQTEmMCQGCSqGSIb3DQEJARYX\n\
23+
c3VwcG9ydEByYXNwYmVycnlwaS5jb20wIBcNMjExMjA5MTEzMjU1WhgPMjA3MTEx\n\
24+
MjcxMTMyNTVaMIGrMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEdMBsG\n\
25+
A1UECgwUUmFzcGJlcnJ5IFBJIExpbWl0ZWQxHDAaBgNVBAsME1Jhc3BiZXJyeSBQ\n\
26+
SSBFQ0MgQ0ExJTAjBgNVBAMMHFJhc3BiZXJyeSBQSSBJbnRlcm1lZGlhdGUgQ0Ex\n\
27+
JjAkBgkqhkiG9w0BCQEWF3N1cHBvcnRAcmFzcGJlcnJ5cGkuY29tMHYwEAYHKoZI\n\
28+
zj0CAQYFK4EEACIDYgAEcN9K6Cpv+od3w6yKOnec4EbyHCBzF+X2ldjorc0b2Pq0\n\
29+
N+ZvyFHkhFZSgk2qvemsVEWIoPz+K4JSCpgPstz1fEV6WzgjYKfYI71ghELl5TeC\n\
30+
byoPY+ee3VZwF1PTy0cco2YwZDAdBgNVHQ4EFgQUJ6YzIqFh4rhQEbmCnEbWmHEo\n\
31+
XAUwHwYDVR0jBBgwFoAUIIAVCSiDPXut23NK39LGIyAA7NAwEgYDVR0TAQH/BAgw\n\
32+
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDaQAwZgIxAJYM+wIM\n\
33+
PC3wSPqJ1byJKA6D+ZyjKR1aORbiDQVEpDNWRKiQ5QapLg8wbcED0MrRKQIxAKUT\n\
34+
v8TJkb/8jC/oBVTmczKlPMkciN+uiaZSXahgYKyYhvKTatCTZb+geSIhc0w/2w==\n\
35+
-----END CERTIFICATE-----\n"
36+
37+
// This is a test certificate
38+
#define TLS_ROOT_CERT_BAD "-----BEGIN CERTIFICATE-----\n\
39+
MIIDezCCAwGgAwIBAgICEAEwCgYIKoZIzj0EAwIwgasxCzAJBgNVBAYTAkdCMRAw\n\
40+
DgYDVQQIDAdFbmdsYW5kMR0wGwYDVQQKDBRSYXNwYmVycnkgUEkgTGltaXRlZDEc\n\
41+
MBoGA1UECwwTUmFzcGJlcnJ5IFBJIEVDQyBDQTElMCMGA1UEAwwcUmFzcGJlcnJ5\n\
42+
IFBJIEludGVybWVkaWF0ZSBDQTEmMCQGCSqGSIb3DQEJARYXc3VwcG9ydEByYXNw\n\
43+
YmVycnlwaS5jb20wHhcNMjExMjA5MTMwMjIyWhcNNDYxMjAzMTMwMjIyWjA6MQsw\n\
44+
CQYDVQQGEwJHQjErMCkGA1UEAwwiZnctZG93bmxvYWQtYWxpYXMxLnJhc3BiZXJy\n\
45+
eXBpLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ6BQv8YtNiNv7ibLtt4\n\
46+
lwpgEr2XD4sOl9wu/l8GnGD5p39YK8jZV0j6HaTNkqi86Nly1H7YklzbxhFy5orM\n\
47+
356jggGDMIIBfzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgB\n\
48+
hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0G\n\
49+
A1UdDgQWBBRlONP3G2wTERZA9D+VxJABfiaCVTCB5QYDVR0jBIHdMIHagBQnpjMi\n\
50+
oWHiuFARuYKcRtaYcShcBaGBvaSBujCBtzELMAkGA1UEBhMCR0IxEDAOBgNVBAgM\n\
51+
B0VuZ2xhbmQxEjAQBgNVBAcMCUNhbWJyaWRnZTEdMBsGA1UECgwUUmFzcGJlcnJ5\n\
52+
IFBJIExpbWl0ZWQxHDAaBgNVBAsME1Jhc3BiZXJyeSBQSSBFQ0MgQ0ExHTAbBgNV\n\
53+
BAMMFFJhc3BiZXJyeSBQSSBSb290IENBMSYwJAYJKoZIhvcNAQkBFhdzdXBwb3J0\n\
54+
QHJhc3BiZXJyeXBpLmNvbYICEAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG\n\
55+
CCsGAQUFBwMBMAoGCCqGSM49BAMCA2gAMGUCMEHerJRT0WmG5tz4oVLSIxLbCizd\n\
56+
//SdJBCP+072zRUKs0mfl5EcO7dXWvBAb386PwIxAL7LrgpJroJYrYJtqeufJ3a9\n\
57+
zVi56JFnA3cNTcDYfIzyzy5wUskPAykdrRrCS534ig==\n\
58+
-----END CERTIFICATE-----\n"
59+
60+
extern bool run_http_client_test(async_context_t *context, bool use_https, const uint8_t *cert, size_t cert_len, const char *hostname, const char *url);
61+
62+
int main() {
63+
stdio_init_all();
64+
if (cyw43_arch_init()) {
65+
printf("failed to initialise\n");
66+
return 1;
67+
}
68+
cyw43_arch_enable_sta_mode();
69+
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) {
70+
printf("failed to connect\n");
71+
return 1;
72+
}
73+
// This should work
74+
const uint8_t cert_ok[] = TLS_ROOT_CERT_OK;
75+
bool pass1 = run_http_client_test(cyw43_arch_async_context(), true, cert_ok, sizeof(cert_ok), HOST, URL_REQUEST);
76+
// Repeat the test with the wrong certificate. It should fail
77+
const uint8_t cert_bad[] = TLS_ROOT_CERT_BAD;
78+
bool pass2 = !run_http_client_test(cyw43_arch_async_context(), true, cert_bad, sizeof(cert_bad), HOST, URL_REQUEST);
79+
if (!pass1 || !pass2) {
80+
panic("test failed");
81+
}
82+
cyw43_arch_deinit();
83+
printf("Test passed\n");
84+
sleep_ms(100);
85+
return 0;
86+
}

0 commit comments

Comments
 (0)