diff --git a/src/config/config_certs.c b/src/config/config_certs.c new file mode 100644 index 000000000..a325d132c --- /dev/null +++ b/src/config/config_certs.c @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** @file + * + * Certificate source configuration + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +#ifdef CERTS_EFI +REQUIRE_OBJECT ( efi_cacert ); +#endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index d9814eab5..a0e52e7a7 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -53,6 +53,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define EFI_SETTINGS /* EFI variable settings */ +#define CERTS_EFI /* EFI certificate sources */ + #if defined ( __i386__ ) || defined ( __x86_64__ ) #define IOAPI_X86 #define ENTROPY_RDRAND diff --git a/src/config/general.h b/src/config/general.h index c40e4fdae..7ac77bfe6 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -174,6 +174,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define SHIM_CMD /* EFI shim command (or dummy command) */ //#define USB_CMD /* USB commands */ +/* + * Certificate sources + * + */ +//#undef CERTS_EFI /* EFI certificate sources */ + /* * ROM-specific options * diff --git a/src/crypto/certstore.c b/src/crypto/certstore.c index 31797c4cd..86f67a0af 100644 --- a/src/crypto/certstore.c +++ b/src/crypto/certstore.c @@ -266,3 +266,9 @@ static int certstore_apply_settings ( void ) { struct settings_applicator certstore_applicator __settings_applicator = { .apply = certstore_apply_settings, }; + +/* Drag in objects via certificate store */ +REQUIRING_SYMBOL ( certstore ); + +/* Drag in alternative certificate sources */ +REQUIRE_OBJECT ( config_certs ); diff --git a/src/crypto/rootcert.c b/src/crypto/rootcert.c index 0835ff071..e2b817c57 100644 --- a/src/crypto/rootcert.c +++ b/src/crypto/rootcert.c @@ -58,6 +58,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); 0xed, 0x1a, #endif +/** Flag indicating if root of trust may be overridden at runtime */ +const int allow_trust_override = ALLOW_TRUST_OVERRIDE; + /** Root certificate fingerprints */ static const uint8_t fingerprints[] = { TRUSTED }; diff --git a/src/crypto/x509.c b/src/crypto/x509.c index 4101c8094..acb27411a 100644 --- a/src/crypto/x509.c +++ b/src/crypto/x509.c @@ -1323,9 +1323,9 @@ int x509_is_valid ( struct x509_certificate *cert, struct x509_root *root ) { * @v issuer Issuing X.509 certificate (or NULL) * @v root Root certificate list */ -static void x509_set_valid ( struct x509_certificate *cert, - struct x509_certificate *issuer, - struct x509_root *root ) { +void x509_set_valid ( struct x509_certificate *cert, + struct x509_certificate *issuer, + struct x509_root *root ) { unsigned int max_path_remaining; /* Sanity checks */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 15bb31b0e..c704dd44d 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -428,6 +428,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_usb_cmd ( ERRFILE_OTHER | 0x00640000 ) #define ERRFILE_usb_settings ( ERRFILE_OTHER | 0x00650000 ) #define ERRFILE_weierstrass ( ERRFILE_OTHER | 0x00660000 ) +#define ERRFILE_efi_cacert ( ERRFILE_OTHER | 0x00670000 ) /** @} */ diff --git a/src/include/ipxe/rootcert.h b/src/include/ipxe/rootcert.h index d4be2e1bc..d1a69723d 100644 --- a/src/include/ipxe/rootcert.h +++ b/src/include/ipxe/rootcert.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +extern const int allow_trust_override; extern struct x509_root root_certificates; #endif /* _IPXE_ROOTCERT_H */ diff --git a/src/include/ipxe/x509.h b/src/include/ipxe/x509.h index e71cee8a3..e8cd0f303 100644 --- a/src/include/ipxe/x509.h +++ b/src/include/ipxe/x509.h @@ -421,6 +421,9 @@ extern int x509_certificate ( const void *data, size_t len, struct x509_certificate **cert ); extern int x509_is_valid ( struct x509_certificate *cert, struct x509_root *root ); +extern void x509_set_valid ( struct x509_certificate *cert, + struct x509_certificate *issuer, + struct x509_root *root ); extern int x509_validate ( struct x509_certificate *cert, struct x509_certificate *issuer, time_t time, struct x509_root *root ); diff --git a/src/interface/efi/efi_cacert.c b/src/interface/efi/efi_cacert.c new file mode 100644 index 000000000..5cc268b0e --- /dev/null +++ b/src/interface/efi/efi_cacert.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * EFI CA certificates + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** List of EFI CA certificates */ +static struct x509_chain efi_cacerts = { + .refcnt = REF_INIT ( ref_no_free ), + .links = LIST_HEAD_INIT ( efi_cacerts.links ), +}; + +/** + * Retrieve EFI CA certificate + * + * @v data TlsCaCertificate variable data + * @v len Length of TlsCaCertificate + * @v offset Offset within data + * @v next Next offset, or negative error + */ +static int efi_cacert ( void *data, size_t len, size_t offset ) { + struct asn1_cursor *cursor; + struct x509_certificate *cert; + int next; + int rc; + + /* Extract ASN.1 object */ + next = efisig_asn1 ( virt_to_user ( data ), len, offset, &cursor ); + if ( next < 0 ) { + rc = next; + DBGC ( &efi_cacerts, "EFICA could not parse at +%#zx: %s\n", + offset, strerror ( rc ) ); + goto err_asn1; + } + + /* Append to list of EFI CA certificates */ + if ( ( rc = x509_append_raw ( &efi_cacerts, cursor->data, + cursor->len ) ) != 0 ) { + DBGC ( &efi_cacerts, "EFICA could not append at +%#zx: %s\n", + offset, strerror ( rc ) ); + goto err_append; + } + cert = x509_last ( &efi_cacerts ); + DBGC ( &efi_cacerts, "EFICA found certificate %s\n", + x509_name ( cert ) ); + + /* Mark certificate as valid (i.e. trusted) if permitted */ + if ( allow_trust_override ) { + DBGC ( &efi_cacerts, "EFICA trusting certificate %s\n", + x509_name ( cert ) ); + x509_set_valid ( cert, NULL, &root_certificates ); + } + + /* Free ASN.1 object */ + free ( cursor ); + + return next; + + err_append: + free ( cursor ); + err_asn1: + return rc; +} + +/** + * Retrieve all EFI CA certificates + * + * @ret rc Return status code + */ +static int efi_cacert_all ( void ) { + EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; + EFI_GUID *guid = &efi_tls_ca_certificate_guid; + static CHAR16 *wname = EFI_TLS_CA_CERTIFICATE_VARIABLE; + int offset = 0; + UINT32 attrs; + UINTN size; + void *data; + EFI_STATUS efirc; + int rc; + + /* Get variable length */ + size = 0; + if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size, + NULL ) ) != EFI_BUFFER_TOO_SMALL ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_cacerts, "EFICA could not get %ls size: %s\n", + wname, strerror ( rc ) ); + goto err_len; + } + + /* Allocate temporary buffer */ + data = malloc ( size ); + if ( ! data ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Read variable */ + if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size, + data ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_cacerts, "EFICA could not read %ls: %s\n", + wname, strerror ( rc ) ); + goto err_get; + } + + /* Parse certificates */ + while ( ( ( size_t ) offset ) < size ) { + offset = efi_cacert ( data, size, offset ); + if ( offset < 0 ) { + rc = offset; + goto err_cacert; + } + } + + /* Success */ + rc = 0; + + err_cacert: + err_get: + free ( data ); + err_alloc: + err_len: + return rc; +} + +/** + * Initialise EFI CA certificates + * + */ +static void efi_cacert_init ( void ) { + int rc; + + /* Initialise all certificates */ + if ( ( rc = efi_cacert_all() ) != 0 ) { + DBGC ( &efi_cacert, "EFICA could not initialise: %s\n", + strerror ( rc ) ); + /* Nothing we can do at this point */ + return; + } +} + +/** EFI CA certificates initialisation function */ +struct init_fn efi_cacert_init_fn __init_fn ( INIT_LATE ) = { + .initialise = efi_cacert_init, +}; + +/** + * Discard any EFI CA certificates + * + */ +static void efi_cacert_shutdown ( int booting __unused ) { + + /* Drop our references to the certificates */ + DBGC ( &efi_cacert, "EFICA discarding certificates\n" ); + x509_truncate ( &efi_cacerts, NULL ); + assert ( list_empty ( &efi_cacerts.links ) ); +} + +/** EFI CA certificates shutdown function */ +struct startup_fn efi_cacert_shutdown_fn __startup_fn ( STARTUP_NORMAL ) = { + .name = "efi_cacert", + .shutdown = efi_cacert_shutdown, +};