/* * PV header related 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/psw.h" #include "include/pv_crypto_def.h" #include "utils/buffer.h" #include "utils/crypto.h" #include "pv_comp.h" #include "pv_hdr.h" #include "pv_image.h" void pv_hdr_free(PvHdr *hdr) { if (!hdr) return; g_free(hdr->optional_items); g_free(hdr->encrypted); g_free(hdr->slots); g_free(hdr); } uint32_t pv_hdr_size(const PvHdr *hdr) { return GUINT32_FROM_BE(hdr->head.phs); } gboolean pv_hdr_uses_encryption(const PvHdr *hdr) { return !(GUINT64_FROM_BE(hdr->head.pcf) & PV_PCF_NO_DECRYPTION); } uint64_t pv_hdr_enc_size(const PvHdr *hdr) { return GUINT64_FROM_BE(hdr->head.sea); } uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr) { uint64_t size = pv_hdr_enc_size(hdr); if (size > UINT32_MAX) g_abort(); return (uint32_t)size; } static guint pv_hdr_tag_size(const PvHdr *hdr) { return sizeof(hdr->tag); } uint32_t pv_hdr_aad_size(const PvHdr *hdr) { return pv_hdr_size(hdr) - pv_hdr_enc_size_casted(hdr) - pv_hdr_tag_size(hdr); } uint64_t pv_hdr_get_nks(const PvHdr *hdr) { return GUINT64_FROM_BE(hdr->head.nks); } /* In-place modification of ``buf`` */ static gint pv_hdr_encrypt(const PvHdr *hdr, const PvImage *img, PvBuffer *buf, GError **err) { uint32_t hdr_len = pv_hdr_size(hdr); uint32_t aad_len = pv_hdr_aad_size(hdr); guint tag_len = pv_hdr_tag_size(hdr); uint32_t enc_len = pv_hdr_enc_size_casted(hdr); const PvBuffer aad_part = { .data = buf->data, .size = aad_len }; PvBuffer enc_part = { .data = (uint8_t *)buf->data + aad_len, .size = enc_len }; PvBuffer tag_part = { .data = (uint8_t *)buf->data + hdr_len - tag_len, .size = tag_len }; struct cipher_parms parms; int64_t c_len; g_assert(aad_part.size + enc_part.size + tag_part.size == buf->size); g_assert(img->cust_root_key->size <= INT_MAX); g_assert(img->gcm_iv->size <= INT_MAX); g_assert(EVP_CIPHER_key_length(img->gcm_cipher) == (int)img->cust_root_key->size); g_assert(EVP_CIPHER_iv_length(img->gcm_cipher) == (int)img->gcm_iv->size); parms.key = img->cust_root_key; parms.iv_or_tweak = img->gcm_iv; parms.cipher = img->gcm_cipher; /* in-place encryption */ c_len = gcm_encrypt(&enc_part, &aad_part, &parms, &enc_part, &tag_part, err); if (c_len < 0) return -1; g_assert(c_len == enc_len); return 0; } /* Initializes the unencrypted, but integrity protected part of the PV * header */ static gint pv_hdr_aad_init(PvHdr *hdr, const PvImage *img, GError **err) { g_autofree union ecdh_pub_key *cust_pub_key = NULL; struct pv_hdr_key_slot *hdr_slot = hdr->slots; struct pv_hdr_head *head = &hdr->head; g_autoptr(PvBuffer) pld = NULL; g_autoptr(PvBuffer) ald = NULL; g_autoptr(PvBuffer) tld = NULL; uint64_t nep = 0; g_assert(sizeof(head->iv) == img->gcm_iv->size); g_assert(sizeof(head->cust_pub_key) == sizeof(*cust_pub_key)); cust_pub_key = evp_pkey_to_ecdh_pub_key(img->cust_pub_priv_key, err); if (!cust_pub_key) return -1; head->magic = GUINT64_TO_BE(PV_MAGIC_NUMBER); head->version = GUINT32_TO_BE(PV_VERSION_1); /* ``phs`` is already set so we can skip it here */ memcpy(head->iv, img->gcm_iv->data, sizeof(head->iv)); /* ``nks`` is already set so we can skip it here */ /* ``sea`` is already set so we can skip it here */ head->pcf = GUINT64_TO_BE(img->pcf); memcpy(head->cust_pub_key.data, cust_pub_key, sizeof(head->cust_pub_key)); if (pv_img_calc_pld_ald_tld_nep(img, &pld, &ald, &tld, &nep, err) < 0) return -1; g_assert(sizeof(head->pld) == pld->size); g_assert(sizeof(head->ald) == ald->size); g_assert(sizeof(head->tld) == tld->size); head->nep = GUINT64_TO_BE(nep); memcpy(head->pld, pld->data, sizeof(head->pld)); memcpy(head->ald, ald->data, sizeof(head->ald)); memcpy(head->tld, tld->data, sizeof(head->tld)); /* set the key slots */ for (GSList *iterator = img->key_slots; iterator; iterator = iterator->next) { const PvHdrKeySlot *slot = iterator->data; g_assert(slot); /* the memory for the slots is pre-allocated so we * have not to allocate and since PvHdrKeySlot is * stored in the big-edian format we can simply use * memcpy. */ memcpy(hdr_slot++, slot, sizeof(*slot)); } return 0; } /* Initializes the encrypted and also integrity protected part of the * PV header */ static gint pv_hdr_enc_init(PvHdr *hdr, const PvImage *img, GError **err) { struct pv_hdr_encrypted *enc = hdr->encrypted; const PvComponent *stage3b; struct psw_t psw; g_assert(sizeof(enc->img_enc_key_1) + sizeof(enc->img_enc_key_2) == EVP_CIPHER_key_length(img->xts_cipher)); g_assert(sizeof(enc->cust_comm_key) == img->cust_comm_key->size); g_assert(img->xts_key->size == (guint)EVP_CIPHER_key_length(img->xts_cipher)); stage3b = pv_img_get_stage3b_comp(img, err); if (!stage3b) return -1; memcpy(enc->cust_comm_key, img->cust_comm_key->data, sizeof(enc->cust_comm_key)); memcpy(enc->img_enc_key_1, img->xts_key->data, sizeof(enc->img_enc_key_1)); memcpy(enc->img_enc_key_2, (uint8_t *)img->xts_key->data + sizeof(enc->img_enc_key_1), sizeof(enc->img_enc_key_2)); /* Setup program check handler */ psw.mask = GUINT64_TO_BE(DEFAULT_INITIAL_PSW_MASK); psw.addr = GUINT64_TO_BE(pv_component_get_src_addr(stage3b)); enc->psw = psw; enc->scf = GUINT64_TO_BE(img->scf); enc->noi = GUINT32_TO_BE(g_slist_length(img->optional_items)); /* set the optional items */ for (GSList *iterator = img->optional_items; iterator; iterator = iterator->next) { const struct pv_hdr_opt_item *item = iterator->data; g_assert(item); /* not supported in the first version */ g_assert_not_reached(); } return 0; } PvHdr *pv_hdr_new(const PvImage *img, GError **err) { uint32_t noi = g_slist_length(img->optional_items); uint32_t hdr_size = pv_img_get_pv_hdr_size(img); gsize nks = g_slist_length(img->key_slots); uint32_t sea = pv_img_get_enc_size(img); g_autoptr(PvHdr) ret = NULL; g_assert(nks > 0); /* must be a multiple of AES block size */ g_assert(sea % AES_BLOCK_SIZE == 0); g_assert(sea >= sizeof(struct pv_hdr_encrypted)); ret = g_new0(PvHdr, 1); ret->slots = g_new0(struct pv_hdr_key_slot, nks); ret->head.phs = GUINT32_TO_BE(hdr_size); ret->head.nks = GUINT64_TO_BE(nks); ret->head.sea = GUINT64_TO_BE(sea); ret->encrypted = g_new0(struct pv_hdr_encrypted, 1); ret->optional_items = g_malloc0(sea - sizeof(struct pv_hdr_encrypted)); ret->encrypted->noi = GUINT32_TO_BE(noi); if (pv_hdr_aad_init(ret, img, err) < 0) return NULL; if (pv_hdr_enc_init(ret, img, err) < 0) return NULL; return g_steal_pointer(&ret); } static void pv_hdr_memcpy(const PvHdr *hdr, const PvBuffer *dst) { uint64_t nks = pv_hdr_get_nks(hdr); uint8_t *data; g_assert(dst->size == pv_hdr_size(hdr)); g_assert(pv_hdr_enc_size_casted(hdr) >= sizeof(*hdr->encrypted)); data = memcpy(dst->data, &hdr->head, sizeof(hdr->head)); data = memcpy(data + sizeof(hdr->head), hdr->slots, sizeof(struct pv_hdr_key_slot) * nks); data = memcpy(data + sizeof(struct pv_hdr_key_slot) * nks, hdr->encrypted, sizeof(*hdr->encrypted)); if (pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted) > 0) { (void)memcpy(data + sizeof(*hdr->encrypted), hdr->optional_items, pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted)); } } PvBuffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img, enum PvCryptoMode mode, GError **err) { uint32_t hdr_size = pv_hdr_size(hdr); g_autoptr(PvBuffer) ret = NULL; ret = pv_buffer_alloc(hdr_size); pv_hdr_memcpy(hdr, ret); if (mode == PV_ENCRYPT) { /* The buffer @ret is modified in-place */ if (pv_hdr_encrypt(hdr, img, ret, err) < 0) return NULL; } else { /* Simply copy the tag */ memcpy((uint8_t *)ret->data + hdr_size - pv_hdr_tag_size(hdr), hdr->tag, pv_hdr_tag_size(hdr)); } return g_steal_pointer(&ret); }