mirror of
https://github.com/ipxe/ipxe
synced 2026-04-16 03:00:10 +03:00
[crypto] Support extracting certificates from EFI signature list images
Add support for the EFI signature list image format (as produced by tools such as efisecdb). The parsing code does not require any EFI boot services functions and so may be enabled even in non-EFI builds. We default to enabling it only for EFI builds. Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* EFI signature lists
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.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 ( userptr_t data, size_t len, size_t *start,
|
||||
EFI_SIGNATURE_LIST *lhdr, 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;
|
||||
}
|
||||
copy_from_user ( lhdr, data, offset, sizeof ( *lhdr ) );
|
||||
|
||||
/* 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 */
|
||||
copy_from_user ( dhdr, data, offset, sizeof ( *dhdr ));
|
||||
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 ( userptr_t data, size_t len, size_t offset,
|
||||
struct asn1_cursor **cursor ) {
|
||||
EFI_SIGNATURE_LIST lhdr;
|
||||
EFI_SIGNATURE_DATA dhdr;
|
||||
int ( * asn1 ) ( userptr_t 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 ) {
|
||||
EFI_SIGNATURE_LIST lhdr;
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user