/* * PV image related definitions and functions * * Copyright IBM Corp. 2020 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include "boot/stage3a.h" #include "common.h" #include "include/pv_crypto_def.h" #include "include/pv_hdr_def.h" #include "utils/align.h" #include "utils/crypto.h" #include "utils/file_utils.h" #include "pv_args.h" #include "pv_comps.h" #include "pv_error.h" #include "pv_hdr.h" #include "pv_image.h" #include "pv_ipib.h" #include "pv_opt_item.h" #include "pv_stage3.h" const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err) { const PvComponent *comp; g_return_val_if_fail(pv_img_comps_length(img->comps) >= 1, NULL); comp = pv_img_comps_get_nth_comp(img->comps, pv_img_comps_length(img->comps) - 1); if (!pv_component_is_stage3b(comp)) { g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, _("Failed to get 'stage3b' component")); return NULL; } return comp; } typedef gint (*prepare_func)(PvComponent *obj, const gchar *tmp_path, void *opaque, GError **err); static gint pv_img_prepare_component(const PvImage *img, PvComponent *comp, GError **err) { struct cipher_parms parms = { 0 }; g_autoptr(Buffer) tweak = NULL; prepare_func func = NULL; void *opaque = NULL; gint rc; if (img->pcf & PV_CFLAG_NO_DECRYPTION) { /* we only need to align the components */ func = pv_component_align; opaque = NULL; } else { const EVP_CIPHER *cipher = img->xts_cipher; g_assert_cmpint((int)img->xts_key->size, ==, EVP_CIPHER_key_length(cipher)); g_assert_cmpint((int)PAGE_SIZE % EVP_CIPHER_block_size(cipher), ==, 0); g_assert_cmpint(sizeof(comp->tweak), ==, EVP_CIPHER_iv_length(cipher)); g_assert(img->xts_key->size <= UINT_MAX); tweak = buffer_alloc(sizeof(comp->tweak.data)); memcpy(tweak->data, comp->tweak.data, tweak->size); func = pv_component_align_and_encrypt; parms.cipher = cipher; parms.key = img->xts_key; parms.iv_or_tweak = tweak; opaque = &parms; } rc = (*func)(comp, img->tmp_dir, opaque, err); if (rc < 0) return -1; return 0; } static Buffer *pv_img_read_key(const gchar *path, guint key_size, GError **err) { g_autoptr(Buffer) tmp_ret = NULL; Buffer *ret = NULL; gsize bytes_read; FILE *f = NULL; gsize size; if (file_size(path, &size, err) != 0) return NULL; if (size - key_size != 0) { g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, _("Wrong file size '%s': read %zd, expected %u"), path, size, key_size); return NULL; } f = file_open(path, "rb", err); if (!f) return NULL; tmp_ret = buffer_alloc(size); if (file_read(f, tmp_ret->data, 1, tmp_ret->size, &bytes_read, err) < 0) goto err; if (bytes_read - key_size != 0) { g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, _("Wrong file size '%s': read %zd, expected %u"), path, bytes_read, key_size); goto err; } ret = g_steal_pointer(&tmp_ret); err: if (f) fclose(f); return ret; } static EVP_PKEY *pv_img_get_cust_pub_priv_key(gint nid, GError **err) { return generate_ec_key(nid, err); } static HostKeyList *pv_img_get_host_keys(gchar **host_cert_paths, X509_STORE *store, gint nid, GError **err) { g_autoslist(EVP_PKEY) ret = NULL; g_assert(host_cert_paths); for (gchar **iterator = host_cert_paths; iterator != NULL && *iterator != NULL; iterator++) { g_autoptr(EVP_PKEY) host_key = NULL; const gchar *path = *iterator; g_assert(path); host_key = read_ec_pubkey_cert(store, nid, path, err); if (!host_key) return NULL; ret = g_slist_append(ret, g_steal_pointer(&host_key)); } return g_steal_pointer(&ret); } static Buffer *pv_img_get_key(const EVP_CIPHER *cipher, const gchar *path, GError **err) { gint key_len = EVP_CIPHER_key_length(cipher); g_assert(key_len > 0); if (path) return pv_img_read_key(path, (guint)key_len, err); return generate_aes_key((guint)key_len, err); } static Buffer *pv_img_get_iv(const EVP_CIPHER *cipher, const gchar *path, GError **err) { gint iv_len = EVP_CIPHER_iv_length(cipher); g_assert(iv_len > 0); if (path) return pv_img_read_key(path, (guint)iv_len, err); return generate_aes_iv((guint)iv_len, err); } static int hex_str_toull(const gchar *nptr, uint64_t *dst, GError **err) { uint64_t value; gchar *end; g_assert(dst); if (!g_str_is_ascii(nptr)) { g_set_error(err, PV_ERROR, EINVAL, _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), nptr); return -1; } value = g_ascii_strtoull(nptr, &end, 16); if ((value == G_MAXUINT64 && errno == ERANGE) || (end && *end != '\0')) { g_set_error(err, PV_ERROR, EINVAL, _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), nptr); return -1; } *dst = value; return 0; } static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s, GError **err) { if (psw_addr_s) { uint64_t psw_addr; if (hex_str_toull(psw_addr_s, &psw_addr, err) < 0) return -1; img->initial_psw.addr = psw_addr; } return 0; } static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s, const gchar *scf_s, GError **err) { uint64_t flags; if (pcf_s) { if (hex_str_toull(pcf_s, &flags, err) < 0) return -1; img->pcf = flags; } if (scf_s) { if (hex_str_toull(scf_s, &flags, err) < 0) return -1; img->scf = flags; } return 0; } /* read in the keys or auto-generate them */ static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) { g_autoptr(X509_STORE) store = NULL; g_assert(img->xts_cipher); g_assert(img->cust_comm_cipher); g_assert(img->gcm_cipher); g_assert(img->nid); img->xts_key = pv_img_get_key(img->xts_cipher, args->xts_key_path, err); if (!img->xts_key) return -1; img->cust_comm_key = pv_img_get_key(img->cust_comm_cipher, args->cust_comm_key_path, err); if (!img->cust_comm_key) return -1; img->cust_root_key = pv_img_get_key(img->gcm_cipher, args->cust_root_key_path, err); if (!img->cust_root_key) return -1; img->gcm_iv = pv_img_get_iv(img->gcm_cipher, args->gcm_iv_path, err); if (!img->gcm_iv) return -1; img->cust_pub_priv_key = pv_img_get_cust_pub_priv_key(img->nid, err); if (!img->cust_pub_priv_key) return -1; img->host_pub_keys = pv_img_get_host_keys(args->host_keys, store, img->nid, err); if (!img->host_pub_keys) return -1; return 0; } static void pv_img_add_host_slot(PvImage *img, PvHdrKeySlot *slot) { img->key_slots = g_slist_append(img->key_slots, slot); } static void pv_hdr_key_slot_free(PvHdrKeySlot *slot) { if (!slot) return; g_free(slot); } WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdrKeySlot, pv_hdr_key_slot_free) static PvHdrKeySlot *pv_hdr_key_slot_new(const EVP_CIPHER *gcm_cipher, const Buffer *cust_root_key, EVP_PKEY *cust_key, EVP_PKEY *host_key, GError **err) { g_autoptr(PvHdrKeySlot) ret = g_new0(PvHdrKeySlot, 1); g_autofree union ecdh_pub_key *pub = NULL; g_autoptr(Buffer) exchange_key = NULL; g_autoptr(Buffer) digest_key = NULL; g_autoptr(Buffer) iv = NULL; Buffer pub_buf; /* No AAD data is used */ Buffer aad = { .data = NULL, .size = 0 }; /* Set the output buffers for the encrypted data and the * generated GCM tag */ Buffer enc = { .data = ret->wrapped_key, .size = sizeof(ret->wrapped_key) }; Buffer tag = { .data = ret->tag, .size = sizeof(ret->tag) }; struct cipher_parms parms; int64_t c_len = 0; g_assert(EVP_CIPHER_iv_length(gcm_cipher) >= 0); pub = evp_pkey_to_ecdh_pub_key(host_key, err); if (!pub) return NULL; pub_buf.data = pub->data; pub_buf.size = sizeof(*pub); digest_key = sha256_buffer(&pub_buf, err); if (!digest_key) return NULL; g_assert(digest_key->size == sizeof(ret->digest_key)); /* set `digest_key` field */ memcpy(ret->digest_key, digest_key->data, sizeof(ret->digest_key)); exchange_key = compute_exchange_key(cust_key, host_key, err); if (!exchange_key) return NULL; /* initialize cipher parameters */ g_assert(exchange_key->size <= INT_MAX); g_assert(exchange_key->size == (guint)EVP_CIPHER_key_length(gcm_cipher)); /* create zero IV */ iv = buffer_alloc((guint)EVP_CIPHER_iv_length(gcm_cipher)); parms.iv_or_tweak = iv; parms.key = exchange_key; parms.cipher = gcm_cipher; /* Encrypt the customer root key that is used for the encryption * of the PV header */ c_len = gcm_encrypt(cust_root_key, &aad, &parms, &enc, &tag, err); if (c_len < 0) return NULL; g_assert(c_len == (int64_t)cust_root_key->size); return g_steal_pointer(&ret); } static gint pv_img_set_host_slots(PvImage *img, GError **err) { for (GSList *iterator = img->host_pub_keys; iterator; iterator = iterator->next) { EVP_PKEY *host_key = iterator->data; g_assert(host_key); PvHdrKeySlot *slot = pv_hdr_key_slot_new(img->gcm_cipher, img->cust_root_key, img->cust_pub_priv_key, host_key, err); if (!slot) return -1; pv_img_add_host_slot(img, slot); } return 0; } static gint pv_img_set_comps_offset(PvImage *img, uint64_t offset, GError **err) { return pv_img_comps_set_offset(img->comps, offset, err); } PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err) { g_autoptr(PvImage) ret = g_new0(PvImage, 1); uint64_t offset; g_assert(args->tmp_dir); g_assert(stage3a_path); if (args->no_verify) g_warning(_("host-key document verification is disabled. Your workload is not secured.")); ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err); if (!ret->comps) return NULL; ret->cust_comm_cipher = EVP_aes_256_gcm(); ret->gcm_cipher = EVP_aes_256_gcm(); ret->initial_psw.addr = DEFAULT_INITIAL_PSW_ADDR; ret->initial_psw.mask = DEFAULT_INITIAL_PSW_MASK; ret->nid = NID_secp521r1; ret->tmp_dir = g_strdup(args->tmp_dir); ret->xts_cipher = EVP_aes_256_xts(); /* set initial PSW that will be loaded by the stage3b */ if (pv_img_set_psw_addr(ret, args->psw_addr, err) < 0) return NULL; /* set the control flags: PCF and SCF */ if (pv_img_set_control_flags(ret, args->pcf, args->scf, err) < 0) return NULL; /* read in the keys */ if (pv_img_set_keys(ret, args, err) < 0) return NULL; if (pv_img_set_host_slots(ret, err) < 0) return NULL; /* allocate enough memory for the stage3a args and load the * stage3a template into memory and set the loader_psw */ if (pv_img_load_and_set_stage3a(ret, stage3a_path, err) < 0) return NULL; offset = PAGE_ALIGN(STAGE3A_LOAD_ADDRESS + ret->stage3a->size); /* shift right all components by the size of stage3a loader */ if (pv_img_set_comps_offset(ret, offset, err) < 0) return NULL; return g_steal_pointer(&ret); } void pv_img_free(PvImage *img) { if (!img) return; g_slist_free_full(img->optional_items, (GDestroyNotify)pv_opt_item_free); g_slist_free_full(img->key_slots, (GDestroyNotify)pv_hdr_key_slot_free); g_slist_free_full(img->host_pub_keys, (GDestroyNotify)EVP_PKEY_free); EVP_PKEY_free(img->cust_pub_priv_key); buffer_clear(&img->stage3a); pv_img_comps_free(img->comps); g_free(img->tmp_dir); buffer_free(img->xts_key); buffer_free(img->cust_root_key); buffer_free(img->gcm_iv); buffer_free(img->cust_comm_key); g_free(img); } static gint pv_img_prepare_and_add_component(PvImage *img, PvComponent **comp, GError **err) { g_assert(comp); g_assert(*comp); /* prepares the component: does the alignment and encryption * if required */ if (pv_img_prepare_component(img, *comp, err) < 0) return -1; /* calculates the memory layout and adds the component to its * internal list */ if (pv_img_comps_add_component(img->comps, comp, err) < 0) return -1; g_assert(!*comp); return 0; } gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err) { g_autoptr(PvComponent) comp = NULL; comp = pv_component_new_file(arg->type, arg->path, err); if (!comp) return -1; if (pv_img_prepare_and_add_component(img, &comp, err) < 0) return -1; g_assert(!comp); return 0; } gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald, Buffer **tld, uint64_t *nep, GError **err) { return pv_img_comps_finalize(img->comps, pld, ald, tld, nep, err); } static gint pv_img_build_stage3b(PvImage *img, Buffer *stage3b, GError **err) { g_autofree struct stage3b_args *args = NULL; args = pv_img_comps_get_stage3b_args(img->comps, &img->initial_psw); if (!args) { g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, _("Cannot generate stage3b arguments")); return -1; } build_stage3b(stage3b, args); return 0; } gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err) { g_autoptr(PvComponent) comp = NULL; g_autoptr(Buffer) stage3b = NULL; stage3b = stage3b_getblob(path, err); if (!stage3b) return -1; /* set the stage3b data */ if (pv_img_build_stage3b(img, stage3b, err) < 0) return -1; comp = pv_component_new_buf(PV_COMP_TYPE_STAGE3B, stage3b, err); if (!comp) return -1; if (pv_img_prepare_and_add_component(img, &comp, err) < 0) return -1; g_assert(!comp); return 0; } static uint32_t pv_img_get_aad_size(const PvImage *img) { uint32_t key_size, size = 0; g_assert(sizeof(struct pv_hdr_head) <= UINT32_MAX); g_assert(sizeof(struct pv_hdr_key_slot) <= UINT32_MAX); g_assert_true(g_uint_checked_add(&size, size, (uint32_t)sizeof(struct pv_hdr_head))); g_assert_true(g_uint_checked_mul(&key_size, (uint32_t)sizeof(struct pv_hdr_key_slot), g_slist_length(img->key_slots))); g_assert_true(g_uint_checked_add(&size, size, key_size)); return size; } static uint32_t pv_img_get_opt_items_size(const PvImage *img) { uint32_t ret = 0; g_assert(img); for (GSList *iterator = img->optional_items; iterator; iterator = iterator->next) { const struct pv_hdr_opt_item *item = iterator->data; g_assert(item); g_assert_true(g_uint_checked_add(&ret, ret, pv_opt_item_size(item))); } return ret; } uint32_t pv_img_get_enc_size(const PvImage *img) { uint32_t ret = 0; g_assert(sizeof(struct pv_hdr_encrypted) <= UINT32_MAX); g_assert_true(g_uint_checked_add( &ret, ret, (uint32_t)sizeof(struct pv_hdr_encrypted))); g_assert_true( g_uint_checked_add(&ret, ret, pv_img_get_opt_items_size(img))); return ret; } static uint32_t pv_img_get_tag_size(const PvImage *img G_GNUC_UNUSED) { g_assert(sizeof(((struct pv_hdr *)0)->tag) <= UINT32_MAX); return (uint32_t)sizeof(((struct pv_hdr *)0)->tag); } uint32_t pv_img_get_pv_hdr_size(const PvImage *img) { uint32_t size = 0; g_assert_true( g_uint_checked_add(&size, size, pv_img_get_aad_size(img))); g_assert_true( g_uint_checked_add(&size, size, pv_img_get_enc_size(img))); g_assert_true( g_uint_checked_add(&size, size, pv_img_get_tag_size(img))); return size; } static gint get_stage3a_data_size(const PvImage *img, gsize *data_size, GError **err) { gsize ipib_size, hdr_size; g_assert(data_size); g_assert(*data_size == 0); ipib_size = pv_ipib_get_size(pv_img_comps_length(img->comps)); if (ipib_size > PV_V1_IPIB_MAX_SIZE) { g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE, _("IPIB size is too large: '%zu' > '%zu'"), ipib_size, PV_V1_IPIB_MAX_SIZE); return -1; } hdr_size = pv_img_get_pv_hdr_size(img); if (hdr_size > PV_V1_PV_HDR_MAX_SIZE) { g_set_error(err, PV_ERROR, PV_ERROR_PV_HDR_SIZE, _("PV header size is too large: '%zu' > '%zu'"), hdr_size, PV_V1_PV_HDR_MAX_SIZE); return -1; } *data_size += PAGE_ALIGN(ipib_size); *data_size += PAGE_ALIGN(hdr_size); return 0; } gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err) { g_autoptr(Buffer) stage3a = NULL; gsize bin_size, data_size = 0; if (get_stage3a_data_size(img, &data_size, err) < 0) return -1; stage3a = stage3a_getblob(path, &bin_size, data_size, err); if (!stage3a) return -1; img->stage3a_psw.addr = STAGE3A_ENTRY; img->stage3a_psw.mask = DEFAULT_INITIAL_PSW_MASK; /* set addresses and size */ img->stage3a = g_steal_pointer(&stage3a); img->stage3a_bin_size = bin_size; return 0; } /* Creates the PV IPIB and sets the stage3a arguments */ static gint pv_img_build_stage3a(Buffer *stage3a, gsize stage3a_bin_size, GSList *comps, const Buffer *hdr, GError **err) { g_autofree struct ipl_parameter_block *ipib = NULL; g_assert(stage3a); g_assert(hdr); ipib = pv_ipib_new(comps, hdr, err); if (!ipib) return -1; if (build_stage3a(stage3a, stage3a_bin_size, hdr, ipib, err) < 0) return -1; g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", "stage3a", STAGE3A_LOAD_ADDRESS, stage3a->size, stage3a->size); return 0; } /* Creates the actual PV header (serialized and AES-GCM encrypted) */ static Buffer *pv_img_create_pv_hdr(PvImage *img, GError **err) { g_autoptr(Buffer) hdr_buf = NULL; g_autoptr(PvHdr) hdr = NULL; hdr = pv_hdr_new(img, err); if (!hdr) return NULL; hdr_buf = pv_hdr_serialize(hdr, img, PV_ENCRYPT, err); if (!hdr_buf) return NULL; return g_steal_pointer(&hdr_buf); } /* No changes to the components are allowed after calling this * function */ gint pv_img_finalize(PvImage *pv, const gchar *stage3b_path, GError **err) { g_autoptr(Buffer) hdr = NULL; /* load stage3b template into memory and add it to the list of * components. This must be done before calling * `pv_img_load_and_set_stage3a`. */ if (pv_img_add_stage3b_comp(pv, stage3b_path, err) < 0) return -1; /* create the PV header */ hdr = pv_img_create_pv_hdr(pv, err); if (!hdr) return -1; /* generate stage3a. At this point in time the PV header and * the stage3b must be generated and encrypted */ if (pv_img_build_stage3a(pv->stage3a, pv->stage3a_bin_size, pv_img_comps_get_comps(pv->comps), hdr, err) < 0) return -1; return 0; } static gint convert_psw_to_short_psw(const struct psw_t *psw, uint64_t *dst, GError **err) { g_assert(psw); g_assert(dst); uint64_t psw_addr = psw->addr; uint64_t psw_mask = psw->mask; /* test if PSW mask can be converted */ if (psw_mask & PSW_SHORT_ADDR_MASK) { g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, _("Failed to convert PSW to short PSW")); return -1; } /* test for bit 12 */ if (psw_mask & PSW_MASK_BIT_12) { g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, _("Failed to convert PSW to short PSW")); return -1; } /* test if PSW addr can be converted */ if (psw_addr & ~PSW_SHORT_ADDR_MASK) { g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, _("Failed to convert PSW to short PSW")); return -1; } *dst = psw_mask; /* set bit 12 to 1 */ *dst |= PSW_MASK_BIT_12; *dst |= psw_addr; return 0; } static gint write_short_psw(FILE *f, struct psw_t *psw, GError **err) { uint64_t short_psw, short_psw_be; if (convert_psw_to_short_psw(psw, &short_psw, err) < 0) return -1; short_psw_be = GUINT64_TO_BE(short_psw); return file_write(f, &short_psw_be, 1, sizeof(short_psw_be), NULL, err); } gint pv_img_write(PvImage *img, const gchar *path, GError **err) { gint ret = -1; FILE *f = file_open(path, "wb", err); if (!f) return -1; if (write_short_psw(f, &img->stage3a_psw, err) < 0) { g_prefix_error(err, _("Failed to write image '%s': "), path); goto err; } if (seek_and_write_buffer(f, img->stage3a, STAGE3A_LOAD_ADDRESS, err) < 0) { g_prefix_error(err, _("Failed to write image '%s': "), path); goto err; } /* list is sorted by component type => by address */ for (GSList *iterator = pv_img_comps_get_comps(img->comps); iterator; iterator = iterator->next) { gint rc; const PvComponent *comp = iterator->data; rc = pv_component_write(comp, f, err); if (rc < 0) { g_prefix_error(err, _("Failed to write image '%s': "), path); goto err; } } ret = 0; err: if (f) fclose(f); return ret; }