diff mbox series

[v2,1/6] net: lwip: extend wget to support CA (root) certificates

Message ID 20250305142650.2966738-2-jerome.forissier@linaro.org
State New
Headers show
Series net: lwip: root certificates | expand

Commit Message

Jerome Forissier March 5, 2025, 2:26 p.m. UTC
Add the "cacert" (Certification Authority certificates) subcommand to
wget to pass root certificates to the code handling the HTTPS protocol.
The subcommand is enabled by the WGET_CACERT Kconfig symbol.

Usage example:

 => dhcp
 # Download some root certificates (note: not authenticated!)
 => wget https://cacerts.digicert.com/DigiCertTLSECCP384RootG5.crt
 # Provide root certificates
 => wget cacert $fileaddr $filesize
 # Enforce verification (it is optional by default)
 => wget cacert required
 # Forget the root certificates
 => wget cacert 0 0
 # Disable verification
 => wget cacert none

Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
 cmd/Kconfig     |   8 ++++
 cmd/net-lwip.c  |  17 ++++++--
 net/lwip/wget.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 121 insertions(+), 6 deletions(-)

Comments

Heinrich Schuchardt March 5, 2025, 3:07 p.m. UTC | #1
On 05.03.25 15:26, Jerome Forissier wrote:
> Add the "cacert" (Certification Authority certificates) subcommand to
> wget to pass root certificates to the code handling the HTTPS protocol.
> The subcommand is enabled by the WGET_CACERT Kconfig symbol.
>
> Usage example:
>
>   => dhcp
>   # Download some root certificates (note: not authenticated!)
>   => wget https://cacerts.digicert.com/DigiCertTLSECCP384RootG5.crt
>   # Provide root certificates
>   => wget cacert $fileaddr $filesize
>   # Enforce verification (it is optional by default)
>   => wget cacert required
>   # Forget the root certificates
>   => wget cacert 0 0
>   # Disable verification
>   => wget cacert none
>
> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> ---
>   cmd/Kconfig     |   8 ++++
>   cmd/net-lwip.c  |  17 ++++++--
>   net/lwip/wget.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++--
>   3 files changed, 121 insertions(+), 6 deletions(-)
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 8dd42571abc..d469217c0ea 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -2177,6 +2177,14 @@ config WGET_HTTPS
>   	help
>   	  Enable TLS over http for wget.
>
> +config WGET_CACERT
> +	bool "wget cacert"
> +	depends on CMD_WGET
> +	depends on WGET_HTTPS
> +	help
> +	  Adds the "cacert" sub-command to wget to provide root certificates
> +	  to the HTTPS engine. Must be in DER format.
> +

Shouldn't we build CA certs into U-Boot?
Downloading certs from unsafe media is not a good replacement.

Best regards

Heinrich

>   endif  # if CMD_NET
>
>   config CMD_PXE
> diff --git a/cmd/net-lwip.c b/cmd/net-lwip.c
> index 0fd446ecb20..1152c94a6dc 100644
> --- a/cmd/net-lwip.c
> +++ b/cmd/net-lwip.c
> @@ -27,9 +27,20 @@ U_BOOT_CMD(dns, 3, 1, do_dns, "lookup the IP of a hostname",
>   #endif
>
>   #if defined(CONFIG_CMD_WGET)
> -U_BOOT_CMD(wget, 3, 1, do_wget,
> -	   "boot image via network using HTTP/HTTPS protocol",
> +U_BOOT_CMD(wget, 4, 1, do_wget,
> +	   "boot image via network using HTTP/HTTPS protocol"
> +#if defined(CONFIG_WGET_CACERT)
> +	   "\nwget cacert - configure wget root certificates"
> +#endif
> +	   ,
>   	   "[loadAddress] url\n"
> -	   "wget [loadAddress] [host:]path"
> +	   "wget [loadAddress] [host:]path\n"
> +	   "    - load file"
> +#if defined(CONFIG_WGET_CACERT)
> +	   "\nwget cacert <address> <length>\n"
> +	   "    - provide CA certificates (0 0 to remove current)"
> +	   "\nwget cacert none|optional|required\n"
> +	   "    - set server certificate verification mode (default: optional)"
> +#endif
>   );
>   #endif
> diff --git a/net/lwip/wget.c b/net/lwip/wget.c
> index 14f27d42998..c22843ee10d 100644
> --- a/net/lwip/wget.c
> +++ b/net/lwip/wget.c
> @@ -285,9 +285,68 @@ static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct
>   	return ERR_OK;
>   }
>
> +#if CONFIG_IS_ENABLED(WGET_HTTPS)
> +enum auth_mode {
> +	AUTH_NONE,
> +	AUTH_OPTIONAL,
> +	AUTH_REQUIRED,
> +};
> +
> +static char *cacert;
> +static size_t cacert_size;
> +static enum auth_mode cacert_auth_mode = AUTH_OPTIONAL;
> +#endif
> +
> +#if CONFIG_IS_ENABLED(WGET_CACERT)
> +static int set_auth(enum auth_mode auth)
> +{
> +	cacert_auth_mode = auth;
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +static int set_cacert(char * const saddr, char * const ssz)
> +{
> +	mbedtls_x509_crt crt;
> +	ulong addr, sz;
> +	int ret;
> +
> +	if (cacert)
> +		free(cacert);
> +
> +	addr = hextoul(saddr, NULL);
> +	sz = hextoul(ssz, NULL);
> +
> +	if (!addr) {
> +		cacert = NULL;
> +		cacert_size = 0;
> +		return CMD_RET_SUCCESS;
> +	}
> +
> +	cacert = malloc(sz);
> +	if (!cacert)
> +		return CMD_RET_FAILURE;
> +	cacert_size = sz;
> +
> +	memcpy(cacert, (void *)addr, sz);
> +
> +	mbedtls_x509_crt_init(&crt);
> +	ret = mbedtls_x509_crt_parse(&crt, cacert, cacert_size);
> +	if (ret) {
> +		printf("Could not parse certificates (%d)\n", ret);
> +		free(cacert);
> +		cacert = NULL;
> +		cacert_size = 0;
> +		return CMD_RET_FAILURE;
> +	}
> +
> +	return CMD_RET_SUCCESS;
> +}
> +#endif
> +
>   static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
>   {
> -#if defined CONFIG_WGET_HTTPS
> +#if CONFIG_IS_ENABLED(WGET_HTTPS)
>   	altcp_allocator_t tls_allocator;
>   #endif
>   	httpc_connection_t conn;
> @@ -312,11 +371,34 @@ static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
>   		return -1;
>
>   	memset(&conn, 0, sizeof(conn));
> -#if defined CONFIG_WGET_HTTPS
> +#if CONFIG_IS_ENABLED(WGET_HTTPS)
>   	if (is_https) {
> +		char *ca = cacert;
> +		size_t ca_sz = cacert_size;
> +
> +		if (cacert_auth_mode == AUTH_REQUIRED) {
> +			if (!ca || !ca_sz) {
> +				printf("Error: cacert authentication mode is "
> +				       "'required' but no CA certificates "
> +				       "given\n");
> +				return CMD_RET_FAILURE;
> +		       }
> +		} else if (cacert_auth_mode == AUTH_NONE) {
> +			ca = NULL;
> +			ca_sz = 0;
> +		} else if (cacert_auth_mode == AUTH_OPTIONAL) {
> +			/*
> +			 * Nothing to do, this is the default behavior of
> +			 * altcp_tls to check server certificates against CA
> +			 * certificates when the latter are provided and proceed
> +			 * with no verification if not.
> +			 */
> +		}
> +
>   		tls_allocator.alloc = &altcp_tls_alloc;
>   		tls_allocator.arg =
> -			altcp_tls_create_config_client(NULL, 0, ctx.server_name);
> +			altcp_tls_create_config_client(ca, ca_sz,
> +						       ctx.server_name);
>
>   		if (!tls_allocator.arg) {
>   			log_err("error: Cannot create a TLS connection\n");
> @@ -369,6 +451,20 @@ int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
>   	ulong dst_addr;
>   	char nurl[1024];
>
> +#if CONFIG_IS_ENABLED(WGET_CACERT)
> +	if (argc == 4 && !strncmp(argv[1], "cacert", strlen("cacert")))
> +		return set_cacert(argv[2], argv[3]);
> +	if (argc == 3 && !strncmp(argv[1], "cacert", strlen("cacert"))) {
> +		if (!strncmp(argv[2], "none", strlen("none")))
> +			return set_auth(AUTH_NONE);
> +		if (!strncmp(argv[2], "optional", strlen("optional")))
> +			return set_auth(AUTH_OPTIONAL);
> +		if (!strncmp(argv[2], "required", strlen("required")))
> +			return set_auth(AUTH_REQUIRED);
> +		return CMD_RET_USAGE;
> +	}
> +#endif
> +
>   	if (argc < 2 || argc > 3)
>   		return CMD_RET_USAGE;
>
Jerome Forissier March 5, 2025, 3:13 p.m. UTC | #2
Hi Heinrich,

On 3/5/25 16:07, Heinrich Schuchardt wrote:
> On 05.03.25 15:26, Jerome Forissier wrote:
>> Add the "cacert" (Certification Authority certificates) subcommand to
>> wget to pass root certificates to the code handling the HTTPS protocol.
>> The subcommand is enabled by the WGET_CACERT Kconfig symbol.
>>
>> Usage example:
>>
>>   => dhcp
>>   # Download some root certificates (note: not authenticated!)
>>   => wget https://cacerts.digicert.com/DigiCertTLSECCP384RootG5.crt
>>   # Provide root certificates
>>   => wget cacert $fileaddr $filesize
>>   # Enforce verification (it is optional by default)
>>   => wget cacert required
>>   # Forget the root certificates
>>   => wget cacert 0 0
>>   # Disable verification
>>   => wget cacert none
>>
>> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
>> ---
>>   cmd/Kconfig     |   8 ++++
>>   cmd/net-lwip.c  |  17 ++++++--
>>   net/lwip/wget.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++--
>>   3 files changed, 121 insertions(+), 6 deletions(-)
>>
>> diff --git a/cmd/Kconfig b/cmd/Kconfig
>> index 8dd42571abc..d469217c0ea 100644
>> --- a/cmd/Kconfig
>> +++ b/cmd/Kconfig
>> @@ -2177,6 +2177,14 @@ config WGET_HTTPS
>>       help
>>         Enable TLS over http for wget.
>>
>> +config WGET_CACERT
>> +    bool "wget cacert"
>> +    depends on CMD_WGET
>> +    depends on WGET_HTTPS
>> +    help
>> +      Adds the "cacert" sub-command to wget to provide root certificates
>> +      to the HTTPS engine. Must be in DER format.
>> +
> 
> Shouldn't we build CA certs into U-Boot?
> Downloading certs from unsafe media is not a good replacement.

That's the purpose of patch 4/6 [1]. Although downloading may still be a
valid option when used with hash verification as I mentioned in a reply to
Ilias in v1 [2].

[1] https://lists.denx.de/pipermail/u-boot/2025-March/582567.html
[2] https://lists.denx.de/pipermail/u-boot/2025-February/582102.html

Best,
diff mbox series

Patch

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 8dd42571abc..d469217c0ea 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2177,6 +2177,14 @@  config WGET_HTTPS
 	help
 	  Enable TLS over http for wget.
 
+config WGET_CACERT
+	bool "wget cacert"
+	depends on CMD_WGET
+	depends on WGET_HTTPS
+	help
+	  Adds the "cacert" sub-command to wget to provide root certificates
+	  to the HTTPS engine. Must be in DER format.
+
 endif  # if CMD_NET
 
 config CMD_PXE
diff --git a/cmd/net-lwip.c b/cmd/net-lwip.c
index 0fd446ecb20..1152c94a6dc 100644
--- a/cmd/net-lwip.c
+++ b/cmd/net-lwip.c
@@ -27,9 +27,20 @@  U_BOOT_CMD(dns, 3, 1, do_dns, "lookup the IP of a hostname",
 #endif
 
 #if defined(CONFIG_CMD_WGET)
-U_BOOT_CMD(wget, 3, 1, do_wget,
-	   "boot image via network using HTTP/HTTPS protocol",
+U_BOOT_CMD(wget, 4, 1, do_wget,
+	   "boot image via network using HTTP/HTTPS protocol"
+#if defined(CONFIG_WGET_CACERT)
+	   "\nwget cacert - configure wget root certificates"
+#endif
+	   ,
 	   "[loadAddress] url\n"
-	   "wget [loadAddress] [host:]path"
+	   "wget [loadAddress] [host:]path\n"
+	   "    - load file"
+#if defined(CONFIG_WGET_CACERT)
+	   "\nwget cacert <address> <length>\n"
+	   "    - provide CA certificates (0 0 to remove current)"
+	   "\nwget cacert none|optional|required\n"
+	   "    - set server certificate verification mode (default: optional)"
+#endif
 );
 #endif
diff --git a/net/lwip/wget.c b/net/lwip/wget.c
index 14f27d42998..c22843ee10d 100644
--- a/net/lwip/wget.c
+++ b/net/lwip/wget.c
@@ -285,9 +285,68 @@  static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct
 	return ERR_OK;
 }
 
+#if CONFIG_IS_ENABLED(WGET_HTTPS)
+enum auth_mode {
+	AUTH_NONE,
+	AUTH_OPTIONAL,
+	AUTH_REQUIRED,
+};
+
+static char *cacert;
+static size_t cacert_size;
+static enum auth_mode cacert_auth_mode = AUTH_OPTIONAL;
+#endif
+
+#if CONFIG_IS_ENABLED(WGET_CACERT)
+static int set_auth(enum auth_mode auth)
+{
+	cacert_auth_mode = auth;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int set_cacert(char * const saddr, char * const ssz)
+{
+	mbedtls_x509_crt crt;
+	ulong addr, sz;
+	int ret;
+
+	if (cacert)
+		free(cacert);
+
+	addr = hextoul(saddr, NULL);
+	sz = hextoul(ssz, NULL);
+
+	if (!addr) {
+		cacert = NULL;
+		cacert_size = 0;
+		return CMD_RET_SUCCESS;
+	}
+
+	cacert = malloc(sz);
+	if (!cacert)
+		return CMD_RET_FAILURE;
+	cacert_size = sz;
+
+	memcpy(cacert, (void *)addr, sz);
+
+	mbedtls_x509_crt_init(&crt);
+	ret = mbedtls_x509_crt_parse(&crt, cacert, cacert_size);
+	if (ret) {
+		printf("Could not parse certificates (%d)\n", ret);
+		free(cacert);
+		cacert = NULL;
+		cacert_size = 0;
+		return CMD_RET_FAILURE;
+	}
+
+	return CMD_RET_SUCCESS;
+}
+#endif
+
 static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
 {
-#if defined CONFIG_WGET_HTTPS
+#if CONFIG_IS_ENABLED(WGET_HTTPS)
 	altcp_allocator_t tls_allocator;
 #endif
 	httpc_connection_t conn;
@@ -312,11 +371,34 @@  static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
 		return -1;
 
 	memset(&conn, 0, sizeof(conn));
-#if defined CONFIG_WGET_HTTPS
+#if CONFIG_IS_ENABLED(WGET_HTTPS)
 	if (is_https) {
+		char *ca = cacert;
+		size_t ca_sz = cacert_size;
+
+		if (cacert_auth_mode == AUTH_REQUIRED) {
+			if (!ca || !ca_sz) {
+				printf("Error: cacert authentication mode is "
+				       "'required' but no CA certificates "
+				       "given\n");
+				return CMD_RET_FAILURE;
+		       }
+		} else if (cacert_auth_mode == AUTH_NONE) {
+			ca = NULL;
+			ca_sz = 0;
+		} else if (cacert_auth_mode == AUTH_OPTIONAL) {
+			/*
+			 * Nothing to do, this is the default behavior of
+			 * altcp_tls to check server certificates against CA
+			 * certificates when the latter are provided and proceed
+			 * with no verification if not.
+			 */
+		}
+
 		tls_allocator.alloc = &altcp_tls_alloc;
 		tls_allocator.arg =
-			altcp_tls_create_config_client(NULL, 0, ctx.server_name);
+			altcp_tls_create_config_client(ca, ca_sz,
+						       ctx.server_name);
 
 		if (!tls_allocator.arg) {
 			log_err("error: Cannot create a TLS connection\n");
@@ -369,6 +451,20 @@  int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
 	ulong dst_addr;
 	char nurl[1024];
 
+#if CONFIG_IS_ENABLED(WGET_CACERT)
+	if (argc == 4 && !strncmp(argv[1], "cacert", strlen("cacert")))
+		return set_cacert(argv[2], argv[3]);
+	if (argc == 3 && !strncmp(argv[1], "cacert", strlen("cacert"))) {
+		if (!strncmp(argv[2], "none", strlen("none")))
+			return set_auth(AUTH_NONE);
+		if (!strncmp(argv[2], "optional", strlen("optional")))
+			return set_auth(AUTH_OPTIONAL);
+		if (!strncmp(argv[2], "required", strlen("required")))
+			return set_auth(AUTH_REQUIRED);
+		return CMD_RET_USAGE;
+	}
+#endif
+
 	if (argc < 2 || argc > 3)
 		return CMD_RET_USAGE;