diff options
Diffstat (limited to 'roms/skiboot/libstb/secvar/backend/edk2-compat-process.c')
-rw-r--r-- | roms/skiboot/libstb/secvar/backend/edk2-compat-process.c | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/roms/skiboot/libstb/secvar/backend/edk2-compat-process.c b/roms/skiboot/libstb/secvar/backend/edk2-compat-process.c new file mode 100644 index 000000000..bd7a0abb3 --- /dev/null +++ b/roms/skiboot/libstb/secvar/backend/edk2-compat-process.c @@ -0,0 +1,801 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2020 IBM Corp. */ +#ifndef pr_fmt +#define pr_fmt(fmt) "EDK2_COMPAT: " fmt +#endif + +#include <opal.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <ccan/endian/endian.h> +#include <mbedtls/error.h> +#include <mbedtls/oid.h> +#include <device.h> +#include <assert.h> +#include "libstb/crypto/pkcs7/pkcs7.h" +#include "edk2.h" +#include "../secvar.h" +#include "edk2-compat-process.h" + +bool setup_mode; + +int update_variable_in_bank(struct secvar *update_var, const char *data, + const uint64_t dsize, struct list_head *bank) +{ + struct secvar *var; + + var = find_secvar(update_var->key, update_var->key_len, bank); + if (!var) + return OPAL_EMPTY; + + /* Reallocate the data memory, if there is change in data size */ + if (var->data_size < dsize) + if (realloc_secvar(var, dsize)) + return OPAL_NO_MEM; + + if (dsize && data) + memcpy(var->data, data, dsize); + var->data_size = dsize; + + /* Clear the volatile bit only if updated with positive data size */ + if (dsize) + var->flags &= ~SECVAR_FLAG_VOLATILE; + else + var->flags |= SECVAR_FLAG_VOLATILE; + + if (key_equals(update_var->key, "PK") || key_equals(update_var->key, "HWKH")) + var->flags |= SECVAR_FLAG_PROTECTED; + + return 0; +} + +/* Expand char to wide character size */ +static char *char_to_wchar(const char *key, const size_t keylen) +{ + int i; + char *str; + + str = zalloc(keylen * 2); + if (!str) + return NULL; + + for (i = 0; i < keylen*2; key++) { + str[i++] = *key; + str[i++] = '\0'; + } + + return str; +} + +/* Returns the authority that can sign the given key update */ +static void get_key_authority(const char *ret[3], const char *key) +{ + int i = 0; + + if (key_equals(key, "PK")) { + ret[i++] = "PK"; + } else if (key_equals(key, "KEK")) { + ret[i++] = "PK"; + } else if (key_equals(key, "db") || key_equals(key, "dbx")) { + ret[i++] = "KEK"; + ret[i++] = "PK"; + } + + ret[i] = NULL; +} + +static EFI_SIGNATURE_LIST* get_esl_signature_list(const char *buf, size_t buflen) +{ + EFI_SIGNATURE_LIST *list = NULL; + + if (buflen < sizeof(EFI_SIGNATURE_LIST) || !buf) + return NULL; + + list = (EFI_SIGNATURE_LIST *)buf; + + return list; +} + +/* Returns the size of the complete ESL. */ +static int32_t get_esl_signature_list_size(const char *buf, const size_t buflen) +{ + EFI_SIGNATURE_LIST *list = get_esl_signature_list(buf, buflen); + + if (!list) + return OPAL_PARAMETER; + + return le32_to_cpu(list->SignatureListSize); +} + +/* + * Copies the certificate from the ESL into cert buffer and returns the size + * of the certificate + */ +static int get_esl_cert(const char *buf, const size_t buflen, char **cert) +{ + size_t sig_data_offset; + size_t size; + EFI_SIGNATURE_LIST *list = get_esl_signature_list(buf, buflen); + + if (!list) + return OPAL_PARAMETER; + + assert(cert != NULL); + + if (le32_to_cpu(list->SignatureSize) <= sizeof(uuid_t)) + return OPAL_PARAMETER; + + size = le32_to_cpu(list->SignatureSize) - sizeof(uuid_t); + + prlog(PR_DEBUG,"size of signature list size is %u\n", + le32_to_cpu(list->SignatureListSize)); + prlog(PR_DEBUG, "size of signature header size is %u\n", + le32_to_cpu(list->SignatureHeaderSize)); + prlog(PR_DEBUG, "size of signature size is %u\n", + le32_to_cpu(list->SignatureSize)); + + sig_data_offset = sizeof(EFI_SIGNATURE_LIST) + + le32_to_cpu(list->SignatureHeaderSize) + + 16 * sizeof(uint8_t); + + /* Ensure this ESL does not overflow the bounds of the buffer */ + if (sig_data_offset + size > buflen) { + prlog(PR_ERR, "Number of bytes of ESL data is less than size specified\n"); + return OPAL_PARAMETER; + } + + *cert = zalloc(size); + if (!(*cert)) + return OPAL_NO_MEM; + + /* Since buf can have more than one ESL, copy only the size calculated + * to return single ESL */ + memcpy(*cert, buf + sig_data_offset, size); + + return size; +} + +/* + * Extracts size of the PKCS7 signed data embedded in the + * struct Authentication 2 Descriptor Header. + */ +static size_t get_pkcs7_len(const struct efi_variable_authentication_2 *auth) +{ + uint32_t dw_length; + size_t size; + + assert(auth != NULL); + + dw_length = le32_to_cpu(auth->auth_info.hdr.dw_length); + size = dw_length - (sizeof(auth->auth_info.hdr.dw_length) + + sizeof(auth->auth_info.hdr.w_revision) + + sizeof(auth->auth_info.hdr.w_certificate_type) + + sizeof(auth->auth_info.cert_type)); + + return size; +} + +int get_auth_descriptor2(const void *buf, const size_t buflen, void **auth_buffer) +{ + const struct efi_variable_authentication_2 *auth = buf; + int auth_buffer_size; + size_t len; + + assert(auth_buffer != NULL); + if (buflen < sizeof(struct efi_variable_authentication_2) + || !buf) + return OPAL_PARAMETER; + + len = get_pkcs7_len(auth); + /* pkcs7 content length cannot be greater than buflen */ + if (len > buflen) + return OPAL_PARAMETER; + + auth_buffer_size = sizeof(auth->timestamp) + sizeof(auth->auth_info.hdr) + + sizeof(auth->auth_info.cert_type) + len; + + if (auth_buffer_size > buflen) + return OPAL_PARAMETER; + + *auth_buffer = zalloc(auth_buffer_size); + if (!(*auth_buffer)) + return OPAL_NO_MEM; + + /* + * Data = auth descriptor + new ESL data. + * Extracts only the auth descriptor from data. + */ + memcpy(*auth_buffer, buf, auth_buffer_size); + + return auth_buffer_size; +} + +static bool validate_cert(char *signing_cert, int signing_cert_size) +{ + mbedtls_x509_crt x509; + char *x509_buf = NULL; + int rc; + + mbedtls_x509_crt_init(&x509); + rc = mbedtls_x509_crt_parse(&x509, signing_cert, signing_cert_size); + + /* If failure in parsing the certificate, exit */ + if(rc) { + prlog(PR_ERR, "X509 certificate parsing failed %04x\n", rc); + return false; + } + + x509_buf = zalloc(CERT_BUFFER_SIZE); + rc = mbedtls_x509_crt_info(x509_buf, CERT_BUFFER_SIZE, "CRT:", &x509); + + mbedtls_x509_crt_free(&x509); + free(x509_buf); + x509_buf = NULL; + + /* If failure in reading the certificate, exit */ + if (rc < 0) + return false; + + return true; +} + +static bool validate_hash(uuid_t type, int size) +{ + if (uuid_equals(&type, &EFI_CERT_SHA1_GUID) && (size == 20)) + return true; + + if (uuid_equals(&type, &EFI_CERT_SHA224_GUID) && (size == 28)) + return true; + + if (uuid_equals(&type, &EFI_CERT_SHA256_GUID) && (size == 32)) + return true; + + if (uuid_equals(&type, &EFI_CERT_SHA384_GUID) && (size == 48)) + return true; + + if (uuid_equals(&type, &EFI_CERT_SHA512_GUID) && (size == 64)) + return true; + + return false; +} + +int validate_esl_list(const char *key, const char *esl, const size_t size) +{ + int count = 0; + int dsize; + char *data = NULL; + int eslvarsize = size; + int eslsize; + int rc = OPAL_SUCCESS; + EFI_SIGNATURE_LIST *list = NULL; + + while (eslvarsize > 0) { + prlog(PR_DEBUG, "esl var size is %d offset is %lu\n", eslvarsize, size - eslvarsize); + if (eslvarsize < sizeof(EFI_SIGNATURE_LIST)) { + prlog(PR_ERR, "ESL with size %d is too small\n", eslvarsize); + rc = OPAL_PARAMETER; + break; + } + + /* Check Supported ESL Type */ + list = get_esl_signature_list(esl, eslvarsize); + + if (!list) + return OPAL_PARAMETER; + + /* Calculate the size of the ESL */ + eslsize = le32_to_cpu(list->SignatureListSize); + + /* If could not extract the size */ + if (eslsize <= 0) { + prlog(PR_ERR, "Invalid size of the ESL: %u\n", + le32_to_cpu(list->SignatureListSize)); + rc = OPAL_PARAMETER; + break; + } + + /* Extract the certificate from the ESL */ + dsize = get_esl_cert(esl, eslvarsize, &data); + if (dsize < 0) { + rc = dsize; + break; + } + + if (key_equals(key, "dbx")) { + if (!validate_hash(list->SignatureType, dsize)) { + prlog(PR_ERR, "No valid hash is found\n"); + rc = OPAL_PARAMETER; + break; + } + } else { + if (!uuid_equals(&list->SignatureType, &EFI_CERT_X509_GUID) + || !validate_cert(data, dsize)) { + prlog(PR_ERR, "No valid cert is found\n"); + rc = OPAL_PARAMETER; + break; + } + } + + count++; + + /* Look for the next ESL */ + esl = esl + eslsize; + eslvarsize = eslvarsize - eslsize; + free(data); + /* Since we are going to allocate again in the next iteration */ + data = NULL; + } + + if (rc == OPAL_SUCCESS) { + if (key_equals(key, "PK") && (count > 1)) { + prlog(PR_ERR, "PK can only be one\n"); + rc = OPAL_PARAMETER; + } else { + rc = count; + } + } + + free(data); + + prlog(PR_INFO, "Total ESLs are %d\n", rc); + return rc; +} + +/* Get the timestamp for the last update of the give key */ +static struct efi_time *get_last_timestamp(const char *key, char *last_timestamp) +{ + struct efi_time *timestamp = (struct efi_time*)last_timestamp; + + if (!last_timestamp) + return NULL; + + if (key_equals(key, "PK")) + return ×tamp[0]; + else if (key_equals(key, "KEK")) + return ×tamp[1]; + else if (key_equals(key, "db")) + return ×tamp[2]; + else if (key_equals(key, "dbx")) + return ×tamp[3]; + else + return NULL; +} + +int update_timestamp(const char *key, const struct efi_time *timestamp, char *last_timestamp) +{ + struct efi_time *prev; + + prev = get_last_timestamp(key, last_timestamp); + if (prev == NULL) + return OPAL_INTERNAL_ERROR; + + /* Update with new timestamp */ + memcpy(prev, timestamp, sizeof(struct efi_time)); + + prlog(PR_DEBUG, "updated prev year is %d month %d day %d\n", + le16_to_cpu(prev->year), prev->month, prev->day); + + return OPAL_SUCCESS; +} + +static uint64_t unpack_timestamp(const struct efi_time *timestamp) +{ + uint64_t val = 0; + uint16_t year = le16_to_cpu(timestamp->year); + + /* pad1, nanosecond, timezone, daylight and pad2 are meant to be zero */ + val |= ((uint64_t) timestamp->pad1 & 0xFF) << 0; + val |= ((uint64_t) timestamp->second & 0xFF) << (1*8); + val |= ((uint64_t) timestamp->minute & 0xFF) << (2*8); + val |= ((uint64_t) timestamp->hour & 0xFF) << (3*8); + val |= ((uint64_t) timestamp->day & 0xFF) << (4*8); + val |= ((uint64_t) timestamp->month & 0xFF) << (5*8); + val |= ((uint64_t) year) << (6*8); + + return val; +} + +int check_timestamp(const char *key, const struct efi_time *timestamp, + char *last_timestamp) +{ + struct efi_time *prev; + uint64_t new; + uint64_t last; + + prev = get_last_timestamp(key, last_timestamp); + if (prev == NULL) + return OPAL_INTERNAL_ERROR; + + prlog(PR_DEBUG, "timestamp year is %d month %d day %d\n", + le16_to_cpu(timestamp->year), timestamp->month, + timestamp->day); + prlog(PR_DEBUG, "prev year is %d month %d day %d\n", + le16_to_cpu(prev->year), prev->month, prev->day); + + new = unpack_timestamp(timestamp); + last = unpack_timestamp(prev); + + if (new > last) + return OPAL_SUCCESS; + + return OPAL_PERMISSION; +} + +/* Extract PKCS7 from the authentication header */ +static mbedtls_pkcs7* get_pkcs7(const struct efi_variable_authentication_2 *auth) +{ + char *checkpkcs7cert = NULL; + size_t len; + mbedtls_pkcs7 *pkcs7 = NULL; + int rc; + + len = get_pkcs7_len(auth); + + pkcs7 = malloc(sizeof(struct mbedtls_pkcs7)); + if (!pkcs7) + return NULL; + + mbedtls_pkcs7_init(pkcs7); + rc = mbedtls_pkcs7_parse_der( auth->auth_info.cert_data, len, pkcs7); + if (rc <= 0) { + prlog(PR_ERR, "Parsing pkcs7 failed %04x\n", rc); + goto out; + } + + checkpkcs7cert = zalloc(CERT_BUFFER_SIZE); + if (!checkpkcs7cert) + goto out; + + rc = mbedtls_x509_crt_info(checkpkcs7cert, CERT_BUFFER_SIZE, "CRT:", + &(pkcs7->signed_data.certs)); + if (rc < 0) { + prlog(PR_ERR, "Failed to parse the certificate in PKCS7 structure\n"); + free(checkpkcs7cert); + goto out; + } + + prlog(PR_DEBUG, "%s \n", checkpkcs7cert); + free(checkpkcs7cert); + return pkcs7; + +out: + mbedtls_pkcs7_free(pkcs7); + free(pkcs7); + pkcs7 = NULL; + return pkcs7; +} + +/* Verify the PKCS7 signature on the signed data. */ +static int verify_signature(const struct efi_variable_authentication_2 *auth, + const char *hash, const size_t hash_len, + const struct secvar *avar) +{ + mbedtls_pkcs7 *pkcs7 = NULL; + mbedtls_x509_crt x509; + mbedtls_md_type_t md_alg; + char *signing_cert = NULL; + char *x509_buf = NULL; + int signing_cert_size; + int rc = 0; + char *errbuf; + int eslvarsize; + int eslsize; + int offset = 0; + + if (!auth) + return OPAL_PARAMETER; + + /* Extract the pkcs7 from the auth structure */ + pkcs7 = get_pkcs7(auth); + /* Failure to parse pkcs7 implies bad input. */ + if (!pkcs7) + return OPAL_PARAMETER; + + /* + * We only support sha256, which has a hash length of 32. + * If the alg is not sha256, then we should bail now. + */ + rc = mbedtls_oid_get_md_alg(&pkcs7->signed_data.digest_alg_identifiers, + &md_alg); + if (rc != 0) { + prlog(PR_ERR, "Failed to get the Digest Algorithm Identifier: %d\n", rc); + rc = OPAL_PARAMETER; + goto err_pkcs7; + } + + if (md_alg != MBEDTLS_MD_SHA256) { + prlog(PR_ERR, "Unexpected digest algorithm: expected %d (SHA-256), got %d\n", + MBEDTLS_MD_SHA256, md_alg); + rc = OPAL_PARAMETER; + goto err_pkcs7; + } + + prlog(PR_INFO, "Load the signing certificate from the keystore"); + + eslvarsize = avar->data_size; + + /* Variable is not empty */ + while (eslvarsize > 0) { + prlog(PR_DEBUG, "esl var size is %d offset is %d\n", eslvarsize, offset); + if (eslvarsize < sizeof(EFI_SIGNATURE_LIST)) { + rc = OPAL_INTERNAL_ERROR; + prlog(PR_ERR, "ESL data is corrupted\n"); + break; + } + + /* Calculate the size of the ESL */ + eslsize = get_esl_signature_list_size(avar->data + offset, + eslvarsize); + /* If could not extract the size */ + if (eslsize <= 0) { + rc = OPAL_PARAMETER; + break; + } + + /* Extract the certificate from the ESL */ + signing_cert_size = get_esl_cert(avar->data + offset, + eslvarsize, &signing_cert); + if (signing_cert_size < 0) { + rc = signing_cert_size; + break; + } + + mbedtls_x509_crt_init(&x509); + rc = mbedtls_x509_crt_parse(&x509, + signing_cert, + signing_cert_size); + + /* This should not happen, unless something corrupted in PNOR */ + if(rc) { + prlog(PR_ERR, "X509 certificate parsing failed %04x\n", rc); + rc = OPAL_INTERNAL_ERROR; + break; + } + + x509_buf = zalloc(CERT_BUFFER_SIZE); + rc = mbedtls_x509_crt_info(x509_buf, + CERT_BUFFER_SIZE, + "CRT:", + &x509); + + /* This should not happen, unless something corrupted in PNOR */ + if (rc < 0) { + free(x509_buf); + rc = OPAL_INTERNAL_ERROR; + break; + } + + prlog(PR_INFO, "%s \n", x509_buf); + free(x509_buf); + x509_buf = NULL; + + rc = mbedtls_pkcs7_signed_hash_verify(pkcs7, &x509, hash, hash_len); + + /* If you find a signing certificate, you are done */ + if (rc == 0) { + prlog(PR_INFO, "Signature Verification passed\n"); + mbedtls_x509_crt_free(&x509); + break; + } else { + errbuf = zalloc(MBEDTLS_ERR_BUFFER_SIZE); + mbedtls_strerror(rc, errbuf, MBEDTLS_ERR_BUFFER_SIZE); + prlog(PR_ERR, "Signature Verification failed %02x %s\n", + rc, errbuf); + free(errbuf); + rc = OPAL_PERMISSION; + } + + + /* Look for the next ESL */ + offset = offset + eslsize; + eslvarsize = eslvarsize - eslsize; + mbedtls_x509_crt_free(&x509); + free(signing_cert); + /* Since we are going to allocate again in the next iteration */ + signing_cert = NULL; + + } + + free(signing_cert); +err_pkcs7: + mbedtls_pkcs7_free(pkcs7); + free(pkcs7); + + return rc; +} + +/* + * Create the hash of the buffer + * name || vendor guid || attributes || timestamp || newcontent + * which is submitted as signed by the user. + * Returns the sha256 hash, else NULL. + */ +static char *get_hash_to_verify(const char *key, const char *new_data, + const size_t new_data_size, + const struct efi_time *timestamp) +{ + le32 attr = cpu_to_le32(SECVAR_ATTRIBUTES); + size_t varlen; + char *wkey; + uuid_t guid; + unsigned char *hash = NULL; + const mbedtls_md_info_t *md_info; + mbedtls_md_context_t ctx; + int rc; + + md_info = mbedtls_md_info_from_type( MBEDTLS_MD_SHA256 ); + mbedtls_md_init(&ctx); + + rc = mbedtls_md_setup(&ctx, md_info, 0); + if (rc) + goto out; + + rc = mbedtls_md_starts(&ctx); + if (rc) + goto out; + + if (key_equals(key, "PK") + || key_equals(key, "KEK")) + guid = EFI_GLOBAL_VARIABLE_GUID; + else if (key_equals(key, "db") + || key_equals(key, "dbx")) + guid = EFI_IMAGE_SECURITY_DATABASE_GUID; + else + return NULL; + + /* Expand char name to wide character width */ + varlen = strlen(key) * 2; + wkey = char_to_wchar(key, strlen(key)); + rc = mbedtls_md_update(&ctx, wkey, varlen); + free(wkey); + if (rc) + goto out; + + rc = mbedtls_md_update(&ctx, (const unsigned char *)&guid, sizeof(guid)); + if (rc) + goto out; + + rc = mbedtls_md_update(&ctx, (const unsigned char *)&attr, sizeof(attr)); + if (rc) + goto out; + + rc = mbedtls_md_update(&ctx, (const unsigned char *)timestamp, + sizeof(struct efi_time)); + if (rc) + goto out; + + rc = mbedtls_md_update(&ctx, new_data, new_data_size); + if (rc) + goto out; + + hash = zalloc(32); + if (!hash) + return NULL; + rc = mbedtls_md_finish(&ctx, hash); + if (rc) { + free(hash); + hash = NULL; + } + +out: + mbedtls_md_free(&ctx); + return hash; +} + +bool is_pkcs7_sig_format(const void *data) +{ + const struct efi_variable_authentication_2 *auth = data; + uuid_t pkcs7_guid = EFI_CERT_TYPE_PKCS7_GUID; + + return !memcmp(&auth->auth_info.cert_type, &pkcs7_guid, 16); +} + +int process_update(const struct secvar *update, char **newesl, + int *new_data_size, struct efi_time *timestamp, + struct list_head *bank, char *last_timestamp) +{ + struct efi_variable_authentication_2 *auth = NULL; + void *auth_buffer = NULL; + int auth_buffer_size = 0; + const char *key_authority[3]; + char *hash = NULL; + struct secvar *avar = NULL; + int rc = 0; + int i; + + /* We need to split data into authentication descriptor and new ESL */ + auth_buffer_size = get_auth_descriptor2(update->data, + update->data_size, + &auth_buffer); + if ((auth_buffer_size < 0) + || (update->data_size < auth_buffer_size)) { + prlog(PR_ERR, "Invalid auth buffer size\n"); + rc = auth_buffer_size; + goto out; + } + + auth = auth_buffer; + + if (!timestamp) { + rc = OPAL_INTERNAL_ERROR; + goto out; + } + + memcpy(timestamp, auth_buffer, sizeof(struct efi_time)); + + rc = check_timestamp(update->key, timestamp, last_timestamp); + /* Failure implies probably an older command being resubmitted */ + if (rc != OPAL_SUCCESS) { + prlog(PR_ERR, "Timestamp verification failed for key %s\n", update->key); + goto out; + } + + /* Calculate the size of new ESL data */ + *new_data_size = update->data_size - auth_buffer_size; + if (*new_data_size < 0) { + prlog(PR_ERR, "Invalid new ESL (new data content) size\n"); + rc = OPAL_PARAMETER; + goto out; + } + *newesl = zalloc(*new_data_size); + if (!(*newesl)) { + rc = OPAL_NO_MEM; + goto out; + } + memcpy(*newesl, update->data + auth_buffer_size, *new_data_size); + + /* Validate the new ESL is in right format */ + rc = validate_esl_list(update->key, *newesl, *new_data_size); + if (rc < 0) { + prlog(PR_ERR, "ESL validation failed for key %s with error %04x\n", + update->key, rc); + goto out; + } + + if (setup_mode) { + rc = OPAL_SUCCESS; + goto out; + } + + /* Prepare the data to be verified */ + hash = get_hash_to_verify(update->key, *newesl, *new_data_size, + timestamp); + if (!hash) { + rc = OPAL_INTERNAL_ERROR; + goto out; + } + + /* Get the authority to verify the signature */ + get_key_authority(key_authority, update->key); + + /* + * Try for all the authorities that are allowed to sign. + * For eg. db/dbx can be signed by both PK or KEK + */ + for (i = 0; key_authority[i] != NULL; i++) { + prlog(PR_DEBUG, "key is %s\n", update->key); + prlog(PR_DEBUG, "key authority is %s\n", key_authority[i]); + avar = find_secvar(key_authority[i], + strlen(key_authority[i]) + 1, + bank); + if (!avar || !avar->data_size) + continue; + + /* Verify the signature. sha256 is 32 bytes long. */ + rc = verify_signature(auth, hash, 32, avar); + + /* Break if signature verification is successful */ + if (rc == OPAL_SUCCESS) { + prlog(PR_INFO, "Key %s successfully verified by authority %s\n", update->key, key_authority[i]); + break; + } + } + +out: + free(auth_buffer); + free(hash); + + return rc; +} |