| /* |
| * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * 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_SECBOOT ( PERMITTED ); |
| |
| /** @file |
| * |
| * EFI signature lists |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <ipxe/asn1.h> |
| #include <ipxe/der.h> |
| #include <ipxe/pem.h> |
| #include <ipxe/image.h> |
| #include <ipxe/efi/efi.h> |
| #include <ipxe/efi/Guid/ImageAuthentication.h> |
| #include <ipxe/efi/efi_siglist.h> |
| |
| /** |
| * Find EFI signature list entry |
| * |
| * @v data EFI signature list |
| * @v len Length of EFI signature list |
| * @v start Starting offset to update |
| * @v lhdr Signature list header to fill in |
| * @v dhdr Signature data header to fill in |
| * @ret rc Return status code |
| */ |
| static int efisig_find ( const void *data, size_t len, size_t *start, |
| const EFI_SIGNATURE_LIST **lhdr, |
| const EFI_SIGNATURE_DATA **dhdr ) { |
| size_t offset; |
| size_t remaining; |
| size_t skip; |
| size_t dlen; |
| |
| /* Scan through signature list */ |
| offset = 0; |
| while ( 1 ) { |
| |
| /* Read list header */ |
| assert ( offset <= len ); |
| remaining = ( len - offset ); |
| if ( remaining < sizeof ( **lhdr ) ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) truncated header " |
| "at +%#zx\n", *start, len, offset ); |
| return -EINVAL; |
| } |
| *lhdr = ( data + offset ); |
| |
| /* Get length of this signature list */ |
| if ( remaining < (*lhdr)->SignatureListSize ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) truncated list at " |
| "+%#zx\n", *start, len, offset ); |
| return -EINVAL; |
| } |
| remaining = (*lhdr)->SignatureListSize; |
| |
| /* Get length of each signature in list */ |
| dlen = (*lhdr)->SignatureSize; |
| if ( dlen < sizeof ( **dhdr ) ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) underlength " |
| "signatures at +%#zx\n", *start, len, offset ); |
| return -EINVAL; |
| } |
| |
| /* Strip list header (including variable portion) */ |
| if ( ( remaining < sizeof ( **lhdr ) ) || |
| ( ( remaining - sizeof ( **lhdr ) ) < |
| (*lhdr)->SignatureHeaderSize ) ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) malformed header at " |
| "+%#zx\n", *start, len, offset ); |
| return -EINVAL; |
| } |
| skip = ( sizeof ( **lhdr ) + (*lhdr)->SignatureHeaderSize ); |
| offset += skip; |
| remaining -= skip; |
| |
| /* Read signatures */ |
| for ( ; remaining ; offset += dlen, remaining -= dlen ) { |
| |
| /* Check length */ |
| if ( remaining < dlen ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) truncated " |
| "at +%#zx\n", *start, len, offset ); |
| return -EINVAL; |
| } |
| |
| /* Continue until we find the requested signature */ |
| if ( offset < *start ) |
| continue; |
| |
| /* Read data header */ |
| *dhdr = ( data + offset ); |
| DBGC2 ( data, "EFISIG [%#zx,%#zx) %s ", |
| offset, ( offset + dlen ), |
| efi_guid_ntoa ( &(*lhdr)->SignatureType ) ); |
| DBGC2 ( data, "owner %s\n", |
| efi_guid_ntoa ( &(*dhdr)->SignatureOwner ) ); |
| *start = offset; |
| return 0; |
| } |
| } |
| } |
| |
| /** |
| * Extract ASN.1 object from EFI signature list |
| * |
| * @v data EFI signature list |
| * @v len Length of EFI signature list |
| * @v offset Offset within image |
| * @v cursor ASN.1 cursor to fill in |
| * @ret next Offset to next image, or negative error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated ASN.1 cursor. |
| */ |
| int efisig_asn1 ( const void *data, size_t len, size_t offset, |
| struct asn1_cursor **cursor ) { |
| const EFI_SIGNATURE_LIST *lhdr; |
| const EFI_SIGNATURE_DATA *dhdr; |
| int ( * asn1 ) ( const void *data, size_t len, size_t offset, |
| struct asn1_cursor **cursor ); |
| size_t skip = offsetof ( typeof ( *dhdr ), SignatureData ); |
| int next; |
| int rc; |
| |
| /* Locate signature list entry */ |
| if ( ( rc = efisig_find ( data, len, &offset, &lhdr, &dhdr ) ) != 0 ) |
| goto err_entry; |
| len = ( offset + lhdr->SignatureSize ); |
| |
| /* Parse as PEM or DER based on first character */ |
| asn1 = ( ( dhdr->SignatureData[0] == ASN1_SEQUENCE ) ? |
| der_asn1 : pem_asn1 ); |
| DBGC2 ( data, "EFISIG [%#zx,%#zx) extracting %s\n", offset, len, |
| ( ( asn1 == der_asn1 ) ? "DER" : "PEM" ) ); |
| next = asn1 ( data, len, ( offset + skip ), cursor ); |
| if ( next < 0 ) { |
| rc = next; |
| DBGC ( data, "EFISIG [%#zx,%#zx) could not extract ASN.1: " |
| "%s\n", offset, len, strerror ( rc ) ); |
| goto err_asn1; |
| } |
| |
| /* Check that whole entry was consumed */ |
| if ( ( ( unsigned int ) next ) != len ) { |
| DBGC ( data, "EFISIG [%#zx,%#zx) malformed data\n", |
| offset, len ); |
| rc = -EINVAL; |
| goto err_whole; |
| } |
| |
| return len; |
| |
| err_whole: |
| free ( *cursor ); |
| err_asn1: |
| err_entry: |
| return rc; |
| } |
| |
| /** |
| * Probe EFI signature list image |
| * |
| * @v image EFI signature list |
| * @ret rc Return status code |
| */ |
| static int efisig_image_probe ( struct image *image ) { |
| const EFI_SIGNATURE_LIST *lhdr; |
| const EFI_SIGNATURE_DATA *dhdr; |
| size_t offset = 0; |
| unsigned int count = 0; |
| int rc; |
| |
| /* Check file is a well-formed signature list */ |
| while ( 1 ) { |
| |
| /* Find next signature list entry */ |
| if ( ( rc = efisig_find ( image->data, image->len, &offset, |
| &lhdr, &dhdr ) ) != 0 ) { |
| return rc; |
| } |
| |
| /* Skip this entry */ |
| offset += lhdr->SignatureSize; |
| count++; |
| |
| /* Check if we have reached end of the image */ |
| if ( offset == image->len ) { |
| DBGC ( image, "EFISIG %s contains %d signatures\n", |
| image->name, count ); |
| return 0; |
| } |
| } |
| } |
| |
| /** |
| * Extract ASN.1 object from EFI signature list image |
| * |
| * @v image EFI signature list |
| * @v offset Offset within image |
| * @v cursor ASN.1 cursor to fill in |
| * @ret next Offset to next image, or negative error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated ASN.1 cursor. |
| */ |
| static int efisig_image_asn1 ( struct image *image, size_t offset, |
| struct asn1_cursor **cursor ) { |
| int next; |
| int rc; |
| |
| /* Extract ASN.1 object */ |
| if ( ( next = efisig_asn1 ( image->data, image->len, offset, |
| cursor ) ) < 0 ) { |
| rc = next; |
| DBGC ( image, "EFISIG %s could not extract ASN.1: %s\n", |
| image->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return next; |
| } |
| |
| /** EFI signature list image type */ |
| struct image_type efisig_image_type __image_type ( PROBE_NORMAL ) = { |
| .name = "EFISIG", |
| .probe = efisig_image_probe, |
| .asn1 = efisig_image_asn1, |
| }; |