diff -Nru s390-tools-2.20.0/debian/changelog s390-tools-2.20.0/debian/changelog --- s390-tools-2.20.0/debian/changelog 2022-05-20 13:48:34.000000000 +0200 +++ s390-tools-2.20.0/debian/changelog 2022-08-30 08:40:00.000000000 +0200 @@ -1,3 +1,15 @@ +s390-tools (2.20.0-0ubuntu3.2) jammy; urgency=medium + + * Fix zipl segfaults when "parameters=" is missing (LP: #1974109) with: + d/p/6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch + * Add KVM Secure Execution Attestation Userspace Tool to enhance secure + execution (hardware feature: FC 115) exploitation (LP: #1959987) with: + d/p/38639269-libpv-New-library-for-PV-tools.patch + d/p/3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch + d/p/26148740-pvattest-tools-Add-tool-for-attestation.patch + + -- Frank Heimes Tue, 30 Aug 2022 08:40:00 +0200 + s390-tools (2.20.0-0ubuntu3.1) jammy; urgency=medium * Fix chreipl-fcp-mpath (LP: #1971993) diff -Nru s390-tools-2.20.0/debian/patches/26148740-pvattest-tools-Add-tool-for-attestation.patch s390-tools-2.20.0/debian/patches/26148740-pvattest-tools-Add-tool-for-attestation.patch --- s390-tools-2.20.0/debian/patches/26148740-pvattest-tools-Add-tool-for-attestation.patch 1970-01-01 01:00:00.000000000 +0100 +++ s390-tools-2.20.0/debian/patches/26148740-pvattest-tools-Add-tool-for-attestation.patch 2022-08-30 08:06:47.000000000 +0200 @@ -0,0 +1,163 @@ +From 26148740dfe14a33a01f1f75008c81fbafc8ed13 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 1 Jun 2022 08:00:24 +0000 +Subject: [PATCH] pvattest/tools: Add tool for attestation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Adds: + * extract_hdr_from_image + a bash script to extract the SE header from an SE image. + +Signed-off-by: Steffen Eiden +Acked-by: Marc Hartmayer +Signed-off-by: Jan Höppner + +Origin: upstream, https://github.com/ibm-s390-linux/s390-tools/commit/26148740dfe14a33a01f1f75008c81fbafc8ed13 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1959987 +Last-Update: 2022-08-30 + +--- + .gitignore | 1 + + pvattest/Makefile | 2 +- + pvattest/tools/Makefile | 5 ++ + pvattest/tools/pvextract-hdr | 89 ++++++++++++++++++++++++++++++++++++ + 4 files changed, 96 insertions(+), 1 deletion(-) + create mode 100644 pvattest/tools/Makefile + create mode 100755 pvattest/tools/pvextract-hdr + +diff --git a/.gitignore b/.gitignore +index 3846a4f4..3a9a3552 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -69,6 +69,7 @@ mon_tools/mon_fsstatd + mon_tools/mon_procd + osasnmpd/osasnmpd + pvattest/src/pvattest ++pvattest/tools/exchange_info + qetharp/qetharp + qethqoat/qethqoat + systemd/cpacfstatsd.service +diff --git a/pvattest/Makefile b/pvattest/Makefile +index 897a5bf8..b61a8052 100644 +--- a/pvattest/Makefile ++++ b/pvattest/Makefile +@@ -4,7 +4,7 @@ include ../common.mak + .DEFAULT_GOAL := all + + PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/pvattest" +-SUBDIRS := src man ++SUBDIRS := src man tools + RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive + + all: all-recursive +diff --git a/pvattest/tools/Makefile b/pvattest/tools/Makefile +new file mode 100644 +index 00000000..d9247127 +--- /dev/null ++++ b/pvattest/tools/Makefile +@@ -0,0 +1,5 @@ ++include ../../common.mak ++ ++install: ++ $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) ++ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvextract-hdr "$(DESTDIR)$(USRBINDIR)" +diff --git a/pvattest/tools/pvextract-hdr b/pvattest/tools/pvextract-hdr +new file mode 100755 +index 00000000..d8116dd9 +--- /dev/null ++++ b/pvattest/tools/pvextract-hdr +@@ -0,0 +1,89 @@ ++#!/bin/bash ++# ++# pvextract_hdr - extract an IBM Secure Execution header from the Image ++# ++# Sample: ++# ./pvextract-hdr -o sehdr.bin se-image.bin ++# ++# Copyright IBM Corp. 2022 ++# ++# s390-tools is free software; you can redistribute it and/or modify ++# it under the terms of the MIT license. See LICENSE for details. ++ ++set -o pipefail ++set -o nounset ++set -e ++ ++def_output="sehdr.bin" ++def_skip=0x14 ++def_len=0x4 ++ ++usage() { ++ cat <<-EOF ++ Usage: $(basename "$0") [-o ${def_output}] [-s ${def_skip}] [-l ${def_len}] FILE ++ ++ Extract the header of the SE-image located in FILE. ++ By default ${def_skip} pages will be skipped until starting to search ++ for the header. By default the search will be stopped after ${def_len} pages. ++ '${def_output}' is the default output file name. ++ EOF ++} ++ ++function check_file() { ++ [ -e "$1" ] || ++ { echo "ERROR: File '$1' not found" >&2 && exit 1; } ++} ++ ++function check_hdr_ver() { ++ local hdr_start="$1" ++ local input="$2" ++ xxd -s $((hdr_start + 8)) -l 4 "$input" | grep -q "000 0100" || ++ { echo -n "WARNING: unknown hdr version " && ++ xxd -s $((hdr_start + 8)) -l 4 "$input" | awk '{print "0x" $2 $3}'; } ++} ++ ++output=${def_output} ++parsed_skip=${def_skip} ++parsed_len=${def_len} ++# the last argument must be the input file ++input="${*: -1}" ++while getopts 'o:s:l:h' OPTION; do ++ case "$OPTION" in ++ o) output="$OPTARG" ;; ++ s) parsed_skip="$OPTARG" ;; ++ l) parsed_len="$OPTARG" ;; ++ h) ++ usage ++ exit 0 ++ ;; ++ :) ++ echo "ERROR: Must supply an argument to -$OPTARG." >&2 ++ exit 1 ++ ;; ++ *) ++ usage ++ exit 1 ++ ;; ++ esac ++done ++ ++#argument specify pages; convert to bytes ++skip=$((parsed_skip * 0x1000)) ++len=$((parsed_len * 0x1000)) ++ ++if [ $# -eq 0 ]; then ++ echo "ERROR: Input not set. Use '$(basename "$0") [FILE]' to specify the Input file" >&2 ++ exit 1 ++fi ++ ++check_file "$input" ++hdr_start=$(xxd -s $((skip)) -l $((len)) "${input}" | grep IBMSecEx || { echo ERROR: "${input} does not contain an SE header." >&2 && exit 1; }) ++hdr_start=$(echo "${hdr_start}" | awk '{print "0x" $1}' | cut -c 1-10) ++echo "SE header found at offset ${hdr_start}" ++ ++check_hdr_ver "$hdr_start" "$input" ++ ++size=$(xxd -s $((hdr_start + 12)) -l 4 "${input}" | awk 'NR==1 {print "0x" $2 $3}') ++ ++dd if="${input}" of="${output}" bs=1 count=$((size)) skip=$((hdr_start)) status=none ++echo "SE header written to '${output}' ($((size)) bytes)" +-- +2.25.1 + diff -Nru s390-tools-2.20.0/debian/patches/38639269-libpv-New-library-for-PV-tools.patch s390-tools-2.20.0/debian/patches/38639269-libpv-New-library-for-PV-tools.patch --- s390-tools-2.20.0/debian/patches/38639269-libpv-New-library-for-PV-tools.patch 1970-01-01 01:00:00.000000000 +0100 +++ s390-tools-2.20.0/debian/patches/38639269-libpv-New-library-for-PV-tools.patch 2022-08-30 08:25:37.000000000 +0200 @@ -0,0 +1,4087 @@ +From 386392690d4940d336d83b3dc943d5c143c03e99 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 1 Jun 2022 08:00:02 +0000 +Subject: [PATCH] libpv: New library for PV tools +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +libpv is a collection of definitions and functions related to +Protected Virtualization (PV). +The functions cover mainly encryption (e.g. AES-GCM) +and certificates (X509). There are also helping functions for glib2. + +Most of the code is extracted+refactored from `genprotimg`, which +will use this library in future. + +Requires openssl v1.1.1+, glib2.56+, and libcurl. + +libpv is not designed or intended to be dynamically linked or used +outside of this project. Its purpose is to avoid code duplication +as PV tools do very similar things regarding cryptography. + +Signed-off-by: Steffen Eiden +Acked-by: Marc Hartmayer +Signed-off-by: Jan Höppner + +[Frank Heimes: Adjusted hunks for Makefile and commom.mak + due to slightly different context.] + +Origin: backport, https://github.com/ibm-s390-linux/s390-tools/commit/386392690d4940d336d83b3dc943d5c143c03e99 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1959987 +Last-Update: 2022-08-30 + +--- + Makefile | 2 +- + common.mak | 4 + + include/libpv/cert.h | 438 +++++++++ + include/libpv/common.h | 35 + + include/libpv/crypto.h | 200 ++++ + include/libpv/curl.h | 53 + + include/libpv/glib-helper.h | 117 +++ + include/libpv/hash.h | 119 +++ + include/libpv/macros.h | 16 + + include/libpv/openssl-compat.h | 29 + + include/libpv/se-hdr.h | 98 ++ + libpv/Makefile | 101 ++ + libpv/cert.c | 1645 ++++++++++++++++++++++++++++++++ + libpv/common.c | 45 + + libpv/config.h | 15 + + libpv/crypto.c | 529 ++++++++++ + libpv/curl.c | 116 +++ + libpv/glib-helper.c | 179 ++++ + libpv/hash.c | 147 +++ + 19 files changed, 3887 insertions(+), 1 deletion(-) + create mode 100644 include/libpv/cert.h + create mode 100644 include/libpv/common.h + create mode 100644 include/libpv/crypto.h + create mode 100644 include/libpv/curl.h + create mode 100644 include/libpv/glib-helper.h + create mode 100644 include/libpv/hash.h + create mode 100644 include/libpv/macros.h + create mode 100644 include/libpv/openssl-compat.h + create mode 100644 include/libpv/se-hdr.h + create mode 100644 libpv/Makefile + create mode 100644 libpv/cert.c + create mode 100644 libpv/common.c + create mode 100644 libpv/config.h + create mode 100644 libpv/crypto.c + create mode 100644 libpv/curl.c + create mode 100644 libpv/glib-helper.c + create mode 100644 libpv/hash.c + +diff --git a/Makefile b/Makefile +index af66561e..e46955c2 100644 +--- a/Makefile ++++ b/Makefile +@@ -10,7 +10,7 @@ include common.mak + # + BASELIB_DIRS = libutil libseckey + LIB_DIRS = libvtoc libzds libdasd libvmdump libccw libvmcp libekmfweb \ +- libkmipclient ++ libkmipclient libpv + TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ + tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \ + vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ +diff --git a/common.mak b/common.mak +index 8113560f..1296f248 100644 +--- a/common.mak ++++ b/common.mak +@@ -387,6 +387,10 @@ $(rootdir)/libap/libap.a: $(rootdir)/libap + $(MAKE) -C $(rootdir)/libkmipclient/ libkmipclient.so + .PHONY: $(rootdir)/libkmipclient + ++$(rootdir)/libpv/libpv.a: $(rootdir)/libpv ++ $(MAKE) -C $(rootdir)/libpv libpv.a ++.PHONY: $(rootdir)/libpv ++ + $(rootdir)/zipl/boot/data.o: + $(MAKE) -C $(rootdir)/zipl/boot/ data.o + +diff --git a/include/libpv/cert.h b/include/libpv/cert.h +new file mode 100644 +index 00000000..3d2ccf0c +--- /dev/null ++++ b/include/libpv/cert.h +@@ -0,0 +1,438 @@ ++/* ++ * Certificate functions and definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_CERT_H ++#define LIBPV_CERT_H ++ ++#include ++#include ++ ++#include "libpv/common.h" ++ ++#define PV_IBM_Z_SUBJECT_COMMON_NAME "International Business Machines Corporation" ++#define PV_IBM_Z_SUBJECT_COUNTRY_NAME "US" ++#define PV_IBM_Z_SUBJECT_LOCALITY_NAME "Poughkeepsie" ++#define PV_IBM_Z_SUBJECT_ORGANIZATIONAL_UNIT_NAME_SUFFIX "Key Signing Service" ++#define PV_IBM_Z_SUBJECT_ORGANIZATION_NAME "International Business Machines Corporation" ++#define PV_IBM_Z_SUBJECT_STATE "New York" ++#define PV_IMB_Z_SUBJECT_ENTRY_COUNT 6 ++ ++/* Minimum security level for the keys/certificates used to establish a chain of ++ * trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html ++ * for details). ++ */ ++#define PV_CERTS_SECURITY_LEVEL 2 ++ ++/** pv_cert_init: ++ * ++ * Should not be called by user. ++ * Use pv_init() instead which ++ * calls this function during creation. ++ * ++ * Sets up data structures for caching CRLs. ++ */ ++void pv_cert_init(void); ++ ++/** pv_cert_cleanup: ++ * ++ * Should not be called by user. ++ * Use pv_cleanup() instead which ++ * calls this function during creation. ++ * ++ * Cleans up data structures for caching CRLs. ++ */ ++void pv_cert_cleanup(void); ++ ++#define PV_CERT_ERROR g_quark_from_static_string("pv-cert-error-quark") ++typedef enum { ++ PV_CERT_ERROR_CERT_REVOKED, ++ PV_CERT_ERROR_CERT_SIGNATURE_INVALID, ++ PV_CERT_ERROR_CERT_SUBJECT_ISSUER_MISMATCH, ++ PV_CERT_ERROR_CRL_DOWNLOAD_FAILED, ++ PV_CERT_ERROR_CRL_SIGNATURE_INVALID, ++ PV_CERT_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, ++ PV_CERT_ERROR_FAILED_DOWNLOAD_CRL, ++ PV_CERT_ERROR_INTERNAL, ++ PV_CERT_ERROR_INVALID_PARM, ++ PV_CERT_ERROR_INVALID_SIGNATURE_ALGORITHM, ++ PV_CERT_ERROR_INVALID_VALIDITY_PERIOD, ++ PV_CERT_ERROR_LOAD_CRL, ++ PV_CERT_ERROR_LOAD_DEFAULT_CA, ++ PV_CERT_ERROR_LOAD_ROOT_CA, ++ PV_CERT_ERROR_MALFORMED_CERTIFICATE, ++ PV_CERT_ERROR_MALFORMED_ROOT_CA, ++ PV_CERT_ERROR_NO_CRL, ++ PV_CERT_ERROR_NO_CRLDP, ++ PV_CERT_ERROR_NO_ISSUER_IBM_Z_FOUND, ++ PV_CERT_ERROR_NO_PUBLIC_KEY, ++ PV_CERT_ERROR_READ_CERTIFICATE, ++ PV_CERT_ERROR_READ_CRL, ++ PV_CERT_ERROR_SIGNATURE_ALGORITHM_MISMATCH, ++ PV_CERT_ERROR_SKID_AKID_MISMATCH, ++ PV_CERT_ERROR_VERIFICATION_FAILED, ++ PV_CERT_ERROR_WRONG_CA_USED, ++} PvCertErrors; ++ ++/** PvX509WithPath - X509 certificate associated with a path ++ */ ++typedef struct { ++ X509 *cert; ++ char *path; ++} PvX509WithPath; ++ ++/** pv_x509_with_path_new: ++ * ++ * @cert: X509 certificate ++ * @path: Path of that X509 certificate ++ * ++ * Returns: (nullable) (transfer full): new X509 with path ++ */ ++PvX509WithPath *pv_x509_with_path_new(X509 *cert, const char *path); ++ ++/** pv_x509_with_path_free: ++ * ++ * Frees the path and the PvX509WithPath; Decreases the refcount of the X509 ++ */ ++void pv_x509_with_path_free(PvX509WithPath *cert); ++ ++typedef STACK_OF(DIST_POINT) STACK_OF_DIST_POINT; ++typedef STACK_OF(X509) STACK_OF_X509; ++typedef STACK_OF(X509_CRL) STACK_OF_X509_CRL; ++typedef GSList PvCertWithPathList; ++ ++typedef struct { ++ X509 *cert; ++ STACK_OF_X509_CRL *crls; ++} PvX509Pair; ++ ++/** pv_x509_pair_new_take: ++ * @cert: ptr to X509 ++ * @crls: ptr to CRLs ++ * ++ * Takes a X509 and the associated CRLs and builds a pair. ++ * Both, *cert and *crls will be NULL afterwards, and owned by the pair. ++ * ++ * Returns: (nullable) (transfer full): New PvX509Pair ++ */ ++PvX509Pair *pv_x509_pair_new_take(X509 **cert, STACK_OF_X509_CRL **crls); ++ ++/** pv_x509_pair_free: ++ * ++ * Decreases the refcount of the X509 and crls. ++ * Frees the PvX509Pair. ++ */ ++void pv_x509_pair_free(PvX509Pair *pair); ++ ++void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack); ++void STACK_OF_X509_free(STACK_OF_X509 *stack); ++void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack); ++ ++/** pv_x509_from_pem_der_data: ++ * ++ * @data: GBytes containing the cert in PEM format ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): X509 cert ++ */ ++X509 *pv_x509_from_pem_der_data(GBytes *data, GError **error); ++ ++/** pv_x509_get_ec_pubkey: ++ * ++ * @cert: X509 to extract elliptic curve pubkey from ++ * @nid: numerical identifier of the expected curve ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): corresponding pupkey for the given certificate ++ */ ++EVP_PKEY *pv_x509_get_ec_pubkey(X509 *cert, int nid, GError **error); ++ ++/** pv_get_ec_pubkeys: ++ * ++ * @certs_with_path: List of PvX509WithPath ++ * @nid: numerical identifier of the expected curve ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): List of corresponding public keys for the given certificate ++ */ ++GSList *pv_get_ec_pubkeys(PvCertWithPathList *certs_with_path, int nid, GError **error); ++ ++/* pv_load_certificates: ++ * ++ * @cert_paths: list of cert paths. ++ * @error: return location for a #GError ++ * ++ * @cert_paths must contain at least one element, otherwise an error is ++ * reported. ++ * ++ * Returns: (nullable) (transfer full): List of PvX509WithPath corresponding to the given paths ++ */ ++PvCertWithPathList *pv_load_certificates(char **cert_paths, GError **error); ++ ++/* pv_load_first_cert_from_file: ++ * ++ * @path: location of the x509 ++ * @error: return location for a #GError ++ * ++ * This function reads in only the first certificate and ignores all other. This ++ * is only relevant for the PEM file format. For the host-key document and the ++ * root CA this behavior is expected. ++ * ++ * Returns: (nullable) (transfer full): PvX509WithPath corresponding to the given path ++ */ ++X509 *pv_load_first_cert_from_file(const char *path, GError **error); ++ ++/* pv_load_first_crl_from_file: ++ * ++ * @path: location of the x509 CRL ++ * @error: return location for a #GError ++ * ++ * This function reads in only the first CRL and ignores all other. This ++ * is only relevant for the PEM file format. ++ * ++ * Returns: (nullable) (transfer full): X509_CRL corresponding to the given path ++ */ ++X509_CRL *pv_load_first_crl_from_file(const char *path, GError **error); ++ ++/** pv_store_setup_crl_download: ++ * ++ * @st: X509_STORE ++ */ ++void pv_store_setup_crl_download(X509_STORE *st); ++ ++/** pv_load_first_crl_by_cert: ++ * @cert: X509 to specify the download location. ++ * @error: return location for a #GError ++ * ++ * This function returns the first X509_CRL found from the CRL distribution ++ * points specified in @cert. ++ * ++ * Returns: (nullable) (transfer full): x509 CRL corresponding to the given X509 ++ */ ++X509_CRL *pv_load_first_crl_by_cert(X509 *cert, GError **error); ++ ++/** pv_try_load_crls_by_certs: ++ * ++ * @certs_with_path: List of PvX509WithPath ++ * ++ * Returns: (nullable) (transfer full): Stack of CRLs corresponding to the given X509 ++ */ ++STACK_OF_X509_CRL *pv_try_load_crls_by_certs(PvCertWithPathList *certs_with_path); ++ ++/** pv_store_setup: ++ * ++ * @root_ca_path: Location of the rootCA or NULL if SystemRoot CA shall be used ++ * @crl_paths: List of CRL paths or NULL ++ * @cert_with_crl_paths: List of (untrusted) X509 paths ++ * @error: return location for a #GError ++ * ++ * The untrusted certs need to be verified before actually verifying a Host Key Document. ++ * ++ * Returns: (nullable) (transfer full): X509_store with given input data. ++ * ++ */ ++X509_STORE *pv_store_setup(char *root_ca_path, char **crl_paths, char **cert_with_crl_paths, ++ GError **error); ++ ++/** pv_get_x509_stack: ++ * ++ * x509_with_path_list: list of PvX509WithPath ++ * ++ * Returns: (nullable) (transfer full): Stack of X509 corresponding to the given x509 with path ++ */ ++STACK_OF_X509 *pv_get_x509_stack(const GSList *x509_with_path_list); ++ ++/** pv_init_store_ctx: ++ * ++ * @ctx: a uninitialized Store CTX ++ * @trusted: X509_STORE with a trusted rootCA ++ * @chain: untrusted X509s ++ * @error: return location for a #GError ++ * ++ * Can be called multiple times on the same context if X509_STORE_CTX_cleanup(ctx) ++ * was called before. ++ * ++ * Returns: ++ * 0 on success ++ * -1 in failure ++ */ ++int pv_init_store_ctx(X509_STORE_CTX *ctx, X509_STORE *trusted, STACK_OF_X509 *chain, ++ GError **error) PV_NONNULL(1, 2, 3); ++ ++/** pv_init_store_ctx: ++ * ++ * @trusted: X509_STORE with a trusted rootCA ++ * @chain: untrusted X509s ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): X509_STORE_CTX setup with the input data ++ */ ++X509_STORE_CTX *pv_create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, GError **error) ++ PV_NONNULL(1, 2); ++/** pv_remove_ibm_signing_certs: ++ * ++ * @certs: Stack of X509s ++ * ++ * Returns: (transfer full): ++ * List of all IBM Z signing key certificates in @certs and remove them ++ * from the chain. ++ * Empty stack if no IBM Z signing key is found. ++ */ ++STACK_OF_X509 *pv_remove_ibm_signing_certs(STACK_OF_X509 *certs); ++ ++/** pv_c2b_name: ++ * ++ * Workaround to fix the mismatch between issuer name of the ++ * IBM Z signing CRLs and the IBM Z signing key subject name. ++ * ++ * In RFC 5280 the attributes of a (subject/issuer) name is not mandatory ++ * ordered. The problem is that our certificates are not consistent in the order ++ * (see https://tools.ietf.org/html/rfc5280#section-4.1.2.4 for details). ++ * ++ * This function tries to reorder the name attributes such that ++ * further OpenSSL calls can work with it. The caller is ++ * responsible to free the returned value. ++ */ ++X509_NAME *pv_c2b_name(const X509_NAME *name); ++ ++/** pv_verify_host_key: ++ * ++ * @host_key: X509 to be verified ++ * @issuer_pairs: IBM signing key X509+CRLs Pairs used for verification ++ * @level: Security level. see PV_CERTS_SECURITY_LEVEL ++ * @error: return location for a #GError ++ * ++ * Returns: ++ * 0 if Host key could be verified with one of the IBM signing keys ++ * -1 if no IBM signing key could verify the authenticity of the given host key ++ * ++ */ ++int pv_verify_host_key(X509 *host_key, GSList *issuer_pairs, int verify_flags, int level, ++ GError **error); ++ ++/** pv_verify_cert: ++ * ++ * @ctx: trusted store ctx used for verification ++ * @cert: X509 to be verified ++ * @error: return location for a #GError ++ * ++ * Cannot be used to verify host keys with IBM signing keys, as IBM signing ++ * keys are no intermediate CAs. Use pv_verify_host_key() instead. ++ * ++ * Returns: ++ * 0 if @cert could be verified ++ * -1 if @cert could not be verified ++ */ ++int pv_verify_cert(X509_STORE_CTX *ctx, X509 *cert, GError **error) PV_NONNULL(1, 2); ++ ++/** pv_check_crl_valid_for_cert: ++ * ++ * @crl: CRL to be verified ++ * @cert: Cert that probably issued the given CRL ++ * @verify_flags: X509 Verification flags (X509_V_FLAG_) ++ * @error: return location for a #GError ++ * ++ * Verify whether a revocation list @crl is valid and is issued by @cert. For ++ * this multiple steps must be done: ++ * ++ * 1. verify issuer of the CRL matches with the suject name of @cert ++ * 2. verify the validity period of the CRL ++ * 3. verify the signature of the CRL ++ * ++ * Important: This function does not verify whether @cert is allowed to issue a ++ * CRL. ++ * ++ * Returns: ++ * 0 if @crl is valid and issued by @cert ++ * -1 otherwise ++ */ ++int pv_verify_crl(X509_CRL *crl, X509 *cert, int verify_flags, GError **error); ++ ++/** pv_check_chain_parameters: ++ * ++ * @chain: chain of trust to be validated ++ * @error: return location for a #GError ++ * ++ * Verifies that chain has at least a RootCA ans intermediate CA ++ * and logs the used ROD CA subject ++ * ++ * Returns: ++ * 0 @chain is valid ++ * -1 otherwise ++ */ ++int pv_check_chain_parameters(const STACK_OF_X509 *chain, GError **error); ++ ++/** pv_store_set_verify_param: ++ * ++ * @store: X509_STORE to set parameters ++ * @error: return location for a #GError ++ * ++ * Returns: ++ * 0 on success ++ * -1 on failure ++ */ ++int pv_store_set_verify_param(X509_STORE *store, GError **error); ++ ++/** pv_store_ctx_find_valid_crls: ++ * ++ * @ctx: STORE_CTX for searching CRLs ++ * @cert: X509 to match CRLs aggainst ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): STACK of CRLs related to given @crl fin @ctx ++ */ ++STACK_OF_X509_CRL *pv_store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, GError **error) ++ PV_NONNULL(1, 2); ++ ++/** pv_verify_host_key_doc: ++ * ++ * @host_key_certs_with_path: X509s to be verified ++ * @trusted. X509_STORE with a rusted RootCA ++ * @untrusted_certs: STACK OF untrusted X509s ++ * @online: true if CRLs shall be downloaded ++ * @error: return location for a #GError ++ * ++ * Returns: ++ * 0 if all given HKDs could be verified using the chain of trust. ++ * -1 otherwise ++ */ ++int pv_verify_host_key_doc(PvCertWithPathList *host_key_certs_with_path, X509_STORE *trusted, ++ STACK_OF_X509 *untrusted_certs, gboolean online, GError **error) ++ PV_NONNULL(1, 2, 3); ++ ++/** pv_verify_host_key_docs_by_path: ++ * ++ * @host_key_paths: locations of X509 to be verified ++ * @optional_root_ca_path: rootCA location or NULL if Default shall be used ++ * @optional_crl_paths: locations of CRLs or NULL ++ * @untrusted_cert_paths: locations of IntermediateCAs including the IBM signing key ++ * @online: true if CRLs shall be downloaded ++ * @error: return location for a #GError ++ * ++ * Returns: ++ * 0 if all given HKDs could be verfied using the chain of trust. ++ * -1 otherwise ++ */ ++int pv_verify_host_key_docs_by_path(char **host_key_paths, char *optional_root_ca_path, ++ char **optional_crl_paths, char **untrusted_cert_paths, ++ gboolean online, GError **error) PV_NONNULL(1, 4); ++ ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(AUTHORITY_KEYID, AUTHORITY_KEYID_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509WithPath, pv_x509_with_path_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_DIST_POINT, STACK_OF_DIST_POINT_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509, STACK_OF_X509_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509_CRL, STACK_OF_X509_CRL_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_CRL, X509_CRL_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_NAME, X509_NAME_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvX509Pair, pv_x509_pair_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE, X509_STORE_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE_CTX, X509_STORE_CTX_free) ++ ++#endif /* LIBPV_CERT_H */ +diff --git a/include/libpv/common.h b/include/libpv/common.h +new file mode 100644 +index 00000000..bfc6c4e4 +--- /dev/null ++++ b/include/libpv/common.h +@@ -0,0 +1,35 @@ ++/* ++ * Libpv common definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ * ++ */ ++#ifndef LIBPV_COMMON_H ++#define LIBPV_COMMON_H ++ ++/* must be included before any (other) glib header to verify that ++ * the glib version is supported ++ */ ++#include "libpv/glib-helper.h" ++ ++#include ++ ++#include "libpv/openssl-compat.h" ++#include "libpv/macros.h" ++ ++/** pv_init: ++ * ++ * Must be called before any libpv call. ++ */ ++int pv_init(void); ++ ++/** pv_cleanup: ++ * ++ * Must be called when done with using libpv. ++ */ ++void pv_cleanup(void); ++ ++#endif /* LIBPV_COMMON_H */ +diff --git a/include/libpv/crypto.h b/include/libpv/crypto.h +new file mode 100644 +index 00000000..24c987fa +--- /dev/null ++++ b/include/libpv/crypto.h +@@ -0,0 +1,200 @@ ++/* ++ * General cryptography helper functions and definitions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_CRYPTO_H ++#define LIBPV_CRYPTO_H ++ ++#include ++#include ++#include ++#include ++ ++#include "libpv/common.h" ++ ++typedef struct pv_cipher_parms { ++ const EVP_CIPHER *cipher; ++ size_t tag_size; ++ GBytes *key; ++ union { ++ GBytes *iv; ++ GBytes *tweak; ++ }; ++} PvCipherParms; ++ ++typedef union { ++ struct { ++ uint8_t x[80]; ++ uint8_t y[80]; ++ }; ++ uint8_t data[160]; ++} PvEcdhPubKey; ++G_STATIC_ASSERT(sizeof(PvEcdhPubKey) == 160); ++ ++typedef GSList PvEvpKeyList; ++ ++enum PvCryptoMode { ++ PV_ENCRYPT, ++ PV_DECRYPT, ++}; ++ ++/** pv_get_openssl_error: ++ * ++ * Returns: (transfer full): String representing the error. ++ */ ++const char *pv_get_openssl_error(void); ++ ++/** ++ * pv_BIO_reset: ++ * @b: BIO to reset ++ * ++ * Resets a BIO to its initial state. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++int pv_BIO_reset(BIO *b); ++ ++/** ++ * pv_generate_rand_data: ++ * @size: number of generated random bytes using a crypographically secure pseudo random generator ++ * @error: return location for a #GError ++ * ++ * Creates a new #GBytes with @size random bytes using a cryptographically ++ * secure pseudo random generator. ++ * ++ * Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error ++ */ ++GBytes *pv_generate_rand_data(size_t size, GError **error); ++ ++/** ++ * pv_generate_key: ++ * @cipher: specifies the OpenSSL cipher for which a cryptographically secure key should be generated ++ * @error: return location for a #GError ++ * ++ * Creates a random key for @cipher using a cryptographically secure pseudo ++ * random generator. ++ * ++ * Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error ++ */ ++GBytes *pv_generate_key(const EVP_CIPHER *cipher, GError **error) PV_NONNULL(1); ++ ++/** ++ * pv_generate_iv: ++ * @cipher: specifies the OpenSSL cipher for which a cryptographically secure IV should be generated ++ * @error: return location for a #GError ++ * ++ * Creates a random IV for @cipher using a cryptographically secure pseudo ++ * random generator. ++ * ++ * Returns: (nullable) (transfer full): a new #GBytes, or %NULL in case of an error ++ */ ++GBytes *pv_generate_iv(const EVP_CIPHER *cipher, GError **error) PV_NONNULL(1); ++ ++/* Symmetric en/decryption functions */ ++ ++/** ++ * pv_gcm_encrypt: ++ * @plain: data to encrypt ++ * @aad: (optional): additional data that should be authenticated with the key ++ * @parms: ++ * @cipher: (out): location to store the ciphertext ++ * @tag: (out): location to store the generated GCM tag ++ * @error: return location for a #GError ++ * ++ * Encrypts the @plain data and authenticates @aad data. ++ * ++ * Returns: number of bytes, or -1 in case of an error ++ */ ++int64_t pv_gcm_encrypt(GBytes *plain, GBytes *aad, const PvCipherParms *parms, GBytes **cipher, ++ GBytes **tag, GError **error) PV_NONNULL(1, 3, 4, 5); ++ ++/** ++ * pv_gcm_decrypt: ++ * @cipher: ciphertext to decrypt ++ * @aad: (optional): additional date to authenticate ++ * @tag: the GCM tag ++ * @parms: ++ * @plain: (out): location to store the decrypted data ++ * @error: return location for a #GError ++ * ++ * Decrypts the @cipher data and authenticates the @aad data. ++ * ++ * Returns: number of bytes, or -1 in case of an error ++ */ ++int64_t pv_gcm_decrypt(GBytes *cipher, GBytes *aad, GBytes *tag, const PvCipherParms *parms, ++ GBytes **plain, GError **error) PV_NONNULL(1, 3, 4, 5); ++ ++/** pv_hkdf_extract_and_expand: ++ * @derived_key_len: size of the output key ++ * @key: input key ++ * @salt: salt for the extraction ++ * @info: infor for the expansion ++ * @md: EVP mode of operation ++ * @error: return location for a #GError ++ * ++ * Performs a RFC 5869 HKDF. ++ * ++ * Returns: (nullable) (transfer full): Result of RFC 5869 HKDF ++ * ++ */ ++GBytes *pv_hkdf_extract_and_expand(size_t derived_key_len, GBytes *key, GBytes *salt, GBytes *info, ++ const EVP_MD *md, GError **error) PV_NONNULL(2, 3, 4, 5); ++ ++/** pv_generate_ec_key: ++ * ++ * @nid: Numerical identifier of the curve ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): new random key based on the given curve ++ */ ++EVP_PKEY *pv_generate_ec_key(int nid, GError **error); ++ ++/** pv_evp_pkey_to_ecdh_pub_key: ++ * ++ * @key: input key in EVP_PKEY format ++ * @error: return location for a #GError ++ * ++ * Returns: the public part of the input @key in ECDH format. ++ */ ++PvEcdhPubKey *pv_evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **error) PV_NONNULL(1); ++ ++/** pv_derive_exchange_key: ++ * @cust: Customer Key ++ * @host: Host key ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): Shared Secret of @cust and @host ++ */ ++GBytes *pv_derive_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **error) PV_NONNULL(1, 2); ++ ++GQuark pv_crypto_error_quark(void); ++#define PV_CRYPTO_ERROR pv_crypto_error_quark() ++typedef enum { ++ PV_CRYPTO_ERROR_DERIVE, ++ PV_CRYPTO_ERROR_HKDF_FAIL, ++ PV_CRYPTO_ERROR_INTERNAL, ++ PV_CRYPTO_ERROR_INVALID_KEY_SIZE, ++ PV_CRYPTO_ERROR_KEYGENERATION, ++ PV_CRYPTO_ERROR_RANDOMIZATION, ++ PV_CRYPTO_ERROR_READ_FILE, ++ PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, ++ PV_CRYPTO_ERROR_NO_MATCH_TAG, ++} PvCryptoErrors; ++ ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_INTEGER, ASN1_INTEGER_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_OCTET_STRING, ASN1_OCTET_STRING_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIO, BIO_free_all) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIGNUM, BN_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BN_CTX, BN_CTX_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_GROUP, EC_GROUP_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_KEY, EC_KEY_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_POINT, EC_POINT_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_CIPHER_CTX, EVP_CIPHER_CTX_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY, EVP_PKEY_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY_CTX, EVP_PKEY_CTX_free) ++ ++#endif /* LIBPV_CRYPTO_H */ +diff --git a/include/libpv/curl.h b/include/libpv/curl.h +new file mode 100644 +index 00000000..f6089221 +--- /dev/null ++++ b/include/libpv/curl.h +@@ -0,0 +1,53 @@ ++/* ++ * Libcurl utils ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_CURL_H ++#define LIBPV_CURL_H ++ ++#include ++ ++#include "libpv/common.h" ++ ++#define CRL_DOWNLOAD_TIMEOUT_MS 3000 ++#define CRL_DOWNLOAD_MAX_SIZE 0x100000 ++ ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURL, curl_easy_cleanup) ++ ++/** curl_download: ++ * @url: URL to specify location of data ++ * @timeout_ms: time to wait until fail ++ * @max_size: Maximum size of the downloaded data ++ * @error: return location for a GError ++ * ++ * Returns: (nullable) (transfer full): Downloaded data as #GByteArray ++ */ ++GByteArray *curl_download(const char *url, long timeout_ms, uint max_size, GError **err); ++ ++/** pv_curl_init: ++ * ++ * Should not be called by user. ++ * Use pv_init() instead which ++ * calls this function during creation. ++ */ ++int pv_curl_init(void); ++ ++/** pv_curl_cleanup: ++ * ++ * Should not be called by user. ++ * Use pv_cleanup() instead which ++ * calls this function during creation. ++ */ ++void pv_curl_cleanup(void); ++ ++#define PV_CURL_ERROR g_quark_from_static_string("pv-curl-error-quark") ++typedef enum { ++ PV_CURL_ERROR_CURL_INIT_FAILED, ++ PV_CURL_ERROR_DOWNLOAD_FAILED, ++} PvCurlErrors; ++ ++#endif /* LIBPV_CURL_H */ +diff --git a/include/libpv/glib-helper.h b/include/libpv/glib-helper.h +new file mode 100644 +index 00000000..6dcd8532 +--- /dev/null ++++ b/include/libpv/glib-helper.h +@@ -0,0 +1,117 @@ ++/* ++ * Glib convenience functions ++ * Shall be used instead of manually including glib. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_GLIB_HELPER_H ++#define LIBPV_GLIB_HELPER_H ++ ++#if defined(GLIB_VERSION_MIN_REQUIRED) ++#if GLIB_VERSION_MIN_REQUIRED < GLIB_VERSION_2_56 ++#error "GLIB_VERSION must be at least 2.56" ++#endif ++#else ++#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_56 ++#endif ++ ++#include ++#include ++#include ++ ++#include "libpv/macros.h" ++ ++#ifdef __clang__ ++#define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) \ ++ DO_PRAGMA(clang diagnostic push) \ ++ DO_PRAGMA(clang diagnostic ignored "-Wunused-function") \ ++ G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__) \ ++ DO_PRAGMA(clang diagnostic pop) ++#else ++#define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__) ++#endif ++ ++#define pv_wrapped_g_assert(__expr) g_assert(__expr) ++ ++/** pv_sec_gbytes_new_take: ++ * ++ * #g_bytes_new_take() with secure cleanup ++ */ ++GBytes *pv_sec_gbytes_new_take(void *data, size_t size); ++ ++/** pv_sec_gbytes_new: ++ * ++ * #g_bytes_new() with secure cleanup ++ */ ++GBytes *pv_sec_gbytes_new(const void *data, size_t size); ++ ++/** pv_file_get_content_as_secure_bytes: ++ * ++ * @filename: path to file for reading in ++ * ++ * read file and save as secure gbytes ++ * ++ * Return: ++ * Content of file as #GBytes with secure cleanup ++ */ ++GBytes *pv_file_get_content_as_secure_bytes(const char *filename); ++ ++/** pv_file_get_content_as_g_bytes: ++ * ++ * @filename: path to file for reading in ++ * ++ * read file and save as gbytes ++ * ++ * Return: ++ * Content of file as #GBytes ++ */ ++GBytes *pv_file_get_content_as_g_bytes(const char *filename, GError **error); ++ ++/** pv_file_seek: ++ * ++ * fseek with error reporting ++ */ ++int pv_file_seek(FILE *file, long offset, int whence, GError **error); ++ ++/** pv_file_write: ++ * ++ * fwrite with error reporting ++ */ ++size_t pv_file_write(FILE *file, const void *ptr, size_t size, GError **error); ++ ++/** pv_file_close: ++ * ++ * fclose with error reporting ++ */ ++long pv_file_close(FILE *file, GError **error); ++ ++/** pv_file_tell: ++ * ++ * ftell with error reporting ++ */ ++long pv_file_tell(FILE *file, GError **error); ++ ++/** pv_file_open: ++ * ++ * fopen with error reporting ++ */ ++FILE *pv_file_open(const char *filename, const char *mode, GError **error); ++ ++/** pv_gbytes_memcpy: ++ * ++ * memcpy with size check. ++ */ ++void *pv_gbytes_memcpy(void *dst, size_t dst_size, GBytes *src); ++ ++#define PV_GLIB_HELPER_ERROR g_quark_from_static_string("pv-glib-helper_error-quark") ++typedef enum { ++ PV_GLIB_HELPER_FILE_ERROR, ++} pv_glib_helper_error_e; ++ ++void pv_auto_close_file(FILE *file); ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, pv_auto_close_file) ++ ++#endif /* LIBPV_GLIB_HELPER_H */ +diff --git a/include/libpv/hash.h b/include/libpv/hash.h +new file mode 100644 +index 00000000..1656e2c0 +--- /dev/null ++++ b/include/libpv/hash.h +@@ -0,0 +1,119 @@ ++/* ++ * Hashing definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_HASH_H ++#define LIBPV_HASH_H ++ ++#include ++ ++#include "libpv/common.h" ++ ++/** pv_digest_ctx_new: ++ * @md: mode of digest, e.g. #EVP_sha256() ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): a new #EVP_MD_CTX, or %NULL in case of an error ++ */ ++EVP_MD_CTX *pv_digest_ctx_new(const EVP_MD *md, GError **error); ++ ++/** pv_digest_ctx_update: ++ * @ctx: EVP_MD_CTX to add data ++ * @data: #GBytes to add to the context ++ * @error: return location for a #GError ++ * ++ * Adds @data to the digest context. Can be called multiple times. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++int pv_digest_ctx_update(EVP_MD_CTX *ctx, GBytes *data, GError **error); ++ ++/** pv_digest_ctx_update_raw: ++ * @ctx: #EVP_MD_CTX to add data ++ * @buf: data to add to the context ++ * @size: size of @buf ++ * @error: return location for a #GError ++ * ++ * Adds @buf to the digest context. Can be called multiple times. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++int pv_digest_ctx_update_raw(EVP_MD_CTX *ctx, const uint8_t *buf, size_t size, GError **error); ++ ++/** pv_digest_ctx_finalize: ++ * @ctx: #EVP_MD_CTX with data to digest ++ * @error: return location for a #GError ++ * ++ * Calculates the digest of all previously added data. Do not use @ctx afterwards. ++ * ++ * Returns: (nullable) (transfer full): Digest of all data added before as #GBytes, or NULL in case of error. ++ */ ++GBytes *pv_digest_ctx_finalize(EVP_MD_CTX *ctx, GError **error); ++ ++/** pv_sha256_hash: ++ * @buf: data for which a sha256 hash sould be calculated ++ * @size: size of @buf ++ * @error: return location for a #GError ++ * ++ * Shorthand for initializing a sha256-digest ctx, updating, and finalizing. ++ * ++ * Returns: (nullable) (transfer full): SHA256 of @buf as #GBytes, or NULL in case of error. ++ */ ++GBytes *pv_sha256_hash(uint8_t *buf, size_t size, GError **error); ++ ++/** pv_hmac_ctx_new: ++ * @key: key used for the HMAC ++ * @md: mode of digest, e.g. #EVP_sha512() ++ * @error: return location for a #GError ++ * ++ * Returns: (nullable) (transfer full): New #HMAC_CTX or NULL in case of error ++ */ ++HMAC_CTX *pv_hmac_ctx_new(GBytes *key, const EVP_MD *md, GError **error); ++ ++/** pv_hmac_ctx_update_raw: ++ * @ctx: #HMAC_CTX to add data ++ * @buf: data to add to the context ++ * @size: size of @buf ++ * @error: return location for a #GError ++ * ++ * Adds @buf to the HMAC context. Can be called multiple times. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++int pv_hmac_ctx_update_raw(HMAC_CTX *ctx, const void *data, size_t size, GError **error); ++ ++/** pv_hmac_ctx_update: ++ * @ctx: #HMAC_CTX to add data ++ * @data: #GBytes to add to the context ++ * @error: return location for a #GError ++ * ++ * Adds @data to the HMAC context. Can be called multiple times. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++ ++int pv_hmac_ctx_update(HMAC_CTX *ctx, GBytes *data, GError **error); ++ ++/** pv_hmac_ctx_finalize: ++ * @ctx: #HMAC_CTX with data to digest ++ * @error: return location for a #GError ++ * ++ * Calculates the HMAC of all previously added data. Do not use @ctx afterwards. ++ * ++ * Returns: (nullable) (transfer full): HMAC of all data added before as #GBytes, or NULL in case of error. ++ */ ++GBytes *pv_hamc_ctx_finalize(HMAC_CTX *ctx, GError **error); ++ ++#define PV_HASH_ERROR g_quark_from_static_string("pv-crypro-error-quark") ++typedef enum { ++ PV_HASH_ERROR_INTERNAL, ++} PvHashErrors; ++ ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_MD_CTX, EVP_MD_CTX_free) ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(HMAC_CTX, HMAC_CTX_free) ++ ++#endif /* LIBPV_HASH_H */ +diff --git a/include/libpv/macros.h b/include/libpv/macros.h +new file mode 100644 +index 00000000..4b9fe0fa +--- /dev/null ++++ b/include/libpv/macros.h +@@ -0,0 +1,16 @@ ++/* ++ * Libpv common macro definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ * ++ */ ++#ifndef LIBPV_MACROS_H ++#define LIBPV_MACROS_H ++ ++#define PV_NONNULL(...) ++#define DO_PRAGMA(x) _Pragma(#x) ++ ++#endif /* LIBPV_MACROS_H */ +diff --git a/include/libpv/openssl-compat.h b/include/libpv/openssl-compat.h +new file mode 100644 +index 00000000..255f08f9 +--- /dev/null ++++ b/include/libpv/openssl-compat.h +@@ -0,0 +1,29 @@ ++/* ++ * OpenSSL compatibility utils ++ * ++ * Copyright IBM Corp. 2021 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef LIBPV_OPENSSL_COMPAT_H ++#define LIBPV_OPENSSL_COMPAT_H ++ ++#include ++#include ++#include ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#define pv_X509_STORE_CTX_get_current_cert(ctx) X509_STORE_CTX_get_current_cert(ctx) ++#define pv_X509_STORE_CTX_get1_crls(ctx, nm) X509_STORE_CTX_get1_crls((ctx), (nm)) ++#define pv_X509_STORE_set_lookup_crls(st, cb) X509_STORE_set_lookup_crls(st, cb) ++#elif OPENSSL_VERSION_NUMBER >= 0x10100000L ++#define pv_X509_STORE_CTX_get_current_cert(ctx) \ ++ X509_STORE_CTX_get_current_cert((X509_STORE_CTX *)(ctx)) ++#define pv_X509_STORE_CTX_get1_crls(ctx, nm) \ ++ X509_STORE_CTX_get1_crls((X509_STORE_CTX *)(ctx), (X509_NAME *)(nm)) ++#define pv_X509_STORE_set_lookup_crls(st, cb) \ ++ X509_STORE_set_lookup_crls(st, (X509_STORE_CTX_lookup_crls_fn)(cb)) ++#endif ++ ++#endif /* LIBPV_OPENSSL_COMPAT_H */ +diff --git a/include/libpv/se-hdr.h b/include/libpv/se-hdr.h +new file mode 100644 +index 00000000..3d4219c1 +--- /dev/null ++++ b/include/libpv/se-hdr.h +@@ -0,0 +1,98 @@ ++/* ++ * PV/SE header definitions ++ * ++ * 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. ++ */ ++#ifndef LIBPV_SE_HDR_H ++#define LIBPV_SE_HDR_H ++ ++#include "libpv/common.h" ++ ++#include ++ ++#include "boot/s390.h" ++#include "libpv/crypto.h" ++ ++/* Magic number which is used to identify the file containing the PV ++ * header ++ */ ++#define PV_MAGIC_NUMBER 0x49424d5365634578ULL ++#define PV_VERSION_1 0x00000100U ++ ++/* Internal helper macro */ ++#define __PV_BIT(nr) (1ULL << (63 - (nr))) ++ ++/* Plaintext control flags */ ++/* dumping of the configuration is allowed */ ++#define PV_PCF_ALLOW_DUMPING __PV_BIT(34) ++/* prevent Ultravisor decryption during unpack operation */ ++#define PV_PCF_NO_DECRYPTION __PV_BIT(35) ++/* PCKMO encrypt-DEA/TDEA-key functions allowed */ ++#define PV_PCF_PCKMO_DEA_TDEA __PV_BIT(56) ++/* PCKMO encrypt-AES-key functions allowed */ ++#define PV_PCF_PCKMO_AES __PV_BIT(57) ++/* PCKMO encrypt-ECC-key functions allowed */ ++#define PV_PCF_PCKM_ECC __PV_BIT(58) ++ ++/* maxima for the PV version 1 */ ++#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE ++#define PV_V1_PV_HDR_MIN_SIZE \ ++ (sizeof(struct pv_hdr_head) + sizeof(struct pv_hdr_encrypted) + \ ++ sizeof(((struct pv_hdr *)0)->tag) + 1 * sizeof(struct pv_hdr_key_slot)) ++#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE) ++ ++#define PV_IMAGE_ENCR_KEY_SIZE 64 ++ ++typedef struct pv_hdr_key_slot { ++ uint8_t digest_key[SHA256_DIGEST_LENGTH]; ++ uint8_t wrapped_key[32]; ++ uint8_t tag[16]; ++} __packed PvHdrKeySlot; ++ ++typedef struct pv_hdr_opt_item { ++ uint32_t otype; ++ uint8_t ibk[32]; ++ uint8_t data[]; ++} __packed PvHdrOptItem; ++ ++/* integrity protected data (by GCM tag), but non-encrypted */ ++struct pv_hdr_head { ++ uint64_t magic; ++ uint32_t version; ++ uint32_t phs; ++ uint8_t iv[12]; ++ uint32_t res1; ++ uint64_t nks; ++ uint64_t sea; ++ uint64_t nep; ++ uint64_t pcf; ++ PvEcdhPubKey cust_pub_key; ++ uint8_t pld[SHA512_DIGEST_LENGTH]; ++ uint8_t ald[SHA512_DIGEST_LENGTH]; ++ uint8_t tld[SHA512_DIGEST_LENGTH]; ++} __packed; ++ ++/* Must not have any padding */ ++struct pv_hdr_encrypted { ++ uint8_t cust_comm_key[32]; ++ uint8_t img_enc_key_1[PV_IMAGE_ENCR_KEY_SIZE / 2]; ++ uint8_t img_enc_key_2[PV_IMAGE_ENCR_KEY_SIZE / 2]; ++ struct psw_t psw; ++ uint64_t scf; ++ uint32_t noi; ++ uint32_t res2; ++}; ++G_STATIC_ASSERT(sizeof(struct pv_hdr_encrypted) == 32 + 32 + 32 + sizeof(struct psw_t) + 8 + 4 + 4); ++ ++typedef struct pv_hdr { ++ struct pv_hdr_head head; ++ struct pv_hdr_key_slot *slots; ++ struct pv_hdr_encrypted *encrypted; ++ struct pv_hdr_opt_item **optional_items; ++ uint8_t tag[16]; ++} PvHdr; ++ ++#endif /* LIBPV_SE_HDR_H */ +diff --git a/libpv/Makefile b/libpv/Makefile +new file mode 100644 +index 00000000..c76e6ceb +--- /dev/null ++++ b/libpv/Makefile +@@ -0,0 +1,101 @@ ++# Common definitions ++include ../common.mak ++ ++.DEFAULT_GOAL := all ++ ++LIB := libpv.a ++ ++ifneq ($(shell sh -c 'command -v pkg-config'),) ++GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0) ++GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0) ++LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto openssl) ++LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto openssl) ++LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl) ++LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl) ++else ++GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include ++GLIB2_LIBS := -lglib-2.0 ++LIBCRYPTO_CFLAGS := ++LIBCRYPTO_LIBS := -lcrypto -lssl ++LIBCURL_CFLAGS := -I/usr/include/s390x-linux-gnu ++LIBCURL_LIBS := -lcurl ++endif ++LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS) ++ ++WARNINGS := -Wall -Wextra -Wshadow \ ++ -Wcast-align -Wwrite-strings -Wmissing-prototypes \ ++ -Wmissing-declarations -Wredundant-decls -Wnested-externs \ ++ -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \ ++ -Wpointer-arith -Wno-error=inline \ ++ -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \ ++ -Werror \ ++ $(NULL) ++ ++ALL_CFLAGS += -std=gnu11 \ ++ -DOPENSSL_API_COMPAT=0x10101000L \ ++ $(GLIB2_CFLAGS) \ ++ $(LIBCRYPTO_CFLAGS) \ ++ $(LIBCURL_CFLAGS) \ ++ $(WARNINGS) \ ++ $(NULL) ++ ++BUILD_TARGETS := skip-$(LIB) ++ifneq (${HAVE_OPENSSL},0) ++ifneq (${HAVE_GLIB2},0) ++ifneq (${HAVE_LIBCURL},0) ++BUILD_TARGETS := $(LIB) ++endif ++endif ++endif ++ ++sources := $(wildcard *.c) ++objects := $(patsubst %.c,%.o,$(sources)) ++ ++all: $(BUILD_TARGETS) .check_dep-$(LIB) ++ ++$(LIB): $(objects) ++$(LIB): ALL_CFLAGS += -fPIC ++ ++$(objects): .check-dep-$(LIB) ++ ++install: all ++ ++clean: ++ rm -f -- $(objects) ++ rm -f -- $(LIB) ++ rm -f -- .check-dep-$(LIB) .detect-openssl.dep.c ++ ++skip-$(LIB): ++ echo " SKIP $(LIB) due to unresolved dependencies" ++ ++.PHONY: all install clean skip-$(LIB) install-$(LIB) ++ ++ ++.detect-openssl.dep.c: ++ echo "#include " > $@ ++ echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@ ++ echo " #error openssl version 1.1.0 is required" >> $@ ++ echo "#endif" >> $@ ++ echo "static void __attribute__((unused)) test(void) {" >> $@ ++ echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@ ++ echo " EVP_MD_CTX_free(ctx);" >> $@ ++ echo "}" >> $@ ++ ++.check-dep-$(LIB): .detect-openssl.dep.c ++ $(call check_dep, \ ++ "$(LIB)", \ ++ "glib.h", \ ++ "glib2-devel / libglib2.0-dev", \ ++ "HAVE_GLIB2=0") ++ $(call check_dep, \ ++ "$(LIB)", \ ++ $^, \ ++ "openssl-devel / libssl-dev version >= 1.1.0", \ ++ "HAVE_OPENSSL=0", \ ++ "-I.") ++ $(call check_dep, \ ++ "$(LIB)", \ ++ "curl/curl.h", \ ++ "libcurl-devel", \ ++ "HAVE_LIBCURL=0") ++ touch $@ +diff --git a/libpv/cert.c b/libpv/cert.c +new file mode 100644 +index 00000000..4cd94211 +--- /dev/null ++++ b/libpv/cert.c +@@ -0,0 +1,1645 @@ ++/* ++ * Certificate functions and definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++ ++#include "libpv/cert.h" ++#include "libpv/crypto.h" ++#include "libpv/curl.h" ++ ++/* Used for the caching of the downloaded CRLs */ ++static GHashTable *cached_crls; ++ ++void pv_cert_init(void) ++{ ++ if (!cached_crls) ++ cached_crls = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, ++ (GDestroyNotify)X509_CRL_free); ++} ++ ++void pv_cert_cleanup(void) ++{ ++ g_clear_pointer(&cached_crls, g_hash_table_destroy); ++} ++ ++PvX509WithPath *pv_x509_with_path_new(X509 *cert, const char *path) ++{ ++ g_autoptr(PvX509WithPath) ret = g_new(PvX509WithPath, 1); ++ ++ g_assert(cert && path); ++ ++ if (X509_up_ref(cert) != 1) ++ g_abort(); ++ ret->cert = cert; ++ ret->path = g_strdup(path); ++ return g_steal_pointer(&ret); ++} ++ ++void pv_x509_with_path_free(PvX509WithPath *cert) ++{ ++ if (!cert) ++ return; ++ ++ X509_free(cert->cert); ++ g_free(cert->path); ++ g_free(cert); ++} ++ ++PvX509Pair *pv_x509_pair_new_take(X509 **cert, STACK_OF_X509_CRL **crls) ++{ ++ g_autoptr(PvX509Pair) ret = g_new0(PvX509Pair, 1); ++ ++ g_assert(cert); ++ g_assert(crls); ++ ++ ret->cert = g_steal_pointer(cert); ++ ret->crls = g_steal_pointer(crls); ++ return g_steal_pointer(&ret); ++} ++ ++void pv_x509_pair_free(PvX509Pair *pair) ++{ ++ if (!pair) ++ return; ++ ++ sk_X509_CRL_pop_free(pair->crls, X509_CRL_free); ++ X509_free(pair->cert); ++ g_free(pair); ++} ++ ++void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack) ++{ ++ if (!stack) ++ return; ++ ++ sk_DIST_POINT_pop_free(stack, DIST_POINT_free); ++} ++ ++void STACK_OF_X509_free(STACK_OF_X509 *stack) ++{ ++ if (!stack) ++ return; ++ ++ sk_X509_pop_free(stack, X509_free); ++} ++ ++void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack) ++{ ++ if (!stack) ++ return; ++ ++ sk_X509_CRL_pop_free(stack, X509_CRL_free); ++} ++ ++static gboolean certificate_uses_elliptic_curve(EVP_PKEY *key, int nid, GError **error) ++{ ++ g_autoptr(EC_KEY) ec = NULL; ++ int rc; ++ ++ g_assert(key); ++ ++ if (EVP_PKEY_id(key) != EVP_PKEY_EC) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_PARM, _("No EC key found")); ++ return FALSE; ++ } ++ ++ ec = EVP_PKEY_get1_EC_KEY(key); ++ if (!ec) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_PARM, _("No EC key found")); ++ return FALSE; ++ } ++ ++ if (EC_KEY_check_key(ec) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_PARM, _("Invalid EC key")); ++ return FALSE; ++ } ++ ++ rc = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); ++ if (rc != nid) { ++ /* maybe the NID is unset */ ++ if (rc == 0) { ++ g_autoptr(EC_GROUP) grp = EC_GROUP_new_by_curve_name(nid); ++ const EC_POINT *pub = EC_KEY_get0_public_key(ec); ++ g_autoptr(BN_CTX) ctx = BN_CTX_new(); ++ ++ if (EC_POINT_is_on_curve(grp, pub, ctx) != 1) { ++ g_set_error_literal(error, PV_CERT_ERROR, ++ PV_CERT_ERROR_INVALID_PARM, ++ _("Invalid EC curve")); ++ return FALSE; ++ } ++ } else { ++ /* NID was set but doesn't match with the expected NID ++ */ ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_PARM, ++ _("Wrong NID used: '%d'"), ++ EC_GROUP_get_curve_name(EC_KEY_get0_group(ec))); ++ return FALSE; ++ } ++ } ++ return TRUE; ++} ++ ++EVP_PKEY *pv_x509_get_ec_pubkey(X509 *cert, int nid, GError **error) ++{ ++ g_autoptr(EVP_PKEY) ret = NULL; ++ ++ ret = X509_get_pubkey(cert); ++ if (!ret) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_PARM, ++ _("Failed to get public key from host-key document")); ++ return NULL; ++ } ++ ++ if (!certificate_uses_elliptic_curve(ret, nid, error)) { ++ g_prefix_error(error, _("Host-key document does not use an elliptic EC curve")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++GSList *pv_get_ec_pubkeys(PvCertWithPathList *certs_with_path, int nid, GError **error) ++{ ++ g_autoslist(EVP_PKEY) ret = NULL; ++ ++ for (GSList *iterator = certs_with_path; iterator; iterator = iterator->next) { ++ const PvX509WithPath *cert_with_path = iterator->data; ++ g_autoptr(EVP_PKEY) host_key = NULL; ++ X509 *cert = cert_with_path->cert; ++ ++ host_key = pv_x509_get_ec_pubkey(cert, nid, error); ++ if (!host_key) ++ return NULL; ++ ++ ret = g_slist_append(ret, g_steal_pointer(&host_key)); ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++PvCertWithPathList *pv_load_certificates(char **cert_paths, GError **error) ++{ ++ g_autoslist(PvX509WithPath) ret = NULL; ++ ++ for (char **iterator = cert_paths; iterator != NULL && *iterator != NULL; iterator++) { ++ const char *cert_path = *iterator; ++ g_autoptr(X509) cert = NULL; ++ ++ g_assert(cert_path); ++ ++ cert = pv_load_first_cert_from_file(cert_path, error); ++ if (!cert) ++ return NULL; ++ ++ ret = g_slist_append(ret, pv_x509_with_path_new(cert, cert_path)); ++ } ++ if (!ret) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CERTIFICATE, ++ _("no certificates specified")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++X509 *pv_load_first_cert_from_file(const char *path, GError **error) ++{ ++ g_autoptr(BIO) bio = BIO_new_file(path, "r"); ++ g_autoptr(X509) cert = NULL; ++ ++ if (!bio) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CERTIFICATE, ++ _("unable to read certificate: '%s'"), path); ++ return NULL; ++ } ++ ++ cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); ++ if (cert) ++ return g_steal_pointer(&cert); ++ ERR_clear_error(); ++ if (pv_BIO_reset(bio) < 0) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CERTIFICATE, ++ _("unable to load certificate: '%s'"), path); ++ return NULL; ++ } ++ ++ /* maybe the certificate is stored in DER format */ ++ cert = d2i_X509_bio(bio, NULL); ++ if (cert) ++ return g_steal_pointer(&cert); ++ ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CERTIFICATE, ++ _("unable to load certificate: '%s'"), path); ++ return NULL; ++} ++ ++static X509_CRL *load_crl_from_bio(BIO *bio) ++{ ++ g_autoptr(X509_CRL) crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); ++ if (crl) ++ return g_steal_pointer(&crl); ++ ERR_clear_error(); ++ if (pv_BIO_reset(bio) < 0) ++ return NULL; ++ ++ /* maybe the CRL is stored in DER format */ ++ crl = d2i_X509_CRL_bio(bio, NULL); ++ if (crl) ++ return g_steal_pointer(&crl); ++ return NULL; ++} ++ ++/* This function reads in only the first CRL and ignores all other. This is only ++ * relevant for the PEM file format. ++ */ ++X509_CRL *pv_load_first_crl_from_file(const char *path, GError **error) ++{ ++ g_autoptr(BIO) bio = BIO_new_file(path, "r"); ++ g_autoptr(X509_CRL) crl = NULL; ++ ++ if (!bio) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CRL, ++ _("unable to read CRL: '%s'"), path); ++ return NULL; ++ } ++ ++ crl = load_crl_from_bio(bio); ++ if (!crl) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_READ_CRL, ++ _("unable to load CRL: '%s'"), path); ++ return NULL; ++ } ++ return g_steal_pointer(&crl); ++} ++ ++static char *pv_X509_NAME_oneline(const X509_NAME *name) ++{ ++ g_autoptr(BIO) key_bio = BIO_new(BIO_s_mem()); ++ g_autofree char *ret = NULL; ++ char *key = NULL; ++ long len; ++ ++ if (X509_NAME_print_ex(key_bio, name, 0, XN_FLAG_RFC2253) == -1) { ++ g_warning(_("Cannot receive X509-NAME from CRL: %s"), pv_get_openssl_error()); ++ return NULL; ++ } ++ ++ len = BIO_get_mem_data(key_bio, &key); ++ if (len < 0) { ++ g_warning(_("Cannot receive X509-NAME from CRL")); ++ return NULL; ++ } ++ ++ ret = g_malloc0((size_t)len + 1); ++ memcpy(ret, key, (size_t)len); ++ return g_steal_pointer(&ret); ++} ++ ++static gboolean cache_crl(const X509_NAME *name, X509_CRL *crl) ++{ ++ g_autofree char *key = NULL; ++ ++ g_assert(name); ++ ++ key = pv_X509_NAME_oneline(name); ++ if (!key) { ++ g_warning(_("Cannot receive X509-NAME from CRL")); ++ return FALSE; ++ } ++ if (X509_CRL_up_ref(crl) != 1) ++ g_abort(); ++ return g_hash_table_insert(cached_crls, g_steal_pointer(&key), crl); ++} ++ ++/* Caller is responsible for free'ing */ ++static X509_CRL *lookup_crl(const X509_NAME *name) ++{ ++ g_autoptr(X509_CRL) crl = NULL; ++ g_autofree char *key = NULL; ++ ++ g_assert(name); ++ ++ key = pv_X509_NAME_oneline(name); ++ if (!key) ++ return NULL; ++ crl = g_hash_table_lookup(cached_crls, key); ++ if (crl) { ++ if (X509_CRL_up_ref(crl) != 1) ++ g_abort(); ++ return g_steal_pointer(&crl); ++ } ++ return NULL; ++} ++ ++/* Returns empty stack if no CRL downloaded. */ ++static STACK_OF_X509_CRL *crls_download_cb(const X509_STORE_CTX *ctx, const X509_NAME *nm) ++{ ++ g_autoptr(STACK_OF_X509_CRL) crls = NULL; ++ g_autoptr(X509_CRL) crl = NULL; ++ /* must not be free'd */ ++ X509 *cert = NULL; ++ ++ crls = sk_X509_CRL_new_null(); ++ if (!crls) ++ g_abort(); ++ cert = pv_X509_STORE_CTX_get_current_cert(ctx); ++ if (!cert) ++ return g_steal_pointer(&crls); ++ g_assert(X509_NAME_cmp(X509_get_issuer_name(cert), nm) == 0); ++ crl = lookup_crl(nm); ++ if (!crl) { ++ /* ignore error */ ++ crl = pv_load_first_crl_by_cert(cert, NULL); ++ if (!crl) ++ return g_steal_pointer(&crls); ++ g_assert_true(cache_crl(nm, crl)); ++ } ++ if (sk_X509_CRL_push(crls, g_steal_pointer(&crl)) == 0) ++ g_abort(); ++ return g_steal_pointer(&crls); ++} ++ ++/* Downloaded CRLs have a higher precedence than the CRLs specified on the ++ * command line. ++ */ ++static STACK_OF_X509_CRL *crls_cb(const X509_STORE_CTX *ctx, const X509_NAME *nm) ++{ ++ g_autoptr(STACK_OF_X509_CRL) crls = crls_download_cb(ctx, nm); ++ ++ if (sk_X509_CRL_num(crls) > 0) ++ return g_steal_pointer(&crls); ++ return pv_X509_STORE_CTX_get1_crls(ctx, nm); ++} ++ ++/* Set up CRL lookup with download support */ ++void pv_store_setup_crl_download(X509_STORE *st) ++{ ++ pv_X509_STORE_set_lookup_crls(st, crls_cb); ++} ++ ++static X509_CRL *GByteArray_to_X509_CRL(const GByteArray *data) ++{ ++ g_autoptr(X509_CRL) ret = NULL; ++ g_autoptr(BIO) bio = NULL; ++ ++ g_assert(data); ++ ++ if (data->len > INT_MAX) ++ return NULL; ++ ++ bio = BIO_new_mem_buf(data->data, (int)data->len); ++ if (!bio) ++ g_abort(); ++ ++ ret = load_crl_from_bio(bio); ++ if (!ret) ++ return NULL; ++ ++ return g_steal_pointer(&ret); ++} ++ ++static int load_crl_from_web(const char *url, X509_CRL **crl, GError **error) ++{ ++ g_autoptr(X509_CRL) tmp_crl = NULL; ++ g_autoptr(GByteArray) data = NULL; ++ g_assert(crl); ++ ++ data = curl_download(url, CRL_DOWNLOAD_TIMEOUT_MS, CRL_DOWNLOAD_MAX_SIZE, error); ++ if (!data) { ++ g_prefix_error(error, _("unable to download CRL: ")); ++ return -1; ++ } ++ tmp_crl = GByteArray_to_X509_CRL(data); ++ if (!tmp_crl) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CRL_DOWNLOAD_FAILED, ++ _("unable to load CRL from '%s'"), url); ++ return -1; ++ } ++ *crl = g_steal_pointer(&tmp_crl); ++ return 0; ++} ++ ++/* Get the first http[s] URL from a DIST_POINT */ ++static const char *get_first_dp_url(DIST_POINT *dp) ++{ ++ GENERAL_NAMES *general_names; ++ ++ g_assert(dp); ++ ++ if (!dp->distpoint || dp->distpoint->type != 0) ++ return NULL; ++ ++ general_names = dp->distpoint->name.fullname; ++ for (int i = 0; i < sk_GENERAL_NAME_num(general_names); i++) { ++ GENERAL_NAME *name = sk_GENERAL_NAME_value(general_names, i); ++ g_autofree const char *uri_str = NULL; ++ ASN1_STRING *uri_asn1; ++ const char *uri_data; ++ int uri_data_len; ++ int type; ++ ++ uri_asn1 = GENERAL_NAME_get0_value(name, &type); ++ if (type != GEN_URI) ++ continue; ++ uri_data_len = ASN1_STRING_length(uri_asn1); ++ if (uri_data_len < 0) ++ continue; ++ uri_data = (const char *)ASN1_STRING_get0_data(uri_asn1); ++ /* Make sure that uri_str is null-terminated as in general it ++ * cannot be assumed that @uri_data is null-terminated. ++ */ ++ uri_str = g_strndup(uri_data, (size_t)uri_data_len); ++ if (g_str_has_prefix(uri_str, "http://")) ++ return uri_data; ++ if (g_str_has_prefix(uri_str, "https://")) ++ return uri_data; ++ } ++ return NULL; ++} ++ ++/* Download a CRL using the URI specified in the distribution @crldp */ ++static X509_CRL *load_crl_by_dist_point(DIST_POINT *crldp, GError **error) ++{ ++ const char *uri = get_first_dp_url(crldp); ++ g_autoptr(X509_CRL) crl = NULL; ++ ++ if (!uri) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("no valid URL specified in distribution point")); ++ return NULL; ++ } ++ ++ if (load_crl_from_web(uri, &crl, error) < 0) ++ return NULL; ++ ++ return g_steal_pointer(&crl); ++} ++ ++/* This function returns the first X509_CRL found from the CRL distribution ++ * points specified in @cert. This function could be optimized by filtering ++ * duplicate certificates and/or filtering duplicated URIs. ++ */ ++X509_CRL *pv_load_first_crl_by_cert(X509 *cert, GError **error) ++{ ++ g_autoptr(STACK_OF_DIST_POINT) crldps = NULL; ++ g_autoptr(X509_CRL) ret = NULL; ++ ++ g_assert(cert); ++ ++ crldps = X509_get_ext_d2i(cert, NID_crl_distribution_points, NULL, NULL); ++ if (!crldps || sk_DIST_POINT_num(crldps) == 0) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRLDP, ++ _("no distribution point found")); ++ return NULL; ++ } ++ ++ for (int i = 0; i < sk_DIST_POINT_num(crldps); i++) { ++ DIST_POINT *crldp = sk_DIST_POINT_value(crldps, i); ++ ++ g_assert(crldp); ++ ++ /* ignore error */ ++ ret = load_crl_by_dist_point(crldp, NULL); ++ if (ret) ++ return g_steal_pointer(&ret); ++ } ++ ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_FAILED_DOWNLOAD_CRL, ++ _("failed to download CRL")); ++ return NULL; ++} ++ ++STACK_OF_X509_CRL *pv_try_load_crls_by_certs(GSList *certs_with_path) ++{ ++ g_autoptr(STACK_OF_X509_CRL) ret = sk_X509_CRL_new_null(); ++ if (!ret) ++ g_abort(); ++ ++ for (GSList *iterator = certs_with_path; iterator; iterator = iterator->next) { ++ PvX509WithPath *cert_with_path = iterator->data; ++ X509 *cert = cert_with_path->cert; ++ g_autoptr(X509_CRL) crl = NULL; ++ g_assert(cert); ++ /* ignore error */ ++ crl = pv_load_first_crl_by_cert(cert, NULL); ++ if (!crl) ++ continue; ++ if (sk_X509_CRL_push(ret, g_steal_pointer(&crl)) == 0) ++ g_abort(); ++ } ++ return g_steal_pointer(&ret); ++} ++ ++#define DEFINE_GSLIST_MAP(t2, t1) \ ++ typedef t1 *(*g_slist_map_func_##t2##_##t1)(const t2 *x, GError **error); \ ++ G_GNUC_UNUSED static GSList *g_slist_map_##t2##_##t1( \ ++ const GSList *list, g_slist_map_func_##t2##_##t1 func, GError **error) \ ++ { \ ++ g_autoslist(t1) ret = NULL; \ ++ for (const GSList *iterator = list; iterator; iterator = iterator->next) { \ ++ const t2 *value = iterator->data; \ ++ t1 *new_value = NULL; \ ++ g_assert(value); \ ++ new_value = func(value, error); \ ++ if (!new_value) \ ++ return NULL; \ ++ ret = g_slist_append(ret, g_steal_pointer(&new_value)); \ ++ } \ ++ return g_steal_pointer(&ret); \ ++ } ++ ++#define DEFINE_GSLIST_TO_STACK(t1) \ ++ G_GNUC_UNUSED static STACK_OF(t1) * g_slist_to_stack_of_##t1(GSList **list) \ ++ { \ ++ g_assert(list); \ ++ g_autoptr(STACK_OF_##t1) ret = sk_##t1##_new_null(); \ ++ if (!ret) \ ++ g_abort(); \ ++ for (GSList *iterator = *list; iterator; iterator = iterator->next) { \ ++ if (sk_##t1##_push(ret, g_steal_pointer(&iterator->data)) == 0) \ ++ g_abort(); \ ++ } \ ++ g_clear_pointer(list, g_slist_free); \ ++ return g_steal_pointer(&ret); \ ++ } ++ ++DEFINE_GSLIST_MAP(PvX509WithPath, X509) ++DEFINE_GSLIST_TO_STACK(X509) ++ ++static X509 *pv_x509_with_path_get_cert(const PvX509WithPath *cert_with_path, ++ G_GNUC_UNUSED GError **error) ++{ ++ g_autoptr(X509) cert = NULL; ++ ++ g_assert(cert_with_path && cert_with_path->cert); ++ ++ cert = cert_with_path->cert; ++ if (X509_up_ref(cert) != 1) ++ g_abort(); ++ return g_steal_pointer(&cert); ++} ++ ++/* @crl_paths is allowed to be NULL */ ++static int load_crls_to_store(X509_STORE *store, char **crl_paths, gboolean err_out_empty_crls, ++ GError **error) ++{ ++ for (char **iterator = crl_paths; iterator != NULL && *iterator != NULL; iterator++) { ++ X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); ++ const char *crl_path = *iterator; ++ int count; ++ ++ g_assert(crl_path); ++ ++ if (!lookup) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("X509 store initialization failed")); ++ return -1; ++ } ++ ++ /* support *.pem files containing multiple CRLs */ ++ count = X509_load_crl_file(lookup, crl_path, X509_FILETYPE_PEM); ++ if (count > 0) ++ continue; ++ ++ count = X509_load_crl_file(lookup, crl_path, X509_FILETYPE_ASN1); ++ if (count == 1) ++ continue; ++ ++ if (err_out_empty_crls) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_LOAD_CRL, ++ _("unable to load CRL from: '%s'"), crl_path); ++ return -1; ++ } ++ } ++ ++ return 0; ++} ++ ++X509_STORE *pv_store_setup(char *root_ca_path, char **crl_paths, char **cert_with_crl_paths, ++ GError **error) ++{ ++ g_autoptr(X509_STORE) store = X509_STORE_new(); ++ if (!store) ++ g_abort(); ++ ++ /* if @root_ca_path != NULL use the specified root CA only, otherwise use the ++ * default root CAs found on the system ++ */ ++ if (root_ca_path) { ++ X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); ++ int count; ++ ++ if (!lookup) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("X509 store initialization failed")); ++ return NULL; ++ } ++ ++ /* only the PEM format allows embedded CRLs so we've to ++ * check for it only here and not in case of ASN1 ++ */ ++ count = X509_load_cert_file(lookup, root_ca_path, X509_FILETYPE_PEM); ++ if (count > 0) { ++ /* Out of security reasons that it can be easily ++ * overseen that there are multiple certificates located ++ * in a PEM-file we raise an error ++ */ ++ if (count > 1) { ++ g_set_error( ++ error, PV_CERT_ERROR, PV_CERT_ERROR_LOAD_ROOT_CA, ++ _("multiple certificates in one PEM file is not supported: '%s'"), ++ root_ca_path); ++ return NULL; ++ } ++ ++ /* PEM format so it's possible there are CRLs embedded ++ */ ++ (void)X509_load_crl_file(lookup, root_ca_path, X509_FILETYPE_PEM); ++ } else { ++ /* Maybe the root CA is stored in ASN1 format */ ++ count = X509_load_cert_file(lookup, root_ca_path, X509_FILETYPE_ASN1); ++ if (count != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_LOAD_ROOT_CA, ++ _("failed to load root certificate from '%s'"), ++ root_ca_path); ++ return NULL; ++ } ++ } ++ } else { ++ /* Load certificates into @store from the hardcoded OpenSSL ++ * default paths ++ */ ++ if (X509_STORE_set_default_paths(store) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_LOAD_DEFAULT_CA, ++ _("failed to load system root certificates")); ++ return NULL; ++ } ++ } ++ ++ /* Error out if a CRL file was provided that has not at least one CRL*/ ++ if (load_crls_to_store(store, crl_paths, TRUE, error) < 0) ++ return NULL; ++ ++ /* Try to load CRLs from the provided untrusted certificates */ ++ if (load_crls_to_store(store, cert_with_crl_paths, FALSE, error) < 0) ++ return NULL; ++ ++ return g_steal_pointer(&store); ++} ++ ++STACK_OF_X509 *pv_get_x509_stack(const GSList *x509_with_path_list) ++{ ++ g_autoslist(X509) certs = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ certs = g_slist_map_PvX509WithPath_X509(x509_with_path_list, pv_x509_with_path_get_cert, ++ &error); ++ g_assert_no_error(error); ++ return g_slist_to_stack_of_X509(&certs); ++} ++ ++int pv_init_store_ctx(X509_STORE_CTX *ctx, X509_STORE *trusted, STACK_OF_X509 *chain, ++ GError **error) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(trusted); ++ pv_wrapped_g_assert(chain); ++ ++ if (X509_STORE_CTX_init(ctx, trusted, NULL, chain) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("X509 store initialization failed: %s"), ++ X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); ++ return -1; ++ } ++ return 0; ++} ++ ++X509_STORE_CTX *pv_create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, GError **error) ++{ ++ g_autoptr(X509_STORE_CTX) ctx = X509_STORE_CTX_new(); ++ ++ pv_wrapped_g_assert(trusted); ++ pv_wrapped_g_assert(chain); ++ ++ if (!ctx) ++ return NULL; ++ ++ if (pv_init_store_ctx(ctx, trusted, chain, error) < 0) ++ return NULL; ++ ++ return g_steal_pointer(&ctx); ++} ++ ++int pv_store_set_verify_param(X509_STORE *store, GError **error) ++{ ++ g_autoptr(X509_VERIFY_PARAM) param = NULL; ++ unsigned long flags = X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL | ++ X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_CHECK_SS_SIGNATURE | ++ X509_V_FLAG_X509_STRICT | X509_V_FLAG_POLICY_CHECK; ++ ++ /* Create a X509_VERIFY_PARAM structure, which specifies which checks ++ * should be done by the certificate verification operation ++ */ ++ param = X509_VERIFY_PARAM_new(); ++ if (!param) ++ g_abort(); ++ ++ /* The maximum depth level of the chain of trust for the verification of ++ * the IBM Z signing key is 2, i.e. IBM Z signing key -> intermediate CA ++ * -> root CA ++ */ ++ X509_VERIFY_PARAM_set_depth(param, 2); ++ ++ /* Set minimum allowed security level to at least 112 bits. */ ++ X509_VERIFY_PARAM_set_auth_level(param, PV_CERTS_SECURITY_LEVEL); ++ ++ /* Set verification purpose to 'Any Purpose' and specify that the ++ * associated trust setting of the default purpose should be used. ++ */ ++ if (X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_ANY | X509_TRUST_DEFAULT) != 1) ++ goto error; ++ ++ /* Each certificate from the chain of trust must be checked against a ++ * CRL to see if it has been revoked. In addition, use trusted ++ * certificates first mode, check signature of the last certificate, ++ * strict mode, and verify the policies. ++ */ ++ if (X509_VERIFY_PARAM_set_flags(param, flags) != 1) ++ goto error; ++ ++ if (X509_STORE_set1_param(store, param) != 1) ++ goto error; ++ ++ return 0; ++ ++error: ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("X509 store initialization failed")); ++ return -1; ++} ++ ++static int x509_name_entry_get0_data(X509_NAME_ENTRY *entry, const uint8_t **data, size_t *data_len) ++{ ++ const ASN1_STRING *asn1_str; ++ int tmp_data_len; ++ ++ g_assert(data); ++ g_assert(data_len); ++ ++ asn1_str = X509_NAME_ENTRY_get_data(entry); ++ if (!asn1_str) ++ return -1; ++ ++ tmp_data_len = ASN1_STRING_length(asn1_str); ++ if (tmp_data_len < 0) ++ return -1; ++ ++ *data = ASN1_STRING_get0_data(asn1_str); ++ *data_len = (size_t)tmp_data_len; ++ return 0; ++} ++ ++/* The caller must not free *data! */ ++static int x509_name_get0_data_by_NID(X509_NAME *name, int nid, const uint8_t **data, ++ size_t *data_len) ++{ ++ X509_NAME_ENTRY *entry = NULL; ++ int lastpos = -1; ++ ++ lastpos = X509_NAME_get_index_by_NID(name, nid, lastpos); ++ if (lastpos == -1) ++ return -1; ++ ++ entry = X509_NAME_get_entry(name, lastpos); ++ if (!entry) ++ return -1; ++ ++ if (x509_name_entry_get0_data(entry, data, data_len) < 0) ++ return -1; ++ ++ return 0; ++} ++ ++/* @y must be a NULL-terminated string */ ++static gboolean x509_name_data_by_nid_equal(X509_NAME *name, int nid, const char *y) ++{ ++ const uint8_t *data = NULL; ++ size_t y_len = strlen(y); ++ size_t data_len; ++ ++ if (x509_name_get0_data_by_NID(name, nid, &data, &data_len) < 0) ++ return FALSE; ++ ++ if (data_len != y_len) ++ return FALSE; ++ ++ return memcmp(data, y, data_len) == 0; ++} ++ ++/* Checks whether the subject of @cert is a IBM signing key subject. For this we ++ * must check that the subject is equal to: 'C = US, ST = New York, L = ++ * Poughkeepsie, O = International Business Machines Corporation, CN = ++ * International Business Machines Corporation' and the organization unit (OUT) ++ * must end with the suffix ' Key Signing Service'. ++ */ ++static gboolean has_ibm_signing_subject(X509 *cert) ++{ ++ X509_NAME *subject = X509_get_subject_name(cert); ++ /* X509_NAME_entry_count is safe to be used with NULL */ ++ int entry_count = X509_NAME_entry_count(subject); ++ g_autofree char *data_str = NULL; ++ const uint8_t *data; ++ size_t data_len; ++ ++ if (entry_count != PV_IMB_Z_SUBJECT_ENTRY_COUNT) ++ return FALSE; ++ ++ if (!x509_name_data_by_nid_equal(subject, NID_countryName, PV_IBM_Z_SUBJECT_COUNTRY_NAME)) ++ return FALSE; ++ ++ if (!x509_name_data_by_nid_equal(subject, NID_stateOrProvinceName, PV_IBM_Z_SUBJECT_STATE)) ++ return FALSE; ++ ++ if (!x509_name_data_by_nid_equal(subject, NID_localityName, PV_IBM_Z_SUBJECT_LOCALITY_NAME)) ++ return FALSE; ++ ++ if (!x509_name_data_by_nid_equal(subject, NID_organizationName, ++ PV_IBM_Z_SUBJECT_ORGANIZATION_NAME)) ++ return FALSE; ++ ++ if (!x509_name_data_by_nid_equal(subject, NID_commonName, PV_IBM_Z_SUBJECT_COMMON_NAME)) ++ return FALSE; ++ ++ if (x509_name_get0_data_by_NID(subject, NID_organizationalUnitName, &data, &data_len) < 0) ++ return FALSE; ++ ++ /* Make sure that data_str is null-terminated as in general it cannot be ++ * assumed that @data is null-terminated. ++ */ ++ data_str = g_strndup((const char *)data, data_len); ++ if (!g_str_has_suffix(data_str, PV_IBM_Z_SUBJECT_ORGANIZATIONAL_UNIT_NAME_SUFFIX)) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++/* Return a list of all IBM Z signing key certificates in @certs and remove them ++ * from the chain. Return empty stack if no IBM Z signing key is found. ++ */ ++STACK_OF_X509 *pv_remove_ibm_signing_certs(STACK_OF_X509 *certs) ++{ ++ g_autoptr(STACK_OF_X509) ret = sk_X509_new_null(); ++ ++ for (int i = 0; i < sk_X509_num(certs); i++) { ++ X509 *cert = sk_X509_value(certs, i); ++ ++ g_assert(cert); ++ ++ if (!has_ibm_signing_subject(cert)) ++ continue; ++ ++ /* Remove this certificate from the list and change i-- as the ++ * array has changed - this is not beautiful, but right now the ++ * easiest solution I came up with. ++ */ ++ if (sk_X509_delete(certs, i--) != cert) ++ g_abort(); ++ ++ if (sk_X509_push(ret, g_steal_pointer(&cert)) == 0) ++ g_abort(); ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++static X509_NAME *x509_name_reorder_attributes(const X509_NAME *name, const int nids[], ++ size_t nids_len) ++{ ++ int entry_count = X509_NAME_entry_count(name); ++ g_autoptr(X509_NAME) ret = NULL; ++ ++ if (entry_count < 0) ++ return NULL; ++ ++ if (nids_len != (size_t)entry_count) ++ return NULL; ++ ++ ret = X509_NAME_new(); ++ if (!ret) ++ g_abort(); ++ ++ for (size_t i = 0; i < nids_len; i++) { ++ const X509_NAME_ENTRY *entry = NULL; ++ int nid = nids[i]; ++ int lastpos = -1; ++ ++ lastpos = X509_NAME_get_index_by_NID((X509_NAME *)name, nid, lastpos); ++ if (lastpos == -1) ++ return NULL; ++ ++ entry = X509_NAME_get_entry(name, lastpos); ++ if (!entry) ++ return NULL; ++ ++ if (X509_NAME_add_entry(ret, entry, -1, 0) != 1) ++ return NULL; ++ } ++ return g_steal_pointer(&ret); ++} ++ ++X509_NAME *pv_c2b_name(const X509_NAME *name) ++{ ++ int nids[] = { NID_countryName, NID_organizationName, NID_organizationalUnitName, ++ NID_localityName, NID_stateOrProvinceName, NID_commonName }; ++ g_autoptr(X509_NAME) broken_name = NULL; ++ ++ g_assert(name); ++ ++ /* Try to reorder the attributes */ ++ broken_name = x509_name_reorder_attributes(name, nids, G_N_ELEMENTS(nids)); ++ if (broken_name) ++ return g_steal_pointer(&broken_name); ++ return X509_NAME_dup((X509_NAME *)name); ++} ++ ++static int security_level_to_bits(int level) ++{ ++ static int security_bits[] = { 0, 80, 112, 128, 192, 256 }; ++ ++ g_assert(level > 0 && level < (int)G_N_ELEMENTS(security_bits)); ++ ++ return security_bits[level]; ++} ++ ++/* returns ++ * 0 when the certificate is valid, ++ * -1 when not yet valid, ++ * 1 when expired ++ */ ++static int check_validity_period(const ASN1_TIME *not_before, const ASN1_TIME *not_after) ++{ ++ if (X509_cmp_current_time(not_before) != -1) ++ return -1; ++ ++ if (X509_cmp_current_time(not_after) != 1) ++ return 1; ++ ++ return 0; ++} ++ ++static gboolean own_X509_NAME_ENTRY_equal(const X509_NAME_ENTRY *x, const X509_NAME_ENTRY *y) ++{ ++ const ASN1_OBJECT *x_obj = X509_NAME_ENTRY_get_object(x); ++ const ASN1_STRING *x_data = X509_NAME_ENTRY_get_data(x); ++ const ASN1_OBJECT *y_obj = X509_NAME_ENTRY_get_object(y); ++ const ASN1_STRING *y_data = X509_NAME_ENTRY_get_data(y); ++ int x_len = ASN1_STRING_length(x_data); ++ int y_len = ASN1_STRING_length(y_data); ++ ++ if (x_len < 0 || x_len != y_len) ++ return FALSE; ++ ++ /* ASN1_STRING_cmp(x_data, y_data) == 0 doesn't work because it also ++ * compares the type, which is sometimes different. ++ */ ++ return OBJ_cmp(x_obj, y_obj) == 0 && ++ memcmp(ASN1_STRING_get0_data(x_data), ASN1_STRING_get0_data(y_data), ++ (unsigned long)x_len) == 0; ++} ++ ++static gboolean own_X509_NAME_equal(const X509_NAME *x, const X509_NAME *y) ++{ ++ int x_count = X509_NAME_entry_count(x); ++ int y_count = X509_NAME_entry_count(y); ++ ++ if (x != y && (!x || !y)) ++ return FALSE; ++ ++ if (x_count != y_count) ++ return FALSE; ++ ++ for (int i = 0; i < x_count; i++) { ++ const X509_NAME_ENTRY *entry_i = X509_NAME_get_entry(x, i); ++ gboolean entry_found = FALSE; ++ ++ for (int j = 0; j < y_count; j++) { ++ const X509_NAME_ENTRY *entry_j = X509_NAME_get_entry(y, j); ++ ++ if (own_X509_NAME_ENTRY_equal(entry_i, entry_j)) { ++ entry_found = TRUE; ++ break; ++ } ++ } ++ ++ if (!entry_found) ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++/* Verify that the used public key algorithm matches the subject signature ++ * algorithm ++ */ ++static int check_signature_algo_match(const EVP_PKEY *pkey, const X509 *subject, GError **error) ++{ ++ int pkey_nid; ++ ++ if (!pkey) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_PUBLIC_KEY, _("no public key")); ++ return -1; ++ } ++ ++ if (OBJ_find_sigid_algs(X509_get_signature_nid(subject), NULL, &pkey_nid) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_SIGNATURE_ALGORITHM, ++ _("unsupported signature algorithm")); ++ return -1; ++ } ++ ++ if (EVP_PKEY_type(pkey_nid) != EVP_PKEY_base_id(pkey)) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_SIGNATURE_ALGORITHM_MISMATCH, ++ _("signature algorithm mismatch")); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* It's almost the same as X509_check_issed from OpenSSL does except that we ++ * don't check the key usage of the potential issuer. This means we check: ++ * 1. issuer_name(cert) == subject_name(issuer) ++ * 2. Check whether the akid(cert) (if available) matches the issuer skid ++ * 3. Check that the cert algrithm matches the subject algorithm ++ * 4. Verify the signature of certificate @cert is using the public key of ++ * @issuer. ++ */ ++static int check_host_key_issued(X509 *cert, X509 *issuer, GError **error) ++{ ++ const X509_NAME *issuer_subject = X509_get_subject_name(issuer); ++ const X509_NAME *cert_issuer = X509_get_issuer_name(cert); ++ g_autoptr(AUTHORITY_KEYID) akid = NULL; ++ ++ /* We cannot use X509_NAME_cmp() because it considers the order of the ++ * X509_NAME_Entries. ++ */ ++ if (!own_X509_NAME_equal(issuer_subject, cert_issuer)) { ++ g_autofree char *issuer_subject_str = pv_X509_NAME_oneline(issuer_subject); ++ g_autofree char *cert_issuer_str = pv_X509_NAME_oneline(cert_issuer); ++ ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CERT_SUBJECT_ISSUER_MISMATCH, ++ _("Subject issuer mismatch:\n'%s'\n'%s'"), issuer_subject_str, ++ cert_issuer_str); ++ return -1; ++ } ++ ++ akid = X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); ++ if (akid && X509_check_akid(issuer, akid) != X509_V_OK) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_SKID_AKID_MISMATCH, ++ _("AKID mismatch")); ++ return -1; ++ } ++ ++ if (check_signature_algo_match(X509_get0_pubkey(issuer), cert, error) < 0) ++ return -1; ++ ++ if (X509_verify(cert, X509_get0_pubkey(issuer)) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CERT_SIGNATURE_INVALID, ++ _("Signature verification failed")); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static gboolean is_cert_revoked(X509 *cert, X509_CRL *crl) ++{ ++ X509_REVOKED *revoked = NULL; ++ int rc; ++ ++ if (!cert || !crl) ++ g_abort(); ++ ++ rc = X509_CRL_get0_by_serial(crl, &revoked, (ASN1_INTEGER *)X509_get0_serialNumber(cert)); ++ if (rc == 0) ++ return FALSE; ++ ++ if (revoked) ++ return TRUE; ++ ++ return FALSE; ++} ++ ++/* Assumptions are that the issuer_crt and issuer_crl is a trusted IBM Z ++ * signing certificate/revocation list. This function verifies a host-key ++ * document. To do so multiple steps are required: ++ * ++ * 1. issuer(host_key) == subject(issuer_crt) ++ * 2. Signature verification ++ * 3. @host_key must not be expired ++ * 4. @host_key must not be revoked ++ */ ++int pv_verify_host_key(X509 *host_key, GSList *issuer_pairs, int verify_flags, int level, ++ GError **error) ++{ ++ const int exp_security_bits = security_level_to_bits(level); ++ EVP_PKEY *pkey; ++ gboolean successfully_checked = FALSE; ++ int pkey_security_bits; ++ ++ g_assert(host_key); ++ pkey = X509_get0_pubkey(host_key); ++ ++ if (!pkey) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("failed to retrieve public key")); ++ return -1; ++ } ++ ++ /* check key level, if necessary */ ++ pkey_security_bits = EVP_PKEY_security_bits(pkey); ++ if (exp_security_bits > 0 && pkey_security_bits < exp_security_bits) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_VERIFICATION_FAILED, ++ _("not enough bits of security (%d, %d expected)"), pkey_security_bits, ++ exp_security_bits); ++ return -1; ++ } ++ ++ if (!(verify_flags & X509_V_FLAG_NO_CHECK_TIME)) { ++ const ASN1_TIME *last = X509_get0_notBefore(host_key); ++ const ASN1_TIME *next = X509_get0_notAfter(host_key); ++ ++ if (!last || !next || check_validity_period(last, next)) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_VALIDITY_PERIOD, ++ _("validity period is not valid")); ++ return -1; ++ } ++ } else { ++ verify_flags &= ~X509_V_FLAG_NO_CHECK_TIME; ++ } ++ ++ /* Verify that the host_key was issued by a certificate and that it ++ * wasn't revoked. ++ */ ++ for (GSList *iterator = issuer_pairs; iterator; iterator = iterator->next) { ++ const PvX509Pair *pair = iterator->data; ++ STACK_OF_X509_CRL *issuer_crls = NULL; ++ X509 *issuer_cert = NULL; ++ ++ g_assert(pair); ++ ++ issuer_cert = pair->cert; ++ issuer_crls = pair->crls; ++ ++ g_assert(issuer_cert); ++ ++ /* Verify that the issuer(host_key) == subject(issuer_cert) and ++ * that the signature is valid ++ */ ++ if (check_host_key_issued(host_key, issuer_cert, NULL) < 0) ++ continue; ++ ++ /* Check against CRL */ ++ if (verify_flags & X509_V_FLAG_CRL_CHECK) { ++ gboolean crl_checked = FALSE; ++ ++ verify_flags &= ~X509_V_FLAG_CRL_CHECK; ++ for (int i = 0; i < sk_X509_CRL_num(issuer_crls); i++) { ++ X509_CRL *issuer_crl = sk_X509_CRL_value(issuer_crls, i); ++ ++ g_assert(issuer_crl); ++ ++ if (is_cert_revoked(host_key, issuer_crl)) { ++ g_set_error(error, PV_CERT_ERROR, ++ PV_CERT_ERROR_CERT_REVOKED, ++ _("certificate revoked")); ++ return -1; ++ } ++ ++ crl_checked = TRUE; ++ } ++ ++ if (!crl_checked) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRL, ++ _("no valid CRL found")); ++ return -1; ++ } ++ successfully_checked = TRUE; ++ break; ++ } ++ } ++ ++ if (!successfully_checked) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_ISSUER_IBM_Z_FOUND, ++ _("no IBM Z signing key that issued this host-key document found")); ++ return -1; ++ } ++ ++ /* were some unsupported flags specified? */ ++ g_assert(verify_flags == 0); ++ return 0; ++} ++ ++int pv_verify_cert(X509_STORE_CTX *ctx, X509 *cert, GError **error) ++{ ++ int rc; ++ ++ pv_wrapped_g_assert(cert); ++ pv_wrapped_g_assert(ctx); ++ ++ X509_STORE_CTX_set_cert(ctx, cert); ++ rc = X509_verify_cert(ctx); ++ if (rc != 1) { ++ X509 *tmp_cert = NULL; ++ ++ tmp_cert = pv_X509_STORE_CTX_get_current_cert(ctx); ++ if (tmp_cert) { ++ g_autofree char *subj_name = ++ pv_X509_NAME_oneline(X509_get_subject_name(tmp_cert)); ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_VERIFICATION_FAILED, ++ _("failed to verify certificate '%s': %s"), subj_name, ++ X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); ++ } else { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_VERIFICATION_FAILED, ++ _("failed to verify certificate: %s"), ++ X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); ++ } ++ return -1; ++ } ++ return 0; ++} ++ ++/* Verify that: subject(issuer) == issuer(crl) and SKID(issuer) == AKID(crl) */ ++static int check_crl_issuer(X509_CRL *crl, X509 *issuer, GError **error) ++{ ++ const X509_NAME *crl_issuer = X509_CRL_get_issuer(crl); ++ const X509_NAME *issuer_subject = X509_get_subject_name(issuer); ++ AUTHORITY_KEYID *akid = NULL; ++ ++ if (!own_X509_NAME_equal(issuer_subject, crl_issuer)) { ++ g_autofree char *issuer_subject_str = pv_X509_NAME_oneline(issuer_subject); ++ g_autofree char *crl_issuer_str = pv_X509_NAME_oneline(crl_issuer); ++ ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, ++ _("issuer mismatch:\n%s\n%s"), issuer_subject_str, crl_issuer_str); ++ return -1; ++ } ++ ++ /* If AKID(@crl) is specified it must match with SKID(@issuer) */ ++ akid = X509_CRL_get_ext_d2i(crl, NID_authority_key_identifier, NULL, NULL); ++ if (akid && X509_check_akid(issuer, akid) != X509_V_OK) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_SKID_AKID_MISMATCH, ++ _("AKID mismatch")); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int pv_verify_crl(X509_CRL *crl, X509 *cert, int verify_flags, GError **error) ++{ ++ EVP_PKEY *pkey = X509_get0_pubkey(cert); ++ ++ g_assert(crl); ++ ++ if (!pkey) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("failed to retrieve public key from the certificate")); ++ return -1; ++ } ++ ++ /* check that the @crl issuer matches with the subject name of @cert*/ ++ if (check_crl_issuer(crl, cert, error) < 0) ++ return -1; ++ ++ /* verify the validity period of the CRL */ ++ if (!(verify_flags & X509_V_FLAG_NO_CHECK_TIME)) { ++ const ASN1_TIME *last = X509_CRL_get0_lastUpdate(crl); ++ const ASN1_TIME *next = X509_CRL_get0_nextUpdate(crl); ++ ++ if (!last || !next || check_validity_period(last, next)) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INVALID_VALIDITY_PERIOD, ++ _("validity period is not valid")); ++ return -1; ++ } ++ } else { ++ verify_flags &= ~X509_V_FLAG_NO_CHECK_TIME; ++ } ++ ++ /* verify the signature */ ++ if (X509_CRL_verify(crl, pkey) != 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CRL_SIGNATURE_INVALID, ++ _("signature is not valid")); ++ return -1; ++ } ++ g_assert(verify_flags == 0); ++ return 0; ++} ++ ++int pv_check_chain_parameters(const STACK_OF_X509 *chain, GError **error) ++{ ++ const X509_NAME *ca_x509_subject = NULL; ++ g_autofree char *ca_subject = NULL; ++ int len = sk_X509_num(chain); ++ X509 *ca = NULL; ++ ++ if (len < 2) { ++ g_set_error( ++ error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("there must be at least one root and one leaf certificate in the chain of trust")); ++ return -1; ++ } ++ ++ /* get the root certificate of the chain of trust */ ++ ca = sk_X509_value(chain, len - 1); ++ if (!ca) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_INTERNAL, ++ _("no root certificate found")); ++ return -1; ++ } ++ ++ ca_x509_subject = X509_get_subject_name(ca); ++ if (!ca_x509_subject) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("subject of the root CA cannot be retrieved")); ++ return -1; ++ } ++ ++ ca_subject = pv_X509_NAME_oneline(ca_x509_subject); ++ if (!ca_subject) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("subject name of the root CA cannot be retrieved")); ++ return -1; ++ } ++ g_info(_("Root CA used: '%s'"), ca_subject); ++ ++ return 0; ++} ++ ++/* Given a certificate @cert try to find valid revocation lists in @ctx. If no ++ * valid CRL was found NULL is returned. ++ */ ++STACK_OF_X509_CRL *pv_store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, GError **error) ++{ ++ g_autoptr(STACK_OF_X509_CRL) ret = NULL; ++ const int verify_flags = 0; ++ X509_NAME *subject = NULL; ++ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(cert); ++ ++ subject = X509_get_subject_name(cert); ++ if (!subject) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_MALFORMED_CERTIFICATE, ++ _("certificate is malformed")); ++ return NULL; ++ } ++ ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, subject); ++ if (!ret) { ++ /* Workaround to fix the mismatch between issuer name of the ++ * IBM Z signing CRLs and the IBM Z signing key subject name. ++ */ ++ g_autoptr(X509_NAME) broken_subject = pv_c2b_name(subject); ++ ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, broken_subject); ++ if (!ret) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRL, _("no CRL found")); ++ g_info("ERROR: %s", (*error)->message); ++ return NULL; ++ } ++ } ++ ++ /* Filter out non-valid CRLs for @cert */ ++ for (int i = 0; i < sk_X509_CRL_num(ret); i++) { ++ X509_CRL *crl = sk_X509_CRL_value(ret, i); ++ ++ g_assert(crl); ++ ++ /* If @crl is not valid remove it from the array and log a ++ * warning. ++ */ ++ if (pv_verify_crl(crl, cert, verify_flags, error) < 0) { ++ g_assert(error); ++ g_warning(_("CRL is not valid: %s"), (*error)->message); ++ g_clear_error(error); ++ ++ /* Remove this certificate from the list and change i-- as the ++ * array has changed - this is not beautfiul, but right now the ++ * easiest solution I came up with ++ */ ++ if (sk_X509_CRL_delete(ret, i--) != crl) ++ g_abort(); ++ ++ g_clear_pointer(&crl, X509_CRL_free); ++ } ++ } ++ ++ if (sk_X509_CRL_num(ret) < 1) { ++ g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRL, _("no valid CRL found")); ++ return NULL; ++ } ++ return g_steal_pointer(&ret); ++} ++ ++/* ++ * Finds the IBM signing key in the stack. ++ * Error out, if there is not exactly one IBM signing key. ++ */ ++static STACK_OF_X509 *get_ibm_signing_certs(STACK_OF_X509 *certs, GError **error) ++{ ++ g_autoptr(STACK_OF_X509) ibm_signing_certs = NULL; ++ int ibm_signing_certs_count; ++ ++ /* Find all IBM Z signing keys and remove them from the chain as we ++ * have to verify that they're valid. The last step of the chain of ++ * trust verification must be done manually, as the IBM Z signing keys ++ * are not marked as (intermediate) CA and therefore the standard ++ * `X509_verify_cert` function of OpenSSL cannot be used to verify the ++ * actual host-key documents. ++ */ ++ ibm_signing_certs = pv_remove_ibm_signing_certs(certs); ++ ibm_signing_certs_count = sk_X509_num(ibm_signing_certs); ++ if (ibm_signing_certs_count < 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, ++ _("Specify at least one IBM Z signing key")); ++ return NULL; ++ } else if (ibm_signing_certs_count > 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, ++ _("Specify only one IBM Z signing key")); ++ return NULL; ++ } ++ g_assert(ibm_signing_certs_count == 1); ++ ++ return g_steal_pointer(&ibm_signing_certs); ++} ++ ++static gboolean download_crls(X509_STORE *trusted, PvCertWithPathList *host_key_certs_with_path, ++ GError **error) ++{ ++ g_autoptr(STACK_OF_X509_CRL) downloaded_ibm_signing_crls = NULL; ++ ++ /* Set up the download routine for the lookup of CRLs. */ ++ pv_store_setup_crl_download(trusted); ++ ++ /* Try to download the CRLs of the IBM Z signing certificates ++ * specified in the host-key documents. Ignore download errors ++ * as it's still possible that a CRL is specified via command ++ * line. ++ */ ++ downloaded_ibm_signing_crls = pv_try_load_crls_by_certs(host_key_certs_with_path); ++ ++ /* Add the downloaded CRLs to the store so they can be used for ++ * the verification later. ++ */ ++ for (int i = 0; i < sk_X509_CRL_num(downloaded_ibm_signing_crls); i++) { ++ X509_CRL *crl = sk_X509_CRL_value(downloaded_ibm_signing_crls, i); ++ ++ if (X509_STORE_add_crl(trusted, crl) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("failed to load CRL")); ++ return FALSE; ++ } ++ } ++ return TRUE; ++} ++ ++gboolean pv_verify_host_key_doc(PvCertWithPathList *host_key_certs_with_path, X509_STORE *trusted, ++ STACK_OF_X509 *untrusted_certs, gboolean online, GError **error) ++{ ++ g_autoslist(PvX509Pair) ibm_z_pairs = NULL; ++ g_autoptr(STACK_OF_X509) ibm_signing_certs = NULL; ++ g_autoptr(X509_STORE_CTX) ctx = NULL; ++ ++ pv_wrapped_g_assert(host_key_certs_with_path); ++ pv_wrapped_g_assert(trusted); ++ pv_wrapped_g_assert(untrusted_certs); ++ ++ if (online && !download_crls(trusted, host_key_certs_with_path, error)) ++ return -1; ++ ++ /* Find all IBM Z signing keys and remove them from the chain as we ++ * have to verify that they're valid. The last step of the chain of ++ * trust verification must be done manually, as the IBM Z signing keys ++ * are not marked as (intermediate) CA and therefore the standard ++ * `X509_verify_cert` function of OpenSSL cannot be used to verify the ++ * actual host-key documents. ++ */ ++ ibm_signing_certs = get_ibm_signing_certs(untrusted_certs, error); ++ if (!ibm_signing_certs) ++ return -1; ++ ++ if (pv_store_set_verify_param(trusted, error) < 0) ++ return -1; ++ ++ ctx = pv_create_store_ctx(trusted, untrusted_certs, error); ++ if (!ctx) ++ return -1; ++ /* ++ * Get all IBM-signing-[key,crls] pairs. ++ * NOTE: Currently there is only one signing-key allowed ++ */ ++ for (int i = 0; i < sk_X509_num(ibm_signing_certs); i++) { ++ g_autoptr(X509) ibm_signing_cert = sk_X509_pop(ibm_signing_certs); ++ g_autoptr(STACK_OF_X509_CRL) ibm_signing_crls = NULL; ++ PvX509Pair *ibm_z_pair = NULL; ++ ++ /* ++ * Get CRLs for the IBM signing cert ++ */ ++ ibm_signing_crls = pv_store_ctx_find_valid_crls(ctx, ibm_signing_cert, error); ++ if (!ibm_signing_crls) { ++ g_prefix_error(error, _("IBM Z signing key: ")); ++ return -1; ++ } ++ ++ /* build the pair and add it to the list */ ++ ibm_z_pair = pv_x509_pair_new_take(&ibm_signing_cert, &ibm_signing_crls); ++ g_assert(!ibm_signing_cert); ++ g_assert(!ibm_signing_crls); ++ ibm_z_pairs = g_slist_append(ibm_z_pairs, ibm_z_pair); ++ } ++ ++ /* Verify host-key documents by using the IBM Z signing ++ * certificates and the corresponding certificate revocation ++ * lists. ++ */ ++ for (GSList *iterator = host_key_certs_with_path; iterator; iterator = iterator->next) { ++ PvX509WithPath *host_key_with_path = iterator->data; ++ const char *host_key_path = host_key_with_path->path; ++ X509 *host_key = host_key_with_path->cert; ++ int flags = X509_V_FLAG_CRL_CHECK; ++ ++ if (pv_verify_host_key(host_key, ibm_z_pairs, flags, PV_CERTS_SECURITY_LEVEL, ++ error) < 0) { ++ g_prefix_error(error, "'%s': ", host_key_path); ++ return -1; ++ } ++ } ++ ++ /* Verify that all IBM Z signing keys are trustable. ++ * For this we must check: ++ * ++ * 1. Can a chain of trust be established ending in a root CA ++ * 2. Is the correct root CA used? It has either to be the ++ * System CA or the root CA specified via command line. ++ */ ++ for (GSList *iterator = ibm_z_pairs; iterator; iterator = iterator->next) { ++ const PvX509Pair *ibm_z_pair = iterator->data; ++ ++ if (pv_verify_cert(ctx, ibm_z_pair->cert, error) < 0) ++ return -1; ++ if (pv_check_chain_parameters(X509_STORE_CTX_get0_chain(ctx), error) < 0) ++ return -1; ++ /* re-init ctx for the next verification */ ++ X509_STORE_CTX_cleanup(ctx); ++ if (pv_init_store_ctx(ctx, trusted, untrusted_certs, error) != 0) ++ return -1; ++ } ++ return 0; ++} ++ ++int pv_verify_host_key_docs_by_path(char **host_key_paths, char *optional_root_ca_path, ++ char **crl_paths, char **untrusted_cert_paths, gboolean online, ++ GError **error) ++{ ++ g_autoslist(PvX509WithPath) untrusted_certs_with_path = NULL, host_key_certs = NULL; ++ g_autoptr(STACK_OF_X509) untrusted_certs = NULL; ++ g_autoptr(X509_STORE) trusted = NULL; ++ ++ pv_wrapped_g_assert(host_key_paths); ++ pv_wrapped_g_assert(untrusted_cert_paths); ++ ++ /* Load trusted root CAs of the system if and only if @root_ca_path is ++ * NULL, otherwise use the root CA specified by @root_ca_path. ++ */ ++ trusted = pv_store_setup(optional_root_ca_path, crl_paths, untrusted_cert_paths, error); ++ if (!trusted) ++ return -1; ++ ++ /* Load all untrusted certificates (e.g. IBM Z signing key and ++ * intermediate CA) that are required to establish a chain of ++ * trust starting from the host-key document up to the root CA (if not ++ * otherwise specified that can be one of the system wide installed ++ * root CAs, e.g. DigiCert). ++ */ ++ untrusted_certs_with_path = pv_load_certificates(untrusted_cert_paths, error); ++ if (!untrusted_certs_with_path) ++ return -1; ++ /* Convert to STACK_OF(X509) */ ++ untrusted_certs = pv_get_x509_stack(untrusted_certs_with_path); ++ ++ host_key_certs = pv_load_certificates(host_key_paths, error); ++ if (!host_key_certs) ++ return -1; ++ ++ return pv_verify_host_key_doc(host_key_certs, trusted, untrusted_certs, online, error); ++} +diff --git a/libpv/common.c b/libpv/common.c +new file mode 100644 +index 00000000..547d88a0 +--- /dev/null ++++ b/libpv/common.c +@@ -0,0 +1,45 @@ ++/* ++ * Libpv common functions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ * ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/common.h" ++#include "libpv/cert.h" ++#include "libpv/curl.h" ++ ++/* setup and tear down */ ++int pv_init(void) ++{ ++ static size_t openssl_initalized; ++ ++ if (g_once_init_enter(&openssl_initalized)) { ++ if (OPENSSL_VERSION_NUMBER < 0x1000100fL) ++ g_assert_not_reached(); ++#if OPENSSL_VERSION_NUMBER < 0x10100000L ++ SSL_library_init(); ++ SSL_load_error_strings(); ++#else ++ OPENSSL_init_crypto(0, NULL); ++#endif ++ ++ if (pv_curl_init() != 0) ++ return -1; ++ ++ pv_cert_init(); ++ g_once_init_leave(&openssl_initalized, 1); ++ } ++ return 0; ++} ++ ++void pv_cleanup(void) ++{ ++ pv_cert_cleanup(); ++ pv_curl_cleanup(); ++} +diff --git a/libpv/config.h b/libpv/config.h +new file mode 100644 +index 00000000..4e3536ea +--- /dev/null ++++ b/libpv/config.h +@@ -0,0 +1,15 @@ ++/* ++ * Config file. ++ * Must be include before any other header. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ * ++ */ ++#ifndef LIBPV_CONFIG_H ++#define LIBPV_CONFIG_H ++#define GETTEXT_PACKAGE "libpv" ++ ++#endif /* LIBPV_CONFIG_H */ +diff --git a/libpv/crypto.c b/libpv/crypto.c +new file mode 100644 +index 00000000..207795d3 +--- /dev/null ++++ b/libpv/crypto.c +@@ -0,0 +1,529 @@ ++/* ++ * Cryptography functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "lib/zt_common.h" ++#include "libpv/crypto.h" ++#include "libpv/glib-helper.h" ++#include "libpv/hash.h" ++ ++const char *pv_get_openssl_error(void) ++{ ++ const char *ret; ++ BIO *bio; ++ char *buf; ++ long len; ++ ++ bio = BIO_new(BIO_s_mem()); ++ ERR_print_errors(bio); ++ len = BIO_get_mem_data(bio, &buf); ++ if (len < 0) ++ ret = "Cannot receive OpenSSL error message."; ++ else ++ ret = g_strndup(buf, (size_t)len); ++ BIO_free(bio); ++ return ret; ++} ++ ++int pv_BIO_reset(BIO *b) ++{ ++ int rc = BIO_reset(b); ++ ++ if (rc != 1 && (BIO_method_type(b) == BIO_TYPE_FILE && rc != 0)) ++ return -1; ++ return 1; ++} ++ ++GBytes *pv_generate_rand_data(size_t size, GError **error) ++{ ++ g_autofree uint8_t *data = NULL; ++ ++ if (size > INT_MAX) { ++ g_set_error_literal(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION, ++ "Too many random data requested. Split it up"); ++ OPENSSL_clear_free(data, size); ++ return NULL; ++ } ++ ++ data = g_malloc(size); ++ if (RAND_bytes(data, (int)size) != 1) { ++ g_set_error_literal(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION, ++ "The required amount of random data is not available"); ++ return NULL; ++ } ++ ++ return pv_sec_gbytes_new_take(g_steal_pointer(&data), size); ++} ++ ++GBytes *pv_generate_key(const EVP_CIPHER *cipher, GError **error) ++{ ++ int size; ++ ++ pv_wrapped_g_assert(cipher); ++ ++ size = EVP_CIPHER_key_length(cipher); ++ if (size <= 0) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, ++ "Unknown cipher"); ++ return NULL; ++ } ++ ++ return pv_generate_rand_data((guint)size, error); ++} ++ ++GBytes *pv_generate_iv(const EVP_CIPHER *cipher, GError **error) ++{ ++ int size; ++ ++ pv_wrapped_g_assert(cipher); ++ ++ size = EVP_CIPHER_iv_length(cipher); ++ if (size <= 0) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, ++ "Unknown cipher"); ++ return NULL; ++ } ++ ++ return pv_generate_rand_data((guint)size, error); ++} ++ ++static int64_t pv_gcm_encrypt_decrypt(GBytes *input, GBytes *aad, const PvCipherParms *parms, ++ GBytes **output, GBytes **tagp, enum PvCryptoMode mode, ++ GError **error) ++{ ++ const uint8_t *in_data, *aad_data = NULL, *iv_data, *key_data; ++ size_t in_size, aad_size = 0, iv_size, key_size, out_size; ++ const EVP_CIPHER *cipher = parms->cipher; ++ const size_t tag_size = parms->tag_size; ++ gboolean encrypt = mode == PV_ENCRYPT; ++ g_autoptr(EVP_CIPHER_CTX) ctx = NULL; ++ g_autofree uint8_t *out_data = NULL; ++ g_autofree uint8_t *tag_data = NULL; ++ const GBytes *key = parms->key; ++ const GBytes *iv = parms->iv; ++ int cipher_block_size; ++ int64_t ret = -1; ++ int len = -1; ++ GBytes *tag; ++ ++ g_assert(tagp); ++ g_assert(cipher); ++ g_assert(key); ++ g_assert(iv); ++ ++ tag = *tagp; ++ in_data = g_bytes_get_data((GBytes *)input, &in_size); ++ if (aad) ++ aad_data = g_bytes_get_data((GBytes *)aad, &aad_size); ++ iv_data = g_bytes_get_data((GBytes *)iv, &iv_size); ++ key_data = g_bytes_get_data((GBytes *)key, &key_size); ++ out_size = in_size; ++ cipher_block_size = EVP_CIPHER_block_size(cipher); ++ ++ /* Checks for later casts */ ++ g_assert(aad_size <= INT_MAX); ++ g_assert(in_size <= INT_MAX); ++ g_assert(iv_size <= INT_MAX); ++ g_assert(cipher_block_size > 0); ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (!ctx) ++ g_abort(); ++ ++ if (tag_size == 0 || (tag_size % (size_t)cipher_block_size != 0)) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Passed tag size is incorrect"); ++ return -1; ++ } ++ ++ /* Has the passed key the correct size? */ ++ if (EVP_CIPHER_key_length(cipher) != (int)key_size) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Passed key has incorrect size: %ld != %d", key_size, ++ EVP_CIPHER_key_length(cipher)); ++ return -1; ++ } ++ ++ /* First, set the cipher algorithm so we can verify our key/IV lengths ++ */ ++ if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "EVP_CIPHER_CTX_new failed"); ++ return -1; ++ } ++ ++ /* Set IV length */ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)iv_size, NULL) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "EVP_CIPHER_CTX_ex failed"); ++ return -1; ++ } ++ ++ /* Initialise key and IV */ ++ if (EVP_CipherInit_ex(ctx, NULL, NULL, key_data, iv_data, encrypt) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "EVP_CipherInit_ex failed"); ++ return -1; ++ } ++ ++ /* Allocate output data */ ++ out_data = g_malloc0(out_size); ++ if (encrypt) ++ tag_data = g_malloc0(tag_size); ++ ++ if (aad_size > 0) { ++ /* Provide any AAD data */ ++ if (EVP_CipherUpdate(ctx, NULL, &len, aad_data, (int)aad_size) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "EVP_CipherUpdate failed"); ++ return -1; ++ } ++ g_assert(len == (int)aad_size); ++ } ++ ++ /* Provide data to be en/decrypted */ ++ if (EVP_CipherUpdate(ctx, out_data, &len, in_data, (int)in_size) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "EVP_CipherUpdate failed"); ++ return -1; ++ } ++ ret = len; ++ ++ if (!encrypt) { ++ const uint8_t *tmp_tag_data = NULL; ++ size_t tmp_tag_size = 0; ++ ++ if (tag) ++ tmp_tag_data = g_bytes_get_data(tag, &tmp_tag_size); ++ if (tag_size != tmp_tag_size) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Getting the GCM tag failed"); ++ return -1; ++ } ++ ++ /* Set expected tag value */ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)tmp_tag_size, ++ (uint8_t *)tmp_tag_data) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Setting the GCM tag failed"); ++ return -1; ++ } ++ } ++ ++ /* Finalize the en/decryption */ ++ if (EVP_CipherFinal_ex(ctx, (uint8_t *)out_data + len, &len) != 1) { ++ if (encrypt) ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Encrypting failed (EVP_CipherFinal_ex)"); ++ else ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_MATCH_TAG, ++ "Verifying the GCM tag failed"); ++ return -1; ++ } ++ ret += len; ++ ++ if (encrypt) { ++ /* Get the tag */ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, (int)tag_size, tag_data) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ "Getting the GCM tag failed"); ++ return -1; ++ } ++ ++ g_assert(!*tagp); ++ *tagp = g_bytes_new_take(g_steal_pointer(&tag_data), tag_size); ++ } ++ g_assert(ret == (int)out_size); ++ g_assert(out_size == in_size); ++ ++ g_assert(!*output); ++ *output = pv_sec_gbytes_new_take(g_steal_pointer(&out_data), out_size); ++ return ret; ++} ++ ++int64_t pv_gcm_encrypt(GBytes *plain, GBytes *aad, const PvCipherParms *parms, GBytes **cipher, ++ GBytes **tag, GError **error) ++{ ++ pv_wrapped_g_assert(plain); ++ pv_wrapped_g_assert(parms); ++ pv_wrapped_g_assert(cipher); ++ pv_wrapped_g_assert(tag); ++ ++ return pv_gcm_encrypt_decrypt(plain, aad, parms, cipher, tag, PV_ENCRYPT, error); ++} ++ ++int64_t pv_gcm_decrypt(GBytes *cipher, GBytes *aad, GBytes *tag, const PvCipherParms *parms, ++ GBytes **plain, GError **error) ++{ ++ pv_wrapped_g_assert(cipher); ++ pv_wrapped_g_assert(tag); ++ pv_wrapped_g_assert(parms); ++ pv_wrapped_g_assert(plain); ++ ++ return pv_gcm_encrypt_decrypt(cipher, aad, parms, plain, &tag, PV_DECRYPT, error); ++} ++ ++GBytes *pv_hkdf_extract_and_expand(size_t derived_key_len, GBytes *key, GBytes *salt, GBytes *info, ++ const EVP_MD *md, G_GNUC_UNUSED GError **error) ++{ ++ const unsigned char *salt_data, *key_data, *info_data; ++ g_autoptr(EVP_PKEY_CTX) ctx = NULL; ++ size_t salt_len, key_len, info_len; ++ g_autofree unsigned char *derived_key = NULL; ++ ++ g_assert(derived_key_len > 0); ++ pv_wrapped_g_assert(key); ++ pv_wrapped_g_assert(salt); ++ pv_wrapped_g_assert(info); ++ pv_wrapped_g_assert(md); ++ ++ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); ++ if (!ctx) ++ g_abort(); ++ ++ if (EVP_PKEY_derive_init(ctx) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_set_hkdf_md(ctx, md) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ salt_data = g_bytes_get_data(salt, &salt_len); ++ if (salt_len > INT_MAX) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt_data, (int)salt_len) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ key_data = g_bytes_get_data(key, &key_len); ++ if (key_len > INT_MAX) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_key(ctx, key_data, (int)key_len) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ info_data = g_bytes_get_data(info, &info_len); ++ if (info_len > INT_MAX) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_add1_hkdf_info(ctx, (unsigned char *)info_data, (int)info_len) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ return NULL; ++ } ++ ++ derived_key = g_malloc0(derived_key_len); ++ if (EVP_PKEY_derive(ctx, derived_key, &derived_key_len) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_HKDF_FAIL, ++ "FAILED to derive key via HKDF"); ++ printf("%s\n", pv_get_openssl_error()); ++ return NULL; ++ } ++ ++ return pv_sec_gbytes_new_take(g_steal_pointer(&derived_key), derived_key_len); ++} ++ ++EVP_PKEY *pv_generate_ec_key(int nid, GError **error) ++{ ++ g_autoptr(EVP_PKEY_CTX) ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); ++ g_autoptr(EVP_PKEY) ret = NULL; ++ ++ g_assert(ctx); ++ ++ if (EVP_PKEY_keygen_init(ctx) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, ++ _("EC key could not be auto-generated")); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, ++ _("EC key could not be auto-generated")); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_keygen(ctx, &ret) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, ++ _("EC key could not be auto-generated")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++/* Convert a EVP_PKEY to the key format used in the PV header */ ++PvEcdhPubKey *pv_evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **error) ++{ ++ g_autofree PvEcdhPubKey *ret = g_new0(PvEcdhPubKey, 1); ++ g_autoptr(BIGNUM) pub_x_big = NULL, pub_y_big = NULL; ++ g_autoptr(EC_KEY) ec_key = NULL; ++ const EC_POINT *pub_key; ++ const EC_GROUP *grp; ++ ++ pv_wrapped_g_assert(key); ++ ++ ec_key = EVP_PKEY_get1_EC_KEY(key); ++ if (!ec_key) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Key has the wrong type")); ++ return NULL; ++ } ++ ++ pub_key = EC_KEY_get0_public_key(ec_key); ++ if (!pub_key) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Failed to get public key")); ++ return NULL; ++ } ++ ++ grp = EC_KEY_get0_group(ec_key); ++ if (!grp) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Failed to get EC group")); ++ return NULL; ++ } ++ ++ pub_x_big = BN_new(); ++ if (!pub_x_big) ++ g_abort(); ++ ++ pub_y_big = BN_new(); ++ if (!pub_y_big) ++ g_abort(); ++ ++ if (EC_POINT_get_affine_coordinates_GFp(grp, pub_key, pub_x_big, pub_y_big, NULL) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Cannot convert key to internal format")); ++ return NULL; ++ } ++ ++ if (BN_bn2binpad(pub_x_big, ret->x, sizeof(ret->x)) < 0) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Cannot convert key to internal format")); ++ return NULL; ++ } ++ ++ if (BN_bn2binpad(pub_y_big, ret->y, sizeof(ret->y)) < 0) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Cannot convert key to internal format")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ret); ++} ++ ++static GBytes *derive_key(EVP_PKEY *key1, EVP_PKEY *key2, GError **error) ++{ ++ g_autoptr(EVP_PKEY_CTX) ctx = NULL; ++ uint8_t *data = NULL; ++ size_t data_size, key_size; ++ ++ ctx = EVP_PKEY_CTX_new(key1, NULL); ++ if (!ctx) ++ g_abort(); ++ ++ if (EVP_PKEY_derive_init(ctx) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Key derivation failed")); ++ return NULL; ++ } ++ ++ if (EVP_PKEY_derive_set_peer(ctx, key2) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, ++ _("Key derivation failed")); ++ return NULL; ++ } ++ ++ /* Determine buffer length */ ++ if (EVP_PKEY_derive(ctx, NULL, &key_size) != 1) { ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, ++ _("Key derivation failed")); ++ return NULL; ++ } ++ ++ data_size = key_size; ++ data = OPENSSL_malloc(data_size); ++ if (!data) ++ g_abort(); ++ if (EVP_PKEY_derive(ctx, data, &data_size) != 1) { ++ OPENSSL_clear_free(data, data_size); ++ g_set_error(error, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, ++ _("Key derivation failed")); ++ return NULL; ++ } ++ ++ g_assert(data_size == key_size); ++ return pv_sec_gbytes_new_take(g_steal_pointer(&data), data_size); ++} ++ ++GBytes *pv_derive_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **error) ++{ ++ const guint8 append[] = { 0x00, 0x00, 0x00, 0x01 }; ++ g_autoptr(GBytes) derived_key = NULL, ret = NULL; ++ g_autoptr(GByteArray) der_key_ga = NULL; ++ g_autofree uint8_t *raw = NULL; ++ size_t raw_len; ++ ++ pv_wrapped_g_assert(cust); ++ pv_wrapped_g_assert(host); ++ ++ derived_key = derive_key(cust, host, error); ++ if (!derived_key) ++ return NULL; ++ ++ der_key_ga = g_bytes_unref_to_array(g_steal_pointer(&derived_key)); ++ /* ANSI X.9.63-2011: 66 bytes x with leading 7 bits and ++ * concatenate 32 bit int '1' ++ */ ++ der_key_ga = g_byte_array_append(der_key_ga, append, sizeof(append)); ++ /* free GBytesArray and get underlying data */ ++ raw_len = der_key_ga->len; ++ raw = g_byte_array_free(g_steal_pointer(&der_key_ga), FALSE); ++ ++ ret = pv_sha256_hash(raw, raw_len, error); ++ OPENSSL_cleanse(raw, raw_len); ++ return g_steal_pointer(&ret); ++} ++ ++GQuark pv_crypto_error_quark(void) ++{ ++ return g_quark_from_static_string("pv-crypto-error-quark"); ++} +diff --git a/libpv/curl.c b/libpv/curl.c +new file mode 100644 +index 00000000..898f9a8a +--- /dev/null ++++ b/libpv/curl.c +@@ -0,0 +1,116 @@ ++/* ++ * Libcurl utils ++ * ++ * 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. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "lib/zt_common.h" ++#include "libpv/curl.h" ++ ++struct UserData { ++ GByteArray *buffer; ++ uint max_size; ++}; ++ ++static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) ++{ ++ g_assert(userdata); ++ struct UserData *data = (struct UserData *)userdata; ++ GByteArray *buffer = data->buffer; ++ uint64_t actual_size; ++ size_t err; ++ ++ g_assert(buffer); ++ ++ if (!g_uint64_checked_mul(&actual_size, size, nmemb)) ++ g_abort(); ++ ++ /* Signal an error condition by returning a amount that differs ++ * from the amount passed to the callback. This results in a ++ * CURLE_WRITE_ERROR. ++ */ ++ err = actual_size + 1; ++ ++ if (actual_size > G_MAXUINT) ++ return err; ++ ++ data->buffer = g_byte_array_append(buffer, (uint8_t *)ptr, (uint)actual_size); ++ if (data->buffer->len > data->max_size) ++ return err; ++ ++ return actual_size; ++} ++ ++int pv_curl_init(void) ++{ ++ if (curl_global_init(CURL_GLOBAL_ALL) != 0) ++ return -1; ++ return 0; ++} ++ ++void pv_curl_cleanup(void) ++{ ++ curl_global_cleanup(); ++} ++ ++GByteArray *curl_download(const char *url, long timeout_ms, uint max_size, GError **err) ++{ ++ g_autoptr(GByteArray) ret = NULL; ++ g_autoptr(CURL) handle = NULL; ++ g_autofree char *agent = NULL; ++ struct UserData userdata; ++ CURLcode rc; ++ ++ /* set up curl session */ ++ handle = curl_easy_init(); ++ if (!handle) ++ g_abort(); ++ ++ /* follow redirection */ ++ rc = curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ rc = curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ rc = curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ agent = g_strdup_printf("%s/%s", GETTEXT_PACKAGE, RELEASE_STRING); ++ rc = curl_easy_setopt(handle, CURLOPT_USERAGENT, agent); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ rc = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ ret = g_byte_array_new(); ++ userdata.buffer = ret; ++ userdata.max_size = max_size; ++ rc = curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *)&userdata); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ rc = curl_easy_setopt(handle, CURLOPT_URL, url); ++ if (rc != CURLE_OK) ++ goto curl_err; ++ ++ rc = curl_easy_perform(handle); ++ if (rc != CURLE_OK) { ++ g_set_error(err, PV_CURL_ERROR, PV_CURL_ERROR_DOWNLOAD_FAILED, ++ _("download failed: %s"), curl_easy_strerror(rc)); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ret); ++curl_err: ++ g_set_error(err, PV_CURL_ERROR, PV_CURL_ERROR_CURL_INIT_FAILED, ++ _("cURL initialization failed: %s"), curl_easy_strerror(rc)); ++ return NULL; ++} +diff --git a/libpv/glib-helper.c b/libpv/glib-helper.c +new file mode 100644 +index 00000000..fe89dd75 +--- /dev/null ++++ b/libpv/glib-helper.c +@@ -0,0 +1,179 @@ ++/* ++ * Glib convenience functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "libpv/glib-helper.h" ++ ++struct __data { ++ void *data; ++ size_t size; ++ GFreeFunc free_func; ++}; ++ ++static void __data_clear_and_free(void *p) ++{ ++ struct __data *ptr = p; ++ ++ if (!ptr) ++ return; ++ ++ if (ptr->data) { ++ OPENSSL_cleanse(ptr->data, ptr->size); ++ ptr->free_func(ptr->data); ++ } ++ g_free(ptr); ++} ++ ++static GBytes *pv_sec_gbytes_new_take_func(void *data, size_t size, GFreeFunc free_func) ++{ ++ struct __data *tmp = g_new(struct __data, 1); ++ ++ tmp->data = data; ++ tmp->size = size; ++ tmp->free_func = free_func; ++ ++ return g_bytes_new_with_free_func(data, size, __data_clear_and_free, tmp); ++} ++ ++GBytes *pv_sec_gbytes_new_take(void *data, size_t size) ++{ ++ return pv_sec_gbytes_new_take_func(data, size, g_free); ++} ++ ++GBytes *pv_sec_gbytes_new(const void *data, size_t size) ++{ ++ g_autofree void *tmp_data = NULL; ++ ++ g_return_val_if_fail(data || size != 0, NULL); ++ ++ tmp_data = g_malloc(size); ++ memcpy(tmp_data, data, size); ++ return pv_sec_gbytes_new_take(g_steal_pointer(&tmp_data), size); ++} ++ ++int pv_file_seek(FILE *file, long offset, int whence, GError **error) ++{ ++ int cached_errno; ++ int ret = fseek(file, offset, whence); ++ ++ if (ret) { ++ cached_errno = errno; ++ g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR, ++ "Cannot seek: %s", g_strerror(cached_errno)); ++ } ++ return ret; ++} ++ ++size_t pv_file_write(FILE *file, const void *ptr, size_t size, GError **error) ++{ ++ int cached_errno; ++ size_t n = fwrite(ptr, 1, size, file); ++ ++ if (n != size) { ++ cached_errno = errno; ++ g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR, ++ "Cannot write: %s", g_strerror(cached_errno)); ++ } ++ return n; ++} ++ ++long pv_file_close(FILE *file, GError **error) ++{ ++ int cached_errno; ++ int ret = fclose(file); ++ ++ if (ret) { ++ cached_errno = errno; ++ g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR, ++ "Cannot close: %s", g_strerror(cached_errno)); ++ } ++ return ret; ++} ++ ++void pv_auto_close_file(FILE *file) ++{ ++ if (!file) ++ return; ++ ++ (void)pv_file_close(file, NULL); ++} ++ ++long pv_file_tell(FILE *file, GError **error) ++{ ++ int cached_errno; ++ long n = ftell(file); ++ ++ if (n < 0) { ++ cached_errno = errno; ++ g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR, ++ "Cannot tell: %s", g_strerror(cached_errno)); ++ } ++ return n; ++} ++ ++FILE *pv_file_open(const char *filename, const char *mode, GError **error) ++{ ++ FILE *file = fopen(filename, mode); ++ int cached_errno; ++ ++ if (!file) { ++ cached_errno = errno; ++ g_set_error(error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR, ++ "Cannot open '%s'. %s", filename, g_strerror(cached_errno)); ++ return NULL; ++ } ++ return file; ++} ++ ++GBytes *pv_file_get_content_as_g_bytes(const char *filename, GError **error) ++{ ++ g_autofree char *data = NULL; ++ size_t data_size; ++ ++ if (!g_file_get_contents(filename, &data, &data_size, error)) ++ return NULL; ++ ++ return g_bytes_new_take(g_steal_pointer(&data), data_size); ++} ++ ++GBytes *pv_file_get_content_as_secure_bytes(const char *filename) ++{ ++ g_autoptr(FILE) f = fopen(filename, "rb"); ++ g_autofree char *data = NULL; ++ ssize_t file_size; ++ size_t data_size; ++ ++ if (!f) ++ return NULL; ++ ++ fseek(f, 0, SEEK_END); ++ file_size = ftell(f); ++ if (file_size < 0) ++ return NULL; ++ data_size = (size_t)file_size; ++ fseek(f, 0, SEEK_SET); ++ data = g_malloc0(data_size); ++ if (data_size != fread(data, 1, data_size, f)) ++ return NULL; ++ return pv_sec_gbytes_new_take(g_steal_pointer(&data), data_size); ++} ++ ++void *pv_gbytes_memcpy(void *dst, size_t dst_size, GBytes *src) ++{ ++ size_t src_size; ++ const void *src_data = g_bytes_get_data(src, &src_size); ++ ++ if (dst_size < src_size) ++ return NULL; ++ return memcpy(dst, src_data, src_size); ++} +diff --git a/libpv/hash.c b/libpv/hash.c +new file mode 100644 +index 00000000..5d58147d +--- /dev/null ++++ b/libpv/hash.c +@@ -0,0 +1,147 @@ ++/* ++ * Hashing functions. ++ ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/crypto.h" ++#include "libpv/hash.h" ++ ++GBytes *pv_sha256_hash(uint8_t *buf, size_t size, GError **error) ++{ ++ g_autoptr(EVP_MD_CTX) ctx = NULL; ++ ++ ctx = pv_digest_ctx_new(EVP_sha256(), error); ++ if (!ctx) ++ return NULL; ++ ++ if (pv_digest_ctx_update_raw(ctx, buf, size, error) != 0) ++ return NULL; ++ ++ return pv_digest_ctx_finalize(ctx, error); ++} ++ ++EVP_MD_CTX *pv_digest_ctx_new(const EVP_MD *md, GError **error) ++{ ++ g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new(); ++ ++ if (!ctx) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ _("Hash context generation failed")); ++ return NULL; ++ } ++ ++ if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ _("EVP_DigestInit_ex failed")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&ctx); ++} ++ ++int pv_digest_ctx_update_raw(EVP_MD_CTX *ctx, const uint8_t *buf, size_t size, GError **error) ++{ ++ if (!buf || size == 0) ++ return 0; ++ ++ if (EVP_DigestUpdate(ctx, buf, size) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ _("EVP_DigestUpdate failed")); ++ return -1; ++ } ++ return 0; ++} ++ ++int pv_digest_ctx_update(EVP_MD_CTX *ctx, GBytes *data, GError **error) ++{ ++ const uint8_t *buf; ++ size_t buf_size; ++ ++ if (!data) ++ return 0; ++ buf = g_bytes_get_data((GBytes *)data, &buf_size); ++ return pv_digest_ctx_update_raw(ctx, buf, buf_size, error); ++} ++ ++GBytes *pv_digest_ctx_finalize(EVP_MD_CTX *ctx, GError **error) ++{ ++ int md_size = EVP_MD_size(EVP_MD_CTX_md(ctx)); ++ g_autofree uint8_t *digest = NULL; ++ unsigned int digest_size; ++ ++ g_assert(md_size > 0); ++ ++ digest = g_malloc0((uint)md_size); ++ if (EVP_DigestFinal_ex(ctx, digest, &digest_size) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ _("EVP_DigestFinal_ex failed")); ++ return NULL; ++ } ++ ++ g_assert(digest_size == (uint)md_size); ++ return g_bytes_new_take(g_steal_pointer(&digest), digest_size); ++} ++ ++HMAC_CTX *pv_hmac_ctx_new(GBytes *key, const EVP_MD *md, GError **error) ++{ ++ g_autoptr(HMAC_CTX) ctx = HMAC_CTX_new(); ++ const uint8_t *key_data; ++ size_t key_size; ++ ++ key_data = g_bytes_get_data(key, &key_size); ++ ++ if (HMAC_Init_ex(ctx, key_data, (int)key_size, md, NULL) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ "unable to create HMAC context: %s", pv_get_openssl_error()); ++ return NULL; ++ } ++ return g_steal_pointer(&ctx); ++} ++ ++int pv_hmac_ctx_update_raw(HMAC_CTX *ctx, const void *buf, size_t size, GError **error) ++{ ++ if (!buf || size == 0) ++ return 0; ++ ++ if (HMAC_Update(ctx, buf, size) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ "unable to add data to HMAC context: %s", pv_get_openssl_error()); ++ return -1; ++ } ++ return 0; ++} ++ ++int pv_hmac_ctx_update(HMAC_CTX *ctx, GBytes *data, GError **error) ++{ ++ const uint8_t *buf; ++ size_t buf_size; ++ ++ if (!data) ++ return 0; ++ buf = g_bytes_get_data((GBytes *)data, &buf_size); ++ return pv_hmac_ctx_update_raw(ctx, buf, buf_size, error); ++} ++ ++GBytes *pv_hamc_ctx_finalize(HMAC_CTX *ctx, GError **error) ++{ ++ int md_size = EVP_MD_size(HMAC_CTX_get_md(ctx)); ++ g_autofree uint8_t *hmac = NULL; ++ unsigned int hmac_size = 0; ++ ++ g_assert(md_size > 0); ++ ++ hmac = g_malloc0((unsigned int)md_size); ++ ++ if (HMAC_Final(ctx, hmac, &hmac_size) != 1) { ++ g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL, ++ "unable to calculate HMAC: %s", pv_get_openssl_error()); ++ return NULL; ++ } ++ return g_bytes_new_take(g_steal_pointer(&hmac), hmac_size); ++} +-- +2.25.1 + diff -Nru s390-tools-2.20.0/debian/patches/3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch s390-tools-2.20.0/debian/patches/3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch --- s390-tools-2.20.0/debian/patches/3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch 1970-01-01 01:00:00.000000000 +0100 +++ s390-tools-2.20.0/debian/patches/3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch 2022-08-30 08:40:00.000000000 +0200 @@ -0,0 +1,4068 @@ +From 3ab06d77fb1b405411c3f257188962535950fab1 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 1 Jun 2022 08:00:19 +0000 +Subject: [PATCH] pvattest: Create, perform, and verify attestation + measurements +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +pvattest is a tool to attest an IBM Secure Execution guest. + +In a trusted environment, one can create a request using +`pvattest create`. To get a measurement of an untrusted +IBM Secure Execution guest call 'pvattest perform'. +Again in a trusted environment, call 'pvattest verify' +to verify that the measurement is the expected one. + +The tool runs on s390 and x86. +It has the same requirements like libpv and therefore +requires openssl v1.1.1+, glib2.56+, and libcurl. +Additionally, to measure, the linux kernel must provide +the Ultravisor userspace interface `uvdevice` at /dev/uv +and must be executed on an IBM Secure Execution guest on +hardware with Ultravisor attestation support, like IBM z16 or later. + +Signed-off-by: Steffen Eiden +Reviewed-by: Marc Hartmayer +Signed-off-by: Jan Höppner + +[Frank Heimes: Adjusted hunks for Makefile + due to slightly different context.] + +Origin: backport, https://github.com/ibm-s390-linux/s390-tools/commit/3ab06d77fb1b405411c3f257188962535950fab1 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1959987 +Last-Update: 2022-08-30 + +--- + .gitignore | 2 + + Makefile | 2 +- + README.md | 20 +- + pvattest/Makefile | 22 ++ + pvattest/README.md | 100 +++++ + pvattest/man/Makefile | 9 + + pvattest/man/pvattest-create.1 | 73 ++++ + pvattest/man/pvattest-perform.1 | 47 +++ + pvattest/man/pvattest-verify.1 | 60 +++ + pvattest/man/pvattest.1 | 104 +++++ + pvattest/src/.gitignore | 2 + + pvattest/src/Makefile | 124 ++++++ + pvattest/src/arcb.c | 423 +++++++++++++++++++++ + pvattest/src/arcb.h | 152 ++++++++ + pvattest/src/argparse.c | 649 ++++++++++++++++++++++++++++++++ + pvattest/src/argparse.h | 106 ++++++ + pvattest/src/attestation.c | 148 ++++++++ + pvattest/src/attestation.h | 99 +++++ + pvattest/src/common.c | 47 +++ + pvattest/src/common.h | 37 ++ + pvattest/src/config.h | 31 ++ + pvattest/src/exchange_format.c | 480 +++++++++++++++++++++++ + pvattest/src/exchange_format.h | 166 ++++++++ + pvattest/src/log.c | 181 +++++++++ + pvattest/src/log.h | 67 ++++ + pvattest/src/pvattest.c | 392 +++++++++++++++++++ + pvattest/src/types.h | 18 + + pvattest/src/uvio.c | 177 +++++++++ + pvattest/src/uvio.h | 110 ++++++ + 29 files changed, 3844 insertions(+), 4 deletions(-) + create mode 100644 pvattest/Makefile + create mode 100644 pvattest/README.md + create mode 100644 pvattest/man/Makefile + create mode 100644 pvattest/man/pvattest-create.1 + create mode 100644 pvattest/man/pvattest-perform.1 + create mode 100644 pvattest/man/pvattest-verify.1 + create mode 100644 pvattest/man/pvattest.1 + create mode 100644 pvattest/src/.gitignore + create mode 100644 pvattest/src/Makefile + create mode 100644 pvattest/src/arcb.c + create mode 100644 pvattest/src/arcb.h + create mode 100644 pvattest/src/argparse.c + create mode 100644 pvattest/src/argparse.h + create mode 100644 pvattest/src/attestation.c + create mode 100644 pvattest/src/attestation.h + create mode 100644 pvattest/src/common.c + create mode 100644 pvattest/src/common.h + create mode 100644 pvattest/src/config.h + create mode 100644 pvattest/src/exchange_format.c + create mode 100644 pvattest/src/exchange_format.h + create mode 100644 pvattest/src/log.c + create mode 100644 pvattest/src/log.h + create mode 100644 pvattest/src/pvattest.c + create mode 100644 pvattest/src/types.h + create mode 100644 pvattest/src/uvio.c + create mode 100644 pvattest/src/uvio.h + +--- a/.gitignore ++++ b/.gitignore +@@ -64,6 +64,7 @@ + mon_tools/mon_fsstatd + mon_tools/mon_procd + osasnmpd/osasnmpd ++pvattest/src/pvattest + qetharp/qetharp + qethqoat/qethqoat + systemd/cpacfstatsd.service +@@ -117,3 +118,4 @@ + zkey/zkey + zkey/zkey-cryptsetup + zpcictl/zpcictl ++**/.detect-openssl.dep.c +--- a/Makefile ++++ b/Makefile +@@ -16,7 +16,7 @@ + vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ + ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \ + systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \ +- genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ++ genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath pvattest + + SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS) + +--- a/README.md ++++ b/README.md +@@ -33,6 +33,9 @@ + * genprotimg: + Create a protected virtualization image. + ++ * pvattest: ++ Create, perform, and verify protected virtualization attestation measurements. ++ + * udev rules: + - 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/. + - 57-osasnmpd.rules: udev rules for osasnmpd. +@@ -300,12 +303,13 @@ + | net-snmp | `HAVE_SNMP` | osasnmpd | + | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump | + | openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, | +-| | | libkmipclient | ++| | | libkmipclient, pvattest | + | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | + | json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, | + | | | libkmipclient | +-| glib2 | `HAVE_GLIB2` | genprotimg | +-| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient | ++| glib2 | `HAVE_GLIB2` | genprotimg, pvattest | ++| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,| ++| | | pvattest | + | libxml2 | `HAVE_LIBXML2` | libkmipclient | + | systemd | `HAVE_SYSTEMD` | hsavmcore | + +@@ -337,6 +341,16 @@ + + The runtime requirements are: openssl-libs (>= 1.1.0) and glib2. + ++* pvattest: ++ For building pvattest you need OpenSSL version 1.1.1 or newer ++ installed (openssl-devel.rpm). Also required is glib2.56 or newer ++ (glib2-devel.rpm) and libcurl. ++ Tip: you may skip the pvattest build by adding ++ `HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_GLIB2=0`. ++ ++ The runtime requirements are: openssl-libs (>= 1.1.1) and ++ glib2.56 or newer. ++ + * osasnmpd: + You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm) + installed, before building the osasnmpd subagent. +--- /dev/null ++++ b/pvattest/Makefile +@@ -0,0 +1,22 @@ ++# Common definitions ++include ../common.mak ++ ++.DEFAULT_GOAL := all ++ ++PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/pvattest" ++SUBDIRS := src man ++RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive ++ ++all: all-recursive ++ ++install: all install-recursive ++ ++clean: clean-recursive ++ ++$(RECURSIVE_TARGETS): ++ @target=`echo $@ |sed s/-recursive//`; \ ++ for d in $(SUBDIRS); do \ ++ $(MAKE) -C $$d $$target || exit 1; \ ++ done ++ ++.PHONY: all install clean $(RECURSIVE_TARGETS) +--- /dev/null ++++ b/pvattest/README.md +@@ -0,0 +1,100 @@ ++# pvattest ++ ++Use `pvattest` to attest an IBM Secure Execution guest running on z16 and later. ++ ++With `pvattest` you can create attestation requests in a trusted environment and attest ++an IBM Secure Execution for Linux guest to verify that a provider is running the correct image. ++To achieve this, use the following commands: ++ * `create` On a trusted system, creates an attestation request. ++ * `perform` Performs an attestation measurement on the SE-guest to be attested. For this a ++ attestation request is sent to the Ultravisor (UV) and the answer received. The `perform` ++ command requires IBM z16 or later z/Architecture hardware. ++ * `verify` On a trusted system, compares the answer from the Ultravisor to the ++ expected answer. If they differ, the Secure Execution guest might be a different guest ++ than expected, or not secure at all. ++ ++For meaningful results, run `create` and `verify` only in a trusted environment, ++like your workstation or a previously attested IBM Secure Execution guest. ++Otherwise, the attestation can be compromised. ++For all certificates, revocation lists, and host-key documents, both the PEM and DER input ++formats are supported. If you run this program on a non S390 System, 'perform' is not be available. ++ ++## Getting started ++ ++If all dependencies are met (see the s390-tools README) issue `make` in the source tree to build `pvattest`. ++ ++## Details ++### create ++`pvattest create` needs the host-key-document, a location to store the ++attestation request protection key, and a location to store the request data. ++Unless the `--no-verify` flag is set it additionally requires the IBM signing key ++and the intermediate CA. The output contains the request in binary form which serves as input ++to `pvattest perform`. Must be run in a trusted environment. Especially, do not create the request ++on a system you want to attest. The attestation request protection key is valid for this request only, ++must be kept until the verification is completed and must be destroyed afterwards ++Keep the key secret. ++ ++### perform ++`pvattest perform` needs a request in binary form generated by `pvattest create`and ++a location to store the output. It will send the request to the device at `/dev/uv` ++which passes the request to the Ultravisor. ++Kernel will then send the request to the Ultravisor which will calculate the answer. ++The Answer is then passed back to userspace and handled by `pvattest` ++The output includes the original request and the answer from the Ultravisor. ++ ++### verify ++`pvattest verify` needs the SE-guest header, the attestation request protection key, ++and the attestation request and the response to the `pvattest perform` command from the Ultravisor. ++It calculates the measurement in the trusted environment and compares it to the response from ++the Ultravisor in the previous step. ++The following return codes are possible: ++ ++0. successful verification: The calculated measurement matches the response from the Ultravisor ++ ++1. failed verification: The command ended with an error, for example, because of incorrect input or an invalid SE header ++ ++2. failed verification: The calculated measurement does not match the response from the Ultravisor ++ ++Run `pvattest verify` in a trusted environment. Especially, do not verify on the system you want to attest. ++ ++## Measurement ++The measurement is a cryptographic measurement of the following block. ++Only HMAC-SHA512 is supported. ++ ++| Start | Size | Content | ++|---------|------------|---------------------------------------------------------------| ++| 0x0 | 0x40 | Page List Digest (from SE header) | ++| 0x40 | 0x40 | Address List Digest (from SE header) | ++| 0x80 | 0x40 | Tweak List Digest (from SE header) | ++| 0xc0 | 0x10 | SE Header Tag (from SE header) | ++| 0xd0 | 0x10 | Configuration UID (generated by UV, included in the answer) | ++| 0xe0 | 0x02 | User Data Length (defined during measurement on the SE-guest) | ++| 0xe2 | 0x02 | Zeros | ++| 0xe4 | 0x04 | Additional Data Length (set by UV, included in the answer) | ++| 0xe8 | 0 - 0x100 | User Data (generated during measurement on the SE-guest) | ++| ... | 0 or 0x10 | Optional Nonce (generated during request creation) | ++| ... | 0 - 0x8000 | Additional Data (generated by UV, included in the answer) | ++ ++### User Data ++By default `pvattest` does not include any User Data, therefore the length is zero. ++`User Data` is data generated by the SE guest and passed to UV during the measurement. ++The `User Data` must be known to or be replicable by the verifier to verify the correctness of the User Data. ++The addition of user data is currently an experimental setting. ++ ++### Additional Data ++`Additional data` is data known to the Ultravisor. By default UV will not include any `Additional Data`. ++Adding `Additional Data` is currently an experimental setting. ++ ++## Example ++ ++Create an attestation request in a trusted environment: ++ ++`pvattest create -k hkd.crt --arpk arp.key -o arcb.bin --cert IntermediateCA.crt --cert IbmSigningKey.crt` ++ ++Perform an attestation measurement on an IBM Secure Execution guest: ++ ++`pvattest perform --input arcb.bin --output measurement.bin` ++ ++Verify the response from the Ultravisor against the attestation request in a trusted environment: ++ ++`pvattest verify --input measurement.bin --arpk arp.key --hdr se_guest.hdr` +--- /dev/null ++++ b/pvattest/man/Makefile +@@ -0,0 +1,9 @@ ++include ../../common.mak ++ ++all: ++ ++install: ++ $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1 ++ $(INSTALL) -m 644 -c *.1 -t $(DESTDIR)$(MANDIR)/man1 ++ ++.PHONY: all install clean +--- /dev/null ++++ b/pvattest/man/pvattest-create.1 +@@ -0,0 +1,73 @@ ++.\" Copyright 2022 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++.TH pvattest-create 1 "07 June 2022" "s390-tools" "Attestation Manual" ++.nh ++.ad l ++.SH NAME ++\fBpvattest [OPTION?] create [OPTIONS] \fP- create an attestation measurement request ++\fB ++.SH DESCRIPTION ++Prepare attestation measurement requests for an IBM Secure Execution guest. Only prepare attestation requests in a trusted environment, such as your workstation. The 'pvattest create' command creates a randomly generated key to protect the attestation request. This key is only valid for this specific request.In order to avoid compromising the attestation, do not publish the protection key and delete it after verification. Every 'create' command generates a new, random protection key. ++.SH OPTIONS ++.TP ++.B ++\fB-h\fP, \fB--help\fP ++Show help options ++.TP ++.B ++\fB-k\fP, \fB--host-key-document\fP=\fBFILE\fP ++Specify one or more host key documents. ++.TP ++.B ++\fB-C\fP, \fB--cert\fP=\fBFILE\fP ++Specifies the certificate that is used to establish a chain of trust for the verification of the host-key documents. Specify this option twice to specify the IBM Z signing key and the intermediate CA certificate (signed by the root CA). Required. Ignored when \fB--no-verify\fP is specified. ++.TP ++.B ++\fB--crl\fP=\fBFILE\fP ++Specify \fBFILE\fP to be a certificate revocation list (optional). ++.TP ++.B ++\fB--root-ca\fP=\fBFILE\fP ++Use \fBFILE\fP as the trusted root CA instead the root CAs that are installed on the system (optional). ++.TP ++.B ++\fB-o\fP, \fB--output\fP=\fBFILE\fP ++\fBFILE\fP specifies the output for the attestation request control block. ++.TP ++.B ++\fB-a\fP, \fB--arpk\fP=\fBFILE\fP ++Save the protection key as GCM-AES256 key in \fBFILE\fP Do not publish this key, otherwise your attestation is compromised. ++.TP ++.B ++\fB--no-verify\fP ++Disable the host-key-document verification. Does not require the host-key documents to be valid. For testing purposes, do not use for a production image. (Optional) ++.TP ++.B ++\fB--offline\fP ++Don't download CRLs (optional). ++.TP ++.B ++\fB-V\fP, \fB--verbose\fP ++Provide more detailed output (optional) ++.SH EXAMPLE ++Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', and verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt'. ++.PP ++.nf ++.fam C ++ pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt ++ ++.fam T ++.fi ++Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt', and instead of downloading the certificate revocation list use certificate revocation lists 'DigiCertCA.crl', 'IbmSigningKey.crl', and 'rootCA.crl'. ++.PP ++.nf ++.fam C ++ pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt --offline --crl DigiCertCA.crl --crl IbmSigningKey.crl --crl rootCA.crl ++ ++ ++.fam T ++.fi ++.SH SEE ALSO ++\fBpvattest\fP(1), \fBpvattest-verify\fP(1), \fBpvattest-perform\fP(1) +--- /dev/null ++++ b/pvattest/man/pvattest-perform.1 +@@ -0,0 +1,47 @@ ++.\" Copyright 2022 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++.TH pvattest-perform 1 "07 June 2022" "s390-tools" "Attestation Manual" ++.nh ++.ad l ++.SH NAME ++\fBpvattest [OPTION?] perform [OPTIONS] \fP- execute an attestation measurement request ++\fB ++.SH DESCRIPTION ++Run a measurement of this system using '/dev/uv'. Works only if this device is available and the attestation Ultravisor facility is present. The input must be an attestation request created with 'pvattest create'. Output will contain the original request, the attestation measurement result, the configuration UID, and if requested in the request Additional Data. ++.RE ++.PP ++ ++.SH OPTIONS ++.TP ++.B ++\fB-h\fP, \fB--help\fP ++Show help options ++.TP ++.B ++\fB-i\fP, \fB--input\fP=\fBFILE\fP ++\fBFILE\fP specifies the attestation request as input. ++.TP ++.B ++\fB-o\fP, \fB--output\fP=\fBFILE\fP ++\fBFILE\fP specifies the output for the attestation result. ++.TP ++.B ++\fB-V\fP, \fB--verbose\fP ++Provide more detailed output (optional) ++.RE ++.PP ++ ++.SH EXAMPLE ++Perform an attestation measurement with the attestation request 'arcb.bin' and write the output to 'measurement.bin'. ++.PP ++.nf ++.fam C ++ pvattest perform --input attreq.bin --output attresp.bin ++ ++ ++.fam T ++.fi ++.SH SEE ALSO ++\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-verify\fP(1) +--- /dev/null ++++ b/pvattest/man/pvattest-verify.1 +@@ -0,0 +1,60 @@ ++.\" Copyright 2022 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++.TH pvattest-verify 1 "07 June 2022" "s390-tools" "Attestation Manual" ++.nh ++.ad l ++.SH NAME ++\fBpvattest [OPTION?] verify [OPTIONS] \fP- verify an attestation measurement ++\fB ++.SH DESCRIPTION ++Verify that a previously generated attestation measurement of an IBM Secure Execution guest is as expected. Only verify attestation requests in a trusted environment, such as your workstation. Input must contain the response as produced by 'pvattest perform'. The protection key must be the one that was used to create the request by 'pvattest create'. Please delete it after verification. The header must be the IBM Secure Execution header of the image that was attested during 'pvattest perform' ++.RE ++.PP ++ ++.SH OPTIONS ++.TP ++.B ++\fB-h\fP, \fB--help\fP ++Show help options ++.TP ++.B ++\fB-i\fP, \fB--input\fP=\fBFILE\fP ++\fBFILE\fP specifies the attestation result as input. ++.TP ++.B ++\fB--hdr\fP=\fBFILE\fP ++Specify the header of the guest image. Exactly one is required. ++.TP ++.B ++\fB-a\fP, \fB--arpk\fP=\fBFILE\fP ++Use \fBFILE\fP to specify the GCM-AES256 key to decrypt the attestation request. Delete this key after verification. ++.TP ++.B ++\fB-V\fP, \fB--verbose\fP ++Provide more detailed output (optional) ++.RE ++.PP ++ ++.SH EXAMPLE ++To verify a measurement in 'measurement.bin' with the protection key 'arp.kep' and SE-guest header 'se_guest.hdr'. ++.PP ++.nf ++.fam C ++ pvattest verify --input attresp.bin --arpk arp.key --hdr se_guest.hdr ++ ++.fam T ++.fi ++If the verification was successful the program exists with zero. ++If the verification failed it exists with 2 and prints the following to stderr: ++.PP ++.nf ++.fam C ++ ERROR: Attestation measurement verification failed: ++ Calculated and received attestation measurement are not the same. ++ ++.fam T ++.fi ++.SH SEE ALSO ++\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-perform\fP(1) +--- /dev/null ++++ b/pvattest/man/pvattest.1 +@@ -0,0 +1,104 @@ ++.\" Copyright 2022 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++.TH pvattest 1 "07 June 2022" "s390-tools" "Attestation Manual" ++.nh ++.ad l ++.SH NAME ++\fBpvattest [OPTION?] COMMAND [OPTIONS] \fP- create, perform, and verify attestation measurements ++\fB ++.RE ++\fB ++.SH SYNOPSIS ++.nf ++.fam C ++\fBpvattest\fP \fIcreate\fP [\fIOPTIONS\fP] ++\fBpvattest\fP \fIperform\fP [\fIOPTIONS\fP] ++\fBpvattest\fP \fIverify\fP [\fIOPTIONS\fP] ++ ++.fam T ++.fi ++.fam T ++.fi ++.SH DESCRIPTION ++Use \fBpvattest\fP to attest that an IBM Secure Execution guest is the correct guest, and that it was started in a secure manner. ++Run '\fBpvattest\fP \fIcreate\fP' and '\fBpvattest\fP \fIverify\fP' in a trusted environment only. ++.PP ++.nf ++.fam C ++ create On a trusted system, creates an attestation request. ++ ++ perform On the SE-guest to be attested, sends the attestation request to the Ultravisor and receives the answer. ++ ++ verify On a trusted system, compares the answer from the Ultravisor to the one from your trusted environment. If they differ, the Secure Execution guest might be compromised. ++ ++.fam T ++.fi ++For meaningful results, run '\fIcreate\fP' and '\fIverify\fP' in a trusted environment, like your workstation or a previously attested IBM Secure Execution guest. Otherwise, the attestation might be tampered with. For all certificates, revocation lists, and host-key documents, both the PEM and DER input formats are supported. If you run \fBpvattest\fP on a machine architecture other than z/Architecture, 'measure' is not available. ++.PP ++Use '\fBpvattest\fP [COMMAND] \fB-h\fP' to get detailed help ++.RE ++.PP ++ ++.SH OPTIONS ++.TP ++.B ++\fB-h\fP, \fB--help\fP ++Show help options ++.TP ++.B ++\fB-v\fP, \fB--version\fP ++Print the version and exit. ++.TP ++.B ++\fB-V\fP, \fB--verbose\fP ++Provide more detailed output (optional) ++.RE ++.PP ++ ++.SH EXAMPLE ++For details refer to the man page of the command. ++.PP ++Create the request on a trusted system. ++.PP ++.nf ++.fam C ++ trusted:~$ pvattest create -k hkd.crt --cert CA.crt --cert ibmsk.crt --arpk arp.key -o attreq.bin ++ ++.fam T ++.fi ++On the SE-guest, \fIperform\fP the attestation. ++.PP ++.nf ++.fam C ++ seguest:~$ pvattest perform -i attreq.bin -o attresp.bin ++ ++.fam T ++.fi ++On a trusted system, \fIverify\fP that the response is correct. Here, the protection key from the creation and the SE-guest’s header is used to \fIverify\fP the measurement. ++.PP ++.nf ++.fam C ++ trusted:~$ pvattest verify -i attresp.bin --arpk arp.key --hdr se_guest.hdr ++ trusted:~$ echo $? ++ 0 ++ ++.fam T ++.fi ++ ++If the measurements do not match \fBpvattest\fP exits with code 2 and emits an error message. The SE-guest attestation failed. ++.PP ++.nf ++.fam C ++ trusted:~$ pvattest verify -i wrongresp.bin --arpk arp.key --hdr se_guest.hdr ++ ERROR: Attestation measurement verification failed: ++ Calculated and received attestation measurement are not the same. ++ trusted:~$ echo $? ++ 2 ++ ++.fam T ++.fi ++ ++.SH SEE ALSO ++\fBpvattest\fP-\fIcreate\fP(1), \fBpvattest-\fIverify\fP\fP(1), \fBpvattest\fP-\fIperform\fP(1) +--- /dev/null ++++ b/pvattest/src/.gitignore +@@ -0,0 +1,2 @@ ++.check-dep-pvattest ++.detect-openssl.dep.c +--- /dev/null ++++ b/pvattest/src/Makefile +@@ -0,0 +1,124 @@ ++include ../../common.mak ++ ++BIN_PROGRAM = pvattest ++PKGDATADIR ?= "$(DESTDIR)$(TOOLS_DATADIR)/$(BIN_PROGRAM)" ++ ++SRC_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) ++ROOT_DIR = $(rootdir) ++PVATTESTDIR := $(ROOT_DIR)/pvattest ++INCLUDE_PATHS = "$(SRC_DIR)" "$(ROOT_DIR)/include" ++INCLUDE_PARMS = $(addprefix -I,$(INCLUDE_PATHS)) ++ ++LIBPV_DIR = $(ROOT_DIR)/libpv ++LIBPV = $(LIBPV_DIR)/libpv.a ++ ++WARNINGS := -Wall -Wextra -Wshadow \ ++ -Wcast-align -Wwrite-strings -Wmissing-prototypes \ ++ -Wmissing-declarations -Wredundant-decls -Wnested-externs \ ++ -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \ ++ -Wpointer-arith -Wno-error=inline \ ++ -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \ ++ -Werror \ ++ $(NULL) ++ ++PVATTEST_SRCS := $(wildcard *.c) \ ++ $(NULL) ++ ++$(BIN_PROGRAM)_SRCS := \ ++ $(PVATTEST_SRCS) \ ++ $(NULL) ++ ++$(BIN_PROGRAM)_OBJS := $($(BIN_PROGRAM)_SRCS:.c=.o) ++ ++ifneq ($(shell sh -c 'command -v pkg-config'),) ++GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0) ++GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0) ++LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto openssl) ++LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto openssl) ++LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl) ++LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl) ++else ++GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include ++GLIB2_LIBS := -lglib-2.0 ++LIBCRYPTO_CFLAGS := ++LIBCRYPTO_LIBS := -lcrypto -lssl ++LIBCURL_CFLAGS := -I/usr/include/s390x-linux-gnu ++LIBCURL_LIBS := -lcurl ++endif ++ ++ALL_CFLAGS += -std=gnu11 \ ++ -DPKGDATADIR=$(PKGDATADIR) \ ++ -DOPENSSL_API_COMPAT=0x10101000L \ ++ $(GLIB2_CFLAGS) \ ++ $(LIBCRYPTO_CFLAGS) \ ++ $(LIBCURL_CFLAGS) \ ++ $(WARNINGS) \ ++ $(NULL) ++ ++ifneq ($(call check_header_prereq,"asm/uvdevice.h"),yes) ++ ALL_CFLAGS += -DPVATTEST_NO_PERFORM ++endif ++ ++ALL_CPPFLAGS += $(INCLUDE_PARMS) ++LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS) ++ ++BUILD_TARGETS := skip-$(BIN_PROGRAM) ++INSTALL_TARGETS := skip-$(BIN_PROGRAM) ++ifneq (${HAVE_OPENSSL},0) ++ifneq (${HAVE_GLIB2},0) ++ifneq (${HAVE_LIBCURL}, 0) ++ BUILD_TARGETS := $(BIN_PROGRAM) ++ INSTALL_TARGETS := install-$(BIN_PROGRAM) ++endif ++endif ++endif ++ ++all: $(BUILD_TARGETS) .check-dep-$(BIN_PROGRAM) ++ ++install: $(INSTALL_TARGETS) ++ ++$(BIN_PROGRAM): $($(BIN_PROGRAM)_OBJS) $(LIBPV) ++ ++skip-$(BIN_PROGRAM): ++ echo " SKIP $(BIN_PROGRAM) due to unresolved dependencies" ++ ++clean: ++ $(RM) -f -- $($(BIN_PROGRAM)_OBJS) $(BIN_PROGRAM) .check-dep-$(BIN_PROGRAM) .detect-openssl.dep.c ++ ++install-$(BIN_PROGRAM): $(BIN_PROGRAM) ++ $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) ++ $(INSTALL) -c $^ $(DESTDIR)$(USRBINDIR) ++ ++ ++.PHONY: all install clean skip-$(BIN_PROGRAM) install-$(BIN_PROGRAM) ++ ++$($(BIN_PROGRAM)_OBJS): .check-dep-$(BIN_PROGRAM) ++ ++.detect-openssl.dep.c: ++ echo "#include " > $@ ++ echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@ ++ echo " #error openssl version 1.1.0 is required" >> $@ ++ echo "#endif" >> $@ ++ echo "static void __attribute__((unused)) test(void) {" >> $@ ++ echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@ ++ echo " EVP_MD_CTX_free(ctx);" >> $@ ++ echo "}" >> $@ ++ ++.check-dep-$(BIN_PROGRAM): .detect-openssl.dep.c ++ $(call check_dep, \ ++ "$(BIN_PROGRAM)", \ ++ "glib.h", \ ++ "glib2-devel / libglib2.0-dev", \ ++ "HAVE_GLIB2=0") ++ $(call check_dep, \ ++ "$(BIN_PROGRAM)", \ ++ "openssl/evp.h", \ ++ "openssl-devel / libssl-dev version >= 1.1.0", \ ++ "HAVE_OPENSSL=0", \ ++ "-I.") ++ $(call check_dep, \ ++ "$(BIN_PROGRAM)", \ ++ "curl/curl.h", \ ++ "libcurl-devel", \ ++ "HAVE_LIBCURL=0") ++ touch $@ +--- /dev/null ++++ b/pvattest/src/arcb.c +@@ -0,0 +1,423 @@ ++/* ++ * Attestation Request Control Block related functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "libpv/crypto.h" ++#include "libpv/hash.h" ++ ++#include "arcb.h" ++#include "common.h" ++#include "log.h" ++ ++#define ARVN_VERSION_1 0x0100 ++#define MAX_ARL 0x2000 ++ ++typedef struct arcb_v1_hdr { ++ uint64_t reserved0; /* 0x0000 */ ++ be32_t arvn; /* 0x0008 */ ++ be32_t arl; /* 0x000c */ ++ uint8_t iv[ARCB_V1_IV_SIZE]; /* 0x0010 */ ++ uint32_t reserved1c; /* 0x001c */ ++ uint8_t reserved20[7]; /* 0x0020 */ ++ uint8_t nks; /* 0x0027 */ ++ uint32_t reserved28; /* 0x0028 */ ++ be32_t sea; /* 0x002c */ ++ be64_t paf; /* 0x0030 */ ++ be32_t mai; /* 0x0038 */ ++ uint32_t reserved3c; /* 0x003c */ ++ PvEcdhPubKey cpk; /* 0x0040 */ ++} __packed arcb_v1_hdr_t; ++G_STATIC_ASSERT(sizeof(arcb_v1_hdr_t) == 0xe0); ++ ++typedef struct arcb_v1_key_slot { ++ uint8_t phkh[ARCB_V1_PHKH_SIZE]; ++ uint8_t warpk[ARCB_V1_ATTEST_PROT_KEY_SIZE]; ++ uint8_t kst[ARCB_V1_TAG_SIZE]; ++} __packed arcb_v1_key_slot_t; ++G_STATIC_ASSERT(sizeof(arcb_v1_key_slot_t) == 0x50); ++ ++struct arcb_v1 { ++ /* authenticated data */ ++ uint32_t arvn; ++ uint32_t mai; ++ uint64_t paf; ++ GBytes *iv; ++ EVP_PKEY *evp_cust_pub_key; ++ GSList *host_key_slots; ++ ++ /* confidential data */ ++ GBytes *confidential_measurement_key; ++ GBytes *confidential_optional_nonce; ++ GBytes *confidential_att_req_prot_key; ++}; ++ ++void arcb_v1_clear_free(arcb_v1_t *arcb) ++{ ++ if (!arcb) ++ return; ++ ++ g_slist_free_full(arcb->host_key_slots, g_free); ++ g_bytes_unref(arcb->confidential_measurement_key); ++ g_bytes_unref(arcb->confidential_optional_nonce); ++ g_bytes_unref(arcb->confidential_att_req_prot_key); ++ g_bytes_unref(arcb->iv); ++ EVP_PKEY_free(arcb->evp_cust_pub_key); ++ g_free(arcb); ++} ++ ++static void arcb_v1_set_paf(arcb_v1_t *arcb, const uint64_t paf, GError **error) ++{ ++ const uint64_t known_flags = ARCB_V1_PAF_ALL & ~ARCB_V1_PAF_NONCE; ++ ++ if ((paf & ARCB_V1_PAF_NONCE) != 0) { ++ g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_PAF, ++ _("The given paf (%#.16lx) specifies the NONCE flag (%#.16lx)."), paf, ++ ARCB_V1_PAF_NONCE); ++ return; ++ } ++ if ((paf & ~known_flags) != 0) ++ pvattest_log_warning( ++ _("The given paf (%#.16lx) specifies unknown flags. Use at your own risk!"), ++ paf, known_flags); ++ arcb->paf = paf; ++} ++ ++arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey, ++ uint64_t paf, GError **error) ++{ ++ g_autoptr(arcb_v1_t) arcb = g_new0(arcb_v1_t, 1); ++ ++ g_assert(g_bytes_get_size(iv) == ARCB_V1_IV_SIZE); ++ g_assert(g_bytes_get_size(arpk) == ARCB_V1_ATTEST_PROT_KEY_SIZE); ++ g_assert(g_bytes_get_size(mkey) == HMAC_SHA512_KEY_SIZE); ++ ++ pv_wrapped_g_assert(arpk); ++ pv_wrapped_g_assert(iv); ++ pv_wrapped_g_assert(evp_cpk); ++ pv_wrapped_g_assert(mkey); ++ ++ arcb->arvn = ARVN_VERSION_1; ++ arcb->mai = mai; ++ arcb_v1_set_paf(arcb, paf, error); ++ if (*error) ++ return NULL; ++ arcb->iv = g_bytes_ref(iv); ++ ++ if (EVP_PKEY_up_ref(evp_cpk) != 1) ++ g_abort(); ++ arcb->evp_cust_pub_key = evp_cpk; ++ ++ arcb->confidential_att_req_prot_key = g_bytes_ref(arpk); ++ arcb->confidential_measurement_key = g_bytes_ref(mkey); ++ ++ return g_steal_pointer(&arcb); ++} ++ ++int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error) ++{ ++ g_autoptr(GBytes) warpk = NULL, tag = NULL, phkh = NULL; ++ g_autoptr(GBytes) exchange_key = NULL, iv = NULL; ++ g_autofree arcb_v1_key_slot_t *key_slot = NULL; ++ g_autofree PvEcdhPubKey *ecdh_host = NULL; ++ g_autofree uint8_t *iv_raw = NULL; ++ PvCipherParms parms; ++ int64_t gcm_rc; ++ ++ g_assert(arcb->confidential_att_req_prot_key); ++ ++ pv_wrapped_g_assert(arcb); ++ pv_wrapped_g_assert(evp_host); ++ ++ /* encrypt (=wrap) attestation request protection key, store warpk + tag */ ++ exchange_key = pv_derive_exchange_key(arcb->evp_cust_pub_key, evp_host, error); ++ if (!exchange_key) ++ return -1; ++ ++ iv_raw = g_malloc0(ARCB_V1_IV_SIZE); ++ iv = g_bytes_new_take(g_steal_pointer(&iv_raw), ARCB_V1_IV_SIZE); ++ if (!iv) ++ g_abort(); ++ ++ parms.key = exchange_key; ++ parms.iv = iv; ++ parms.cipher = EVP_aes_256_gcm(); ++ parms.tag_size = ARCB_V1_TAG_SIZE; ++ gcm_rc = pv_gcm_encrypt(arcb->confidential_att_req_prot_key, NULL, &parms, &warpk, &tag, ++ error); ++ if (gcm_rc != ARCB_V1_ATTEST_PROT_KEY_SIZE) ++ return -1; ++ ++ /* calculate public host key hash */ ++ ecdh_host = pv_evp_pkey_to_ecdh_pub_key(evp_host, error); ++ if (!ecdh_host) ++ return -1; ++ phkh = pv_sha256_hash(ecdh_host->data, sizeof(ecdh_host->data), error); ++ if (!phkh) ++ return -1; ++ ++ /* copy to list */ ++ g_assert(g_bytes_get_size(warpk) == sizeof(key_slot->warpk)); ++ g_assert(g_bytes_get_size(tag) == sizeof(key_slot->kst)); ++ g_assert(g_bytes_get_size(phkh) == sizeof(key_slot->phkh)); ++ ++ key_slot = g_malloc0(sizeof(*key_slot)); ++ pv_gbytes_memcpy(key_slot->warpk, sizeof(key_slot->warpk), warpk); ++ pv_gbytes_memcpy(key_slot->kst, sizeof(key_slot->warpk), tag); ++ pv_gbytes_memcpy(key_slot->phkh, sizeof(key_slot->warpk), phkh); ++ ++ arcb->host_key_slots = g_slist_prepend(arcb->host_key_slots, g_steal_pointer(&key_slot)); ++ return 0; ++} ++ ++void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce) ++{ ++ pv_wrapped_g_assert(arcb); ++ pv_wrapped_g_assert(nonce); ++ arcb_v1_rm_nonce(arcb); ++ g_assert(!arcb->confidential_optional_nonce); ++ ++ g_assert(g_bytes_get_size(nonce) == ARCB_V1_NONCE_SIZE); ++ arcb->confidential_optional_nonce = g_bytes_ref(nonce); ++ ++ arcb->paf |= ARCB_V1_PAF_NONCE; ++} ++ ++void arcb_v1_rm_nonce(arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ if (!arcb->confidential_optional_nonce) ++ return; ++ g_bytes_unref(arcb->confidential_optional_nonce); ++ arcb->confidential_optional_nonce = NULL; ++ arcb->paf &= ~ARCB_V1_PAF_NONCE; ++} ++ ++GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error) ++{ ++ pv_wrapped_g_assert(arcb); ++ g_autoptr(GByteArray) arcb_gba = NULL; ++ g_autoptr(GBytes) confidential_area = NULL; ++ g_autoptr(GBytes) aad = NULL; ++ g_autoptr(GBytes) art = NULL; ++ g_autoptr(GBytes) encrypted_area = NULL; ++ g_autoptr(GBytes) result = NULL; ++ g_autofree PvEcdhPubKey *ecdh_cpk = NULL; ++ PvCipherParms parms = { ++ .cipher = EVP_aes_256_gcm(), ++ .tag_size = AES_256_GCM_TAG_SIZE, ++ }; ++ size_t att_req_len = 0, nks = 0, sea = 0; ++ ++ arcb_v1_hdr_t hdr = { ++ .arvn = GUINT32_TO_BE(arcb->arvn), ++ .paf = GUINT64_TO_BE(arcb->paf), ++ .mai = GUINT32_TO_BE(arcb->mai), ++ }; ++ ++ g_assert(arcb->host_key_slots); ++ ++ /* calculate sizes */ ++ nks = g_slist_length(arcb->host_key_slots); ++ g_assert(nks < 0xFF); ++ ++ sea = g_bytes_get_size(arcb->confidential_measurement_key); ++ if (arcb->confidential_optional_nonce) ++ sea += g_bytes_get_size(arcb->confidential_optional_nonce); ++ ++ g_assert(sea == HMAC_SHA512_KEY_SIZE || sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE); ++ ++ att_req_len = sizeof(hdr) + nks * sizeof(arcb_v1_key_slot_t) + HMAC_SHA512_KEY_SIZE + ++ ARCB_V1_TAG_SIZE; ++ if (arcb->confidential_optional_nonce) ++ att_req_len += ARCB_V1_NONCE_SIZE; ++ ++ g_assert(att_req_len <= MAX_ARL); ++ ++ /* copy plain data to contiguous memory */ ++ hdr.arl = GUINT32_TO_BE((uint32_t)att_req_len); ++ ++ pv_gbytes_memcpy(hdr.iv, ARCB_V1_IV_SIZE, arcb->iv); ++ hdr.nks = (uint8_t)nks; ++ hdr.sea = GUINT32_TO_BE((uint32_t)sea); ++ ecdh_cpk = pv_evp_pkey_to_ecdh_pub_key(arcb->evp_cust_pub_key, error); ++ memcpy(&hdr.cpk, ecdh_cpk, sizeof(*ecdh_cpk)); ++ arcb_gba = g_byte_array_sized_new((guint)att_req_len); ++ g_byte_array_append(arcb_gba, (const uint8_t *)&hdr, sizeof(hdr)); ++ ++ for (GSList *elem = arcb->host_key_slots; elem; elem = elem->next) ++ g_byte_array_append(arcb_gba, elem->data, sizeof(arcb_v1_key_slot_t)); ++ ++ /* encrypt the confidential data */ ++ confidential_area = secure_gbytes_concat(arcb->confidential_measurement_key, ++ arcb->confidential_optional_nonce); ++ parms.key = arcb->confidential_att_req_prot_key; ++ parms.iv = arcb->iv; ++ aad = g_bytes_new(arcb_gba->data, arcb_gba->len); ++ pv_gcm_encrypt(confidential_area, aad, &parms, &encrypted_area, &art, error); ++ if (*error) ++ return NULL; ++ ++ g_byte_array_append(arcb_gba, g_bytes_get_data(encrypted_area, NULL), (guint)sea); ++ g_byte_array_append(arcb_gba, g_bytes_get_data(art, NULL), ARCB_V1_TAG_SIZE); ++ ++ result = g_byte_array_free_to_bytes(arcb_gba); ++ arcb_gba = NULL; ++ return g_steal_pointer(&result); ++} ++ ++uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error) ++{ ++ pv_wrapped_g_assert(arcb); ++ switch (arcb->mai) { ++ case MAI_HMAC_SHA512: ++ return HMAC_SHA512_KEY_SIZE; ++ default: ++ g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI, ++ _("Unknown measurement algorithm ID specified (%#x)."), arcb->mai); ++ return 0; ++ } ++} ++ ++uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb) ++{ ++ uint32_t size = 0; ++ ++ pv_wrapped_g_assert(arcb); ++ ++ if (arcb_v1_additional_has_phkh_image(arcb)) ++ size += ARCB_V1_PHKH_SIZE; ++ if (arcb_v1_additional_has_phkh_attest(arcb)) ++ size += ARCB_V1_PHKH_SIZE; ++ return size; ++} ++ ++gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ return arcb->confidential_optional_nonce != NULL; ++} ++ ++gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_HEADER) != 0; ++} ++ ++gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_ATTEST) != 0; ++} ++ ++GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ return g_bytes_ref(arcb->confidential_measurement_key); ++} ++ ++GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ if (arcb->confidential_optional_nonce) ++ return g_bytes_ref(arcb->confidential_optional_nonce); ++ return NULL; ++} ++ ++GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb) ++{ ++ pv_wrapped_g_assert(arcb); ++ return g_bytes_ref(arcb->confidential_att_req_prot_key); ++} ++ ++static gboolean is_v1_arcb(size_t aad_size, size_t sea, size_t arl, size_t serialized_arcb_size, ++ uint32_t arcb_version, gboolean has_nonce) ++{ ++ gboolean result = aad_size + sea + ARCB_V1_TAG_SIZE == arl; ++ ++ result &= arl <= serialized_arcb_size; ++ result &= arcb_version == ARVN_VERSION_1; ++ result &= has_nonce ? sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE : ++ sea == HMAC_SHA512_KEY_SIZE; ++ return result; ++} ++ ++gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk, ++ GBytes **measurement_key, GBytes **optional_nonce, ++ GError **error) ++{ ++ g_autoptr(GBytes) encr = NULL, decr = NULL, aad = NULL, tag = NULL, iv = NULL; ++ const struct arcb_v1_hdr *serialized_arcb_hdr; ++ const uint8_t *encr_u8, *aad_u8, *tag_u8; ++ const uint8_t *serialized_arcb_u8; ++ size_t serialized_arcb_size; ++ uint32_t arcb_version, mai; ++ size_t aad_size, arl, sea; ++ PvCipherParms parms; ++ gboolean has_nonce; ++ uint64_t paf; ++ ++ pv_wrapped_g_assert(serialized_arcb); ++ pv_wrapped_g_assert(arpk); ++ serialized_arcb_u8 = g_bytes_get_data(serialized_arcb, &serialized_arcb_size); ++ serialized_arcb_hdr = (const arcb_v1_hdr_t *)serialized_arcb_u8; ++ arl = GUINT32_FROM_BE(serialized_arcb_hdr->arl); ++ arcb_version = GUINT32_FROM_BE(serialized_arcb_hdr->arvn); ++ mai = GUINT32_FROM_BE(serialized_arcb_hdr->mai); ++ ++ aad_u8 = serialized_arcb_u8; ++ aad_size = sizeof(*serialized_arcb_hdr) + ++ serialized_arcb_hdr->nks * sizeof(arcb_v1_key_slot_t); ++ encr_u8 = aad_u8 + aad_size; ++ sea = GUINT32_FROM_BE(serialized_arcb_hdr->sea); ++ tag_u8 = encr_u8 + sea; ++ paf = GUINT64_FROM_BE(serialized_arcb_hdr->paf); ++ has_nonce = (paf & ARCB_V1_PAF_NONCE) != 0; ++ ++ if (!is_v1_arcb(aad_size, sea, arl, serialized_arcb_size, arcb_version, has_nonce)) { ++ g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB, ++ _("The provided attestation request is not valid")); ++ return FALSE; ++ } ++ if (mai != MAI_HMAC_SHA512) { ++ g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI, ++ _("Unsupported measurement argument ID (%#x)"), mai); ++ return FALSE; ++ } ++ ++ aad = g_bytes_new(aad_u8, aad_size); ++ encr = g_bytes_new(encr_u8, sea); ++ tag = g_bytes_new(tag_u8, ARCB_V1_TAG_SIZE); ++ iv = g_bytes_new(serialized_arcb_hdr->iv, sizeof(serialized_arcb_hdr->iv)); ++ ++ parms.cipher = EVP_aes_256_gcm(); ++ parms.tag_size = AES_256_GCM_TAG_SIZE; ++ parms.key = arpk; ++ parms.iv = iv; ++ pv_gcm_decrypt(encr, aad, tag, &parms, &decr, error); ++ if (*error) { ++ GError *tmp_error = NULL; ++ ++ g_set_error(&tmp_error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB, ++ _("Cannot verify the attestation request: %s"), (*error)->message); ++ g_clear_error(error); ++ g_propagate_error(error, tmp_error); ++ return FALSE; ++ } ++ if (measurement_key) ++ *measurement_key = g_bytes_new(g_bytes_get_data(decr, NULL), HMAC_SHA512_KEY_SIZE); ++ if (optional_nonce && has_nonce) ++ *optional_nonce = ++ g_bytes_new((uint8_t *)g_bytes_get_data(decr, NULL) + HMAC_SHA512_KEY_SIZE, ++ ARCB_V1_NONCE_SIZE); ++ return TRUE; ++} +--- /dev/null ++++ b/pvattest/src/arcb.h +@@ -0,0 +1,152 @@ ++/* ++ * Attestation Request Control Block related functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_ARCB_H ++#define PVATTEST_ARCB_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/glib-helper.h" ++ ++#include "lib/zt_common.h" ++#include "libpv/crypto.h" ++ ++#include "types.h" ++ ++#define MAI_HMAC_RESERVED_INVALID 0 ++#define MAI_HMAC_SHA512 0x1 ++ ++#define HMAC_SHA512_KEY_SIZE 64 ++#define ARCB_V1_ATTEST_PROT_KEY_SIZE 32 ++#define ARCB_V1_NONCE_SIZE 16 ++#define ARCB_V1_TAG_SIZE 16 ++#define ARCB_V1_IV_SIZE 12 ++#define ARCB_V1_PHKH_SIZE 32 ++ ++#define BIT(bit) ((uint64_t)1 << (63 - (bit))) ++/* Optional nonce in ARCB */ ++#define ARCB_V1_PAF_NONCE BIT(1) ++/* Public host key hash used to unseal SE header added to additional data to be measured */ ++#define ARCB_V1_PAF_AAD_PHKH_HEADER BIT(2) ++/* Public host key hash used to unseal this attestation added to additional data to be measured */ ++#define ARCB_V1_PAF_AAD_PHKH_ATTEST BIT(3) ++/* Temporary backup-host-key use allowed */ ++#define ARCB_V1_PAF_TMP_BACKUP_ALLOWED BIT(62) ++ ++/* Global not-host-specific key allowed */ ++#define ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED BIT(63) ++ ++#define ARCB_V1_PAF_ALL \ ++ (ARCB_V1_PAF_NONCE | ARCB_V1_PAF_AAD_PHKH_HEADER | ARCB_V1_PAF_AAD_PHKH_ATTEST | \ ++ ARCB_V1_PAF_TMP_BACKUP_ALLOWED | ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED) ++ ++typedef struct arcb_v1 arcb_v1_t; ++ ++/** arcb_v1_new: ++ * ++ * @arpk: Attestation Request Protection key. AES-GCM-256 key to ++ * protect Measurement Key and Nonce. ++ * Must be ´ARCB_V1_ATTEST_PROT_KEY_SIZE´ bytes long. ++ * @iv: IV for protecting Measuremt Key and Nonce. ++ * Should be random for each new ARPK. ++ * Must be ´ARCB_V1_IV_SIZE´ bytes long. ++ * @mai: Measurement Algorithm Identifier for the attestation measurement. ++ * See ´enum mai´ ++ * @evp_cpk: Customer key in EVP_PKEY format. Must contain private and public key pair. ++ * @mkey: Measurement key to calculate the Measurement. ++ * Must be ´HMAC_SHA512_KEY_SIZE´ bytes long. ++ * @paf: Plain text Attestation Flags. See ´enum plaintext_attestattion_flags´. ++ * ´ARCB_V1_PAF_NONCE´ must not be set. ++ * @error: GError. *error will != NULL if error occours. ++ * ++ * arpk, mkey, and iv must me correct size ++ * If not this is considered as a programming error (No warning; ++ * Results in Assertion or undefined behavior). ++ * ++ * GBytes will be ref'ed. ++ * ++ * All numbers must be in system byte order and will be converted to big endian ++ * if needed. ++ * ++ * Returns: (nullable) (transfer full): new ARCB context. ++ */ ++arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey, ++ uint64_t paf, GError **error) PV_NONNULL(1, 2, 4, 5); ++void arcb_v1_clear_free(arcb_v1_t *arcb); ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(arcb_v1_t, arcb_v1_clear_free) ++ ++/** arcb_v1_add_key_slot: ++ * ++ * @arcb: ARCB context. ++ * @evp_host: Host public key. ++ * @error: GError. *error will != NULL if error occours. ++ * ++ * Builds a key slot. Calculates exchange key, wraps ARPK with the exchange key. ++ * Calculates the public host key hash. Calculates the key slot tag. ++ * Adds it to the ARCB. ++ * ++ * Returns: 0 in case of success, -1 otherwise ++ */ ++int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error) PV_NONNULL(1, 2); ++void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce) PV_NONNULL(1, 2); ++void arcb_v1_rm_nonce(arcb_v1_t *arcb) PV_NONNULL(1); ++ ++/** arcb_v1_serialize: ++ * ++ * @arcb: ARCB context. ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Will create a valid ARCB for the UV. Including encrypting confidential data. ++ * At least one key_slot must be added beforehand. ++ * ++ * Returns: (nullable) (transfer full): The serialized ARCB which can be added to the ++ * Retrieve Attestation Measurement UVC as GBytes. ++ */ ++GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error) PV_NONNULL(1, 2); ++ ++uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error) ++ PV_NONNULL(1, 2); ++uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb) PV_NONNULL(1); ++gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb) PV_NONNULL(1); ++gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb) PV_NONNULL(1); ++gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb) PV_NONNULL(1); ++ ++GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb) PV_NONNULL(1); ++GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb) PV_NONNULL(1); ++GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb) PV_NONNULL(1); ++ ++/** arcb_v1_verify_serialized_arcb: ++ * ++ * @serialized_arcb: binary ARCB in UV readable format. ++ * @arpk: Attestation Request Protection key that was used to create serialized_arpk ++ * @measurement_key: Output parameter: decrypted measurement key if no error. ++ * May be NULL if not interested for this output. ++ * @optional_nonce: Output parameter: decrypted nonce if no error. ++ * May be NULL if not interested for this output. ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * ++ * Checks if sizes are sound and flags are known by this implementation. ++ * Decrypts Measurement key and nonce (if given) and verifies ARCB tag. ++ * ++ * Returns: TRUE if ARCB is valid, including matching ARCB tag. Otherwise FALSE. ++ * ++ */ ++gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk, ++ GBytes **measurement_key, GBytes **optional_nonce, ++ GError **error) PV_NONNULL(1, 2); ++ ++#define ARCB_ERROR g_quark_from_static_string("pv-arcb_error-quark") ++typedef enum arcb_error { ++ ARCB_ERR_INVALID_ARCB, ++ ARCB_ERR_INVALID_PAF, ++ ARCB_ERR_INVALID_MAI, ++ ARCB_ERR_UNABLE_ENCR_ARPK, ++} arcb_error_e; ++ ++#endif /* PVATTEST_ARCB_H */ +--- /dev/null ++++ b/pvattest/src/argparse.c +@@ -0,0 +1,649 @@ ++/* ++ * Definitions used for parsing arguments. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "argparse.h" ++#include "log.h" ++#include "common.h" ++ ++#define DEFAULT_OUTPUT_FILE_NAME "attest.bin" ++#define DEFAULT_OPTION_PHKH_IMG FALSE ++#define DEFAULT_OPTION_PHKH_ATT FALSE ++#define DEFAULT_OPTION_NO_VERIFY FALSE ++#define DEFAULT_OPTION_ONLINE TRUE ++#define DEFAULT_OPTION_NONCE TRUE ++ ++static pvattest_config_t pvattest_config = { ++ .general = { ++ .log_level = PVATTEST_LOG_LVL_DEFAULT, ++ }, ++ .create = { ++ .output_path = NULL, ++ .host_key_document_paths = NULL, ++ .crl_paths = NULL, ++ .root_ca_path = NULL, ++ .certificate_paths = NULL, ++ .arp_key_out_path = NULL, ++ .phkh_img = DEFAULT_OPTION_PHKH_IMG, ++ .phkh_att = DEFAULT_OPTION_PHKH_ATT, ++ .online = DEFAULT_OPTION_ONLINE, ++ .use_nonce = DEFAULT_OPTION_NONCE, ++ .paf = 0, ++ .x_aad_size = -1, ++ }, ++ .perform = { ++ .output_path = NULL, ++ .input_path = NULL, ++ }, ++ .verify = { ++ .input_path = NULL, ++ .hdr_path = NULL, ++ .arp_key_in_path = NULL, ++ }, ++}; ++typedef gboolean (*verify_options_fn_t)(GError **); ++ ++static gboolean check_for_non_null(const void *ptr, const char *msg, GError **error) ++{ ++ if (!ptr) { ++ g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "%s", msg); ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++static gboolean _check_for_invalid_path(const char *path, gboolean must_exist, GError **error) ++{ ++ int cached_errno = 0; ++ ++ g_assert(path); ++ ++ if (must_exist) { ++ if (access(path, F_OK | R_OK) != 0) ++ cached_errno = errno; ++ } ++ if (cached_errno) { ++ g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "Cannot access '%s': %s", ++ path, g_strerror(cached_errno)); ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++static gboolean check_for_optional_invalid_path(const char *path, gboolean must_exist, ++ GError **error) ++{ ++ if (!path) ++ return TRUE; ++ return _check_for_invalid_path(path, must_exist, error); ++} ++ ++static gboolean check_for_invalid_path(const char *path, gboolean must_exist, const char *null_msg, ++ GError **error) ++{ ++ if (!check_for_non_null(path, null_msg, error)) ++ return FALSE; ++ return _check_for_invalid_path(path, must_exist, error); ++} ++ ++static gboolean _check_file_list(char **path_list, gboolean must_exist, GError **error) ++{ ++ char *path = NULL; ++ for (char **path_it = path_list; path_it != NULL && *path_it != NULL; path_it++) { ++ path = *path_it; ++ if (!_check_for_invalid_path(path, must_exist, error)) ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++static gboolean check_optional_file_list(char **path_list, gboolean must_exist, GError **error) ++{ ++ if (!path_list) ++ return TRUE; ++ return _check_file_list(path_list, must_exist, error); ++} ++ ++static gboolean check_file_list(char **path_list, gboolean must_exist, const char *null_msg, ++ GError **error) ++{ ++ if (!check_for_non_null(path_list, null_msg, error)) ++ return FALSE; ++ return _check_file_list(path_list, must_exist, error); ++} ++ ++static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error) ++{ ++ uint64_t value; ++ gchar *end; ++ ++ g_assert(dst); ++ ++ if (!g_str_is_ascii(nptr)) { ++ g_set_error( ++ error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, ++ _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), ++ nptr); ++ return FALSE; ++ } ++ ++ value = g_ascii_strtoull(nptr, &end, 16); ++ if ((value == G_MAXUINT64 && errno == ERANGE) || (end && *end != '\0')) { ++ g_set_error( ++ error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, ++ _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), ++ nptr); ++ return FALSE; ++ } ++ *dst = value; ++ return TRUE; ++} ++ ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wmissing-field-initializers" ++ ++/************************* SHARED OPTIONS *************************************/ ++/* NOTE REQUIRED */ ++#define _entry_host_key_document(__arg_data, __indent) \ ++ { \ ++ .long_name = "host-key-document", .short_name = 'k', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \ ++ .description = "Specify one or more host key documents.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_certs(__arg_data, __indent) \ ++ { \ ++ .long_name = "cert", .short_name = 'C', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \ ++ .description = \ ++ "Specifies the certificate that is used to establish a chain\n" __indent \ ++ "of trust for the verification of the host-key documents. Specify\n" __indent \ ++ "this option twice to specify the IBM Z signing key and the\n" __indent \ ++ "intermediate CA certificate (signed by the root CA). Required.\n" __indent \ ++ "Ignored when --no-verify is specified.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_crls(__arg_data, __indent) \ ++ { \ ++ .long_name = "crl", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \ ++ .description = "Specify FILE to be a certificate revocation list\n" __indent \ ++ "(optional).", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_root_ca(__arg_data, __indent) \ ++ { \ ++ .long_name = "root-ca", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \ ++ .description = "Use FILE as the trusted root CA instead the\n" __indent \ ++ "root CAs that are installed on the system (optional).", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_guest_hdr(__arg_data, __indent) \ ++ { \ ++ .long_name = "hdr", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = \ ++ "Specify the header of the guest image. Exactly one is required.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_input(__arg_data, __additional_text, __indent) \ ++ { \ ++ .long_name = "input", .short_name = 'i', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = "FILE specifies the " __additional_text "\n" __indent \ ++ " as input.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_output(__arg_data, __additional_text, __indent) \ ++ { \ ++ .long_name = "output", .short_name = 'o', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = "FILE specifies the output for the\n" __indent __additional_text \ ++ ".\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_att_prot_key_save(__arg_data, __indent) \ ++ { \ ++ .long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = \ ++ "Save the protection key as GCM-AES256 key in FILE\n" __indent \ ++ "Do not publish this key, otherwise your attestation is compromised.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++/* NOTE REQUIRED */ ++#define _entry_att_prot_key_load(__arg_data, __indent) \ ++ { \ ++ .long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = "Use FILE to specify the GCM-AES256 key to decrypt\n" __indent \ ++ "the attestation request.\n" __indent \ ++ "Delete this key after verification.\n", \ ++ .arg_description = "FILE", \ ++ } ++ ++#define _entry_phkh_img(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-phkh-img", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \ ++ .description = "add the public host key hash of the\n" __indent \ ++ "image header used to decrypt\n" __indent \ ++ "the secure guest to the measurement. (optional)\n" \ ++ } ++ ++#define _entry_phkh_att(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-phkh-att", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \ ++ .description = "add the public host key hash of the\n" __indent \ ++ "attestation header used to decrypt\n" __indent \ ++ "the attestation request to the measurement. (optional)\n" \ ++ } ++ ++#define _entry_no_verify(__arg_data, __indent) \ ++ { \ ++ .long_name = "no-verify", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \ ++ .description = \ ++ "Disable the host-key-document verification.\n" __indent \ ++ "Does not require the host-key documents to be valid.\n" __indent \ ++ "For testing purposes, do not use for a production image.\n" __indent \ ++ "(optional)\n", \ ++ } ++ ++#define _entry_offline_maps_to_online(__arg_data, __indent) \ ++ { \ ++ .long_name = "offline", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \ ++ .arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \ ++ .description = "Don't download CRLs (optional).\n", \ ++ } ++ ++#define _entry_verbose(__indent) \ ++ { \ ++ .long_name = "verbose", .short_name = 'V', .flags = G_OPTION_FLAG_NO_ARG, \ ++ .arg = G_OPTION_ARG_CALLBACK, .arg_data = &increase_log_lvl, \ ++ .description = "Provide more detailed output (optional)\n", \ ++ .arg_description = NULL, \ ++ } ++ ++#define _entry_x_paf(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-paf", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_CALLBACK, .arg_data = __arg_data, \ ++ .description = "Specify the Plain text Attestation Flags\n" __indent \ ++ "as a hexadecimal value. Flags that change\n" __indent \ ++ "the paf (--phkh-*) take precedence over\n" __indent \ ++ "this flag.\n" __indent \ ++ "Setting the nonce paf is not allowed here.\n" __indent \ ++ "(optional, default 0x0)\n", \ ++ .arg_description = "HEX", \ ++ } ++ ++#define _entry_x_no_nonce(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-no-nonce", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \ ++ .arg = G_OPTION_ARG_NONE, .arg_data = __arg_data, \ ++ .description = "Do not use a nonce in the request.\n" __indent \ ++ "(optional, not recommended)\n" \ ++ } ++ ++#define _entry_x_aad_size(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-add-size", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_INT, .arg_data = __arg_data, \ ++ .description = "Specify the size of the additional area\n" __indent \ ++ "Overwrite every flag that changes\n" __indent \ ++ "this size implicitly. No verification is performed!\n" __indent \ ++ "Ignored if negative.\n" __indent "(optional, default ignored)\n", \ ++ .arg_description = "INT" \ ++ } ++ ++#define _entry_x_user_data(__arg_data, __indent) \ ++ { \ ++ .long_name = "x-user-data", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = "Use FILE to specify the user data.\n", .arg_description = "FILE", \ ++ } ++ ++static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name, ++ G_GNUC_UNUSED const char *value, G_GNUC_UNUSED void *data, ++ G_GNUC_UNUSED GError **error) ++{ ++ pvattest_log_increase_log_lvl(&pvattest_config.general.log_level); ++ return TRUE; ++} ++ ++static gboolean create_set_paf(G_GNUC_UNUSED const char *option_name, const char *value, ++ G_GNUC_UNUSED void *data, GError **error) ++{ ++ return hex_str_toull(value, &pvattest_config.create.paf, error); ++} ++ ++/***************************** GENERAL OPTIONS ********************************/ ++static gboolean print_version = FALSE; ++ ++static GOptionEntry general_options[] = { ++ { ++ .long_name = "version", ++ .short_name = 'v', ++ .flags = G_OPTION_FLAG_NONE, ++ .arg = G_OPTION_ARG_NONE, ++ .arg_data = &print_version, ++ .description = "Print the version and exit.\n", ++ .arg_description = NULL, ++ }, ++ _entry_verbose(""), ++ { NULL }, ++}; ++ ++/************************* CREATE ATTESTATION OPTIONS *************************/ ++#define create_indent " " ++ ++static GOptionEntry create_options[] = { ++ _entry_host_key_document(&pvattest_config.create.host_key_document_paths, create_indent), ++ _entry_certs(&pvattest_config.create.certificate_paths, create_indent), ++ _entry_crls(&pvattest_config.create.crl_paths, create_indent), ++ _entry_root_ca(&pvattest_config.create.root_ca_path, create_indent), ++ _entry_output(&pvattest_config.create.output_path, "attestation request", create_indent), ++ _entry_att_prot_key_save(&pvattest_config.create.arp_key_out_path, create_indent), ++ ++ _entry_no_verify(&pvattest_config.create.no_verify, create_indent), ++ _entry_offline_maps_to_online(&pvattest_config.create.online, create_indent), ++ _entry_verbose(create_indent), ++ { NULL } ++}; ++ ++static GOptionEntry experimental_create_options[] = { ++ _entry_x_no_nonce(&pvattest_config.create.use_nonce, create_indent), ++ _entry_x_paf(&create_set_paf, create_indent), ++ _entry_x_aad_size(&pvattest_config.create.x_aad_size, create_indent), ++ _entry_phkh_img(&pvattest_config.create.phkh_img, create_indent), ++ _entry_phkh_att(&pvattest_config.create.phkh_att, create_indent), ++ { NULL } ++}; ++ ++static gboolean verify_create(GError **error) ++{ ++ if (!check_file_list(pvattest_config.create.host_key_document_paths, TRUE, ++ _("Specify --host-key-document at least once."), error)) ++ return FALSE; ++ if (!pvattest_config.create.no_verify) { ++ if (!check_file_list( ++ pvattest_config.create.certificate_paths, TRUE, ++ _("Either specify the IBM Z signing key and" ++ " intermediate CA certificate\nby using the '--cert' option, or" ++ " use the '--no-verify' flag to disable the\nhost-key document" ++ " verification completely (at your own risk).\n" ++ "Only use this option in test environments or if" ++ " you trust the unverified document."), ++ error)) ++ return FALSE; ++ } ++ if (!check_for_invalid_path(pvattest_config.create.arp_key_out_path, FALSE, ++ _("Missing argument for --arpk."), error)) ++ return FALSE; ++ if (!check_for_invalid_path(pvattest_config.create.output_path, FALSE, ++ _("Missing argument for --output."), error)) ++ return FALSE; ++ if (!check_optional_file_list(pvattest_config.create.crl_paths, TRUE, error)) ++ return FALSE; ++ if (!check_for_optional_invalid_path(pvattest_config.create.root_ca_path, TRUE, error)) ++ return FALSE; ++ return TRUE; ++}; ++ ++/************************* MEASUREMENT OPTIONS ********************************/ ++#define perform_indent " " ++ ++static GOptionEntry perform_options[] = { ++ _entry_input(&pvattest_config.perform.input_path, "attestation request", perform_indent), ++ _entry_output(&pvattest_config.perform.output_path, "attestation result", perform_indent), ++ _entry_verbose(perform_indent), ++ { NULL }, ++}; ++ ++static GOptionEntry experimental_perform_options[] = { ++ _entry_x_user_data(&pvattest_config.perform.user_data_path, perform_indent), ++ { NULL }, ++}; ++ ++static gboolean verify_perform(GError **error) ++{ ++ if (!check_for_invalid_path(pvattest_config.perform.input_path, TRUE, ++ _("Missing argument for --input."), error)) ++ return FALSE; ++ if (!check_for_invalid_path(pvattest_config.perform.output_path, FALSE, ++ _("Missing argument for --output."), error)) ++ return FALSE; ++ if (!check_for_optional_invalid_path(pvattest_config.perform.user_data_path, TRUE, error)) ++ return FALSE; ++ return TRUE; ++} ++ ++/************************* VERIFY OPTIONS ************************************/ ++#define verify_indent " " ++ ++static GOptionEntry verify_options[] = { ++ _entry_input(&pvattest_config.verify.input_path, "attestation result", verify_indent), ++ _entry_guest_hdr(&pvattest_config.verify.hdr_path, verify_indent), ++ _entry_att_prot_key_load(&pvattest_config.verify.arp_key_in_path, verify_indent), ++ _entry_verbose(verify_indent), ++ { NULL }, ++}; ++ ++static gboolean verify_verify(GError **error) ++{ ++ if (!check_for_invalid_path(pvattest_config.verify.input_path, TRUE, ++ _("Missing argument for --input."), error)) ++ return FALSE; ++ if (!check_for_invalid_path(pvattest_config.verify.hdr_path, TRUE, ++ _("Missing argument for --hdr."), error)) ++ return FALSE; ++ if (!check_for_invalid_path(pvattest_config.verify.arp_key_in_path, TRUE, ++ _("Missing argument for --arpk."), error)) ++ return FALSE; ++ return TRUE; ++} ++ ++/************************** OPTIONS END ***************************************/ ++ ++#pragma GCC diagnostic pop ++ ++static char summary[] = ++ "\n" ++ "Create, perform, and verify attestation measurements for IBM Secure Execution guest" ++ " systems.\n" ++ "\n" ++ "COMMANDS\n" ++ " create On a trusted system, creates an attestation request.\n" ++ " perform On the SE-guest to be attested, sends the attestation request\n" ++ " to the Ultravisor and receives the answer.\n" ++#ifndef PVATTEST_COMPILE_PERFORM ++ " (not supported on this platform)\n" ++#endif /* PVATTEST_COMPILE_PERFORM */ ++ ++ " verify On a trusted system, compares the one from your trusted system.\n" ++ " If they differ, the Secure Execution guest might not be compromised\n" ++ "\n" ++ "Use '" GETTEXT_PACKAGE " [COMMAND] -h' to get detailed help\n"; ++static char create_summary[] = ++ "Create attestation measurement requests to attest an\n" ++ "IBM Secure Execution guest. Only build attestation requests in a trusted\n" ++ "environment such as your Workstation.\n" ++ "To avoid compromising the attestation do not publish the\n" ++ "protection key and delete it after verification.\n" ++ "Every 'create' will generate a new, random protection key.\n"; ++static char perform_summary[] = ++#ifndef PVATTEST_COMPILE_PERFORM ++ "This system does NOT support 'perform'.\n" ++#endif /* PVATTEST_COMPILE_PERFORM */ ++ "Perform a measurement of this IBM Secure Execution guest using '/dev/uv'.\n"; ++static char verify_summary[] = ++ "Verify that a previously generated attestation measurement of an\n" ++ "IBM Secure Execution guest yielded the expected results.\n" ++ "Verify attestation requests only in a trusted environment, such as your workstation."; ++ ++static void print_version_and_exit(void) ++{ ++ printf("%s version %s\n", GETTEXT_PACKAGE, RELEASE_STRING); ++ printf("%s\n", COPYRIGHT_NOTICE); ++ exit(EXIT_SUCCESS); ++} ++ ++static GOptionContext *create_ctx(GOptionEntry *options, GOptionEntry *experimental_options, ++ const char *param_name, const char *opt_summary) ++{ ++ GOptionContext *ret = g_option_context_new(param_name); ++ GOptionGroup *x_group = NULL; ++ g_option_context_add_main_entries(ret, options, NULL); ++ g_option_context_set_summary(ret, opt_summary); ++ if (experimental_options) { ++ x_group = g_option_group_new( ++ "experimental", ++ "Experimental Options; Do not use in a production environment", ++ "Show experimental options", NULL, NULL); ++ g_option_group_add_entries(x_group, experimental_options); ++ g_option_context_add_group(ret, x_group); ++ } ++ return ret; ++} ++ ++enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config, ++ GError **error) ++{ ++ g_autoptr(GOptionContext) main_context = NULL, subc_context = NULL; ++ char **argv = *argvp; ++ enum pvattest_command subc = PVATTEST_SUBC_INVALID; ++ verify_options_fn_t verify_options_fn = NULL; ++ ++ pv_wrapped_g_assert(argc); ++ pv_wrapped_g_assert(argvp); ++ pv_wrapped_g_assert(config); ++ ++ /* ++ * First parse until the first non dash argument. This must be one of the commands. ++ * (strict POSIX parsing) ++ */ ++ main_context = g_option_context_new( ++ "COMMAND [OPTIONS] - create, perform, and verify attestation measurements"); ++ g_option_context_set_strict_posix(main_context, TRUE); ++ g_option_context_add_main_entries(main_context, general_options, NULL); ++ g_option_context_set_summary(main_context, summary); ++ ++ if (!g_option_context_parse(main_context, argc, argvp, error)) ++ return PVATTEST_SUBC_INVALID; ++ if (print_version) ++ print_version_and_exit(); ++ ++ /* ++ * Parse depending on the specified command ++ */ ++ else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_CREATE) == 0) { ++ subc_context = ++ create_ctx(create_options, experimental_create_options, ++ "create [OPTIONS] - create an attestation measurement request", ++ create_summary); ++ subc = PVATTEST_SUBC_CREATE; ++ verify_options_fn = &verify_create; ++ } else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_PERFORM) == 0) { ++ subc_context = ++ create_ctx(perform_options, experimental_perform_options, ++ "perform [OPTIONS] - perform an attestation measurement request", ++ perform_summary); ++ subc = PVATTEST_SUBC_PERFORM; ++ verify_options_fn = &verify_perform; ++#ifndef PVATTEST_COMPILE_PERFORM ++ g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, ++ _("This system does not support the 'perform' command.")); ++ return PVATTEST_SUBC_INVALID; ++#endif /* PVATTEST_COMPILE_PERFORM */ ++ } else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_VERIFY) == 0) { ++ subc_context = create_ctx(verify_options, NULL, ++ "verify [OPTIONS] - verify an attestation measurement", ++ verify_summary); ++ subc = PVATTEST_SUBC_VERIFY; ++ verify_options_fn = &verify_verify; ++ } else { ++ if (argv[1]) ++ g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV, ++ _("Invalid command specified: %s."), argv[1]); ++ else ++ g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV, ++ _("No command specified.")); ++ return PVATTEST_SUBC_INVALID; ++ } ++ g_assert(verify_options_fn); ++ ++ if (!g_option_context_parse(subc_context, argc, argvp, error)) ++ return PVATTEST_SUBC_INVALID; ++ ++ if (!verify_options_fn(error)) ++ return PVATTEST_SUBC_INVALID; ++ ++ *config = &pvattest_config; ++ return subc; ++} ++ ++static void pvattest_parse_clear_create_config(pvattest_create_config_t *config) ++{ ++ if (!config) ++ return; ++ g_strfreev(config->host_key_document_paths); ++ g_strfreev(config->certificate_paths); ++ g_free(config->arp_key_out_path); ++ g_free(config->output_path); ++} ++ ++static void pvattest_parse_clear_perform_config(pvattest_perform_config_t *config) ++{ ++ if (!config) ++ return; ++ g_free(config->input_path); ++ g_free(config->output_path); ++} ++ ++static void pvattest_parse_clear_verify_config(pvattest_verify_config_t *config) ++{ ++ if (!config) ++ return; ++ g_free(config->input_path); ++ g_free(config->hdr_path); ++ g_free(config->arp_key_in_path); ++} ++ ++void pvattest_parse_clear_config(pvattest_config_t *config) ++{ ++ if (!config) ++ return; ++ pvattest_parse_clear_create_config(&config->create); ++ pvattest_parse_clear_perform_config(&config->perform); ++ pvattest_parse_clear_verify_config(&config->verify); ++} +--- /dev/null ++++ b/pvattest/src/argparse.h +@@ -0,0 +1,106 @@ ++/* ++ * Definitions used for parsing arguments. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_ARGPARSE_H ++#define PVATTEST_ARGPARSE_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++ ++#include "libpv/glib-helper.h" ++#include "libpv/macros.h" ++ ++#define PVATTEST_SUBC_STR_CREATE "create" ++#define PVATTEST_SUBC_STR_PERFORM "perform" ++#define PVATTEST_SUBC_STR_VERIFY "verify" ++ ++enum pvattest_command { ++ PVATTEST_SUBC_INVALID, ++ PVATTEST_SUBC_CREATE, ++ PVATTEST_SUBC_PERFORM, ++ PVATTEST_SUBC_VERIFY, ++}; ++ ++typedef struct { ++ int log_level; ++} pvattest_general_config_t; ++ ++typedef struct { ++ char **host_key_document_paths; ++ char **certificate_paths; ++ char **crl_paths; ++ char *root_ca_path; ++ ++ char *arp_key_out_path; ++ char *output_path; ++ ++ gboolean phkh_img; ++ gboolean phkh_att; ++ gboolean no_verify; ++ gboolean online; ++ ++ /* experimental flags */ ++ gboolean use_nonce; /* default TRUE */ ++ uint64_t paf; /* default 0 */ ++ int x_aad_size; /* default -1 -> ignore */ ++} pvattest_create_config_t; ++ ++typedef struct { ++ char *output_path; ++ char *input_path; ++ /* experimental flags */ ++ char *user_data_path; /* default NULL */ ++} pvattest_perform_config_t; ++ ++typedef struct { ++ char *input_path; ++ char *hdr_path; ++ char *arp_key_in_path; ++} pvattest_verify_config_t; ++ ++typedef struct { ++ pvattest_general_config_t general; ++ pvattest_create_config_t create; ++ pvattest_perform_config_t perform; ++ pvattest_verify_config_t verify; ++} pvattest_config_t; ++ ++/** ++ * pvattest_parse_clear_config: ++ * ++ * @config: struct to be cleared ++ * ++ * clears but not frees all config. ++ * all non config members such like char* will be freed. ++ */ ++void pvattest_parse_clear_config(pvattest_config_t *config); ++ ++/** ++ * pvattest_parse: ++ * ++ * @argc: ptr to argument count ++ * @argv: ptr to argument vector ++ * @config: output: ptr to parsed config. Target is statically allocated. ++ * You are responsible for freeing all non config ptrs. ++ * use #pvattest_parse_clear_config for that. ++ * ++ * Will not return if verbose or help parsed. ++ * ++ * Returns: selected command as enum ++ */ ++enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config, ++ GError **error) PV_NONNULL(1, 2, 3); ++ ++#define PVATTEST_ERROR g_quark_from_static_string("pv-pvattest_error-quark") ++typedef enum { ++ PVATTEST_ERR_INV_ARGV, ++ PVATTEST_ERR_INV_ARG, ++} pv_pvattest_error_e; ++ ++#endif /* PVATTEST_ARGPARSE_H */ +--- /dev/null ++++ b/pvattest/src/attestation.c +@@ -0,0 +1,148 @@ ++/* ++ * Attestation related functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/cert.h" ++#include "libpv/hash.h" ++#include "libpv/se-hdr.h" ++ ++#include "exchange_format.h" ++#include "attestation.h" ++ ++G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->pld) == sizeof(((struct pv_hdr_head *)0)->pld)); ++G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->ald) == sizeof(((struct pv_hdr_head *)0)->ald)); ++G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tld) == sizeof(((struct pv_hdr_head *)0)->tld)); ++G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tag) == sizeof(((struct pv_hdr *)0)->tag)); ++ ++struct att_meas_sizes { ++ uint16_t user_data_len; ++ uint16_t zeros; ++ uint32_t additional_data_len; ++} __packed; ++G_STATIC_ASSERT(sizeof(struct att_meas_sizes) == 8); ++ ++/* ++ * All optional arguments may be NULL ++ * user_data is up to 256 bytes long, or NULL. ++ * nonce is 16 bytes long or NULL. ++ * additional_data is up to 32768 bytes long or NULL. ++ */ ++GBytes *att_gen_measurement_hmac_sha512(const att_meas_ctx_t *meas_ctx, GBytes *measurement_key, ++ GBytes *optional_user_data, GBytes *optional_nonce, ++ GBytes *optional_additional_data, GError **error) ++{ ++ struct att_meas_sizes meas_sizes = {}; ++ g_autoptr(HMAC_CTX) hmac_ctx = NULL; ++ size_t additional_data_size = 0; ++ size_t user_data_size = 0; ++ size_t nonce_size = 0; ++ ++ pv_wrapped_g_assert(meas_ctx); ++ pv_wrapped_g_assert(measurement_key); ++ ++ if (optional_user_data) ++ user_data_size = g_bytes_get_size(optional_user_data); ++ if (optional_additional_data) ++ additional_data_size = g_bytes_get_size(optional_additional_data); ++ if (optional_nonce) ++ nonce_size = g_bytes_get_size(optional_nonce); ++ ++ /* checks for these sizes resulting in GErrors are done before */ ++ g_assert(user_data_size <= PVATTEST_USER_DATA_MAX_SIZE); ++ g_assert(additional_data_size <= PVATTEST_ADDITIONAL_MAX_SIZE); ++ g_assert(nonce_size == 0 || nonce_size == ARCB_V1_NONCE_SIZE); ++ ++ pv_wrapped_g_assert(meas_ctx); ++ pv_wrapped_g_assert(measurement_key); ++ ++ hmac_ctx = pv_hmac_ctx_new(measurement_key, EVP_sha512(), error); ++ if (!hmac_ctx) ++ return NULL; ++ ++ meas_sizes.user_data_len = GUINT16_TO_BE((uint16_t)user_data_size); ++ meas_sizes.zeros = 0; ++ meas_sizes.additional_data_len = GUINT32_TO_BE((uint32_t)additional_data_size); ++ ++ if (pv_hmac_ctx_update_raw(hmac_ctx, meas_ctx, sizeof(*meas_ctx), error) != 0) ++ return NULL; ++ ++ /* add the sizes of user and additional data. */ ++ if (pv_hmac_ctx_update_raw(hmac_ctx, &meas_sizes, sizeof(meas_sizes), error)) ++ return NULL; ++ ++ /* update optional data. if NULL passed (or size = 0) nothing will happen to the HMAC_CTX */ ++ if (pv_hmac_ctx_update(hmac_ctx, optional_user_data, error) != 0) ++ return NULL; ++ if (pv_hmac_ctx_update(hmac_ctx, optional_nonce, error) != 0) ++ return NULL; ++ if (pv_hmac_ctx_update(hmac_ctx, optional_additional_data, error) != 0) ++ return NULL; ++ return pv_hamc_ctx_finalize(hmac_ctx, error); ++} ++ ++att_meas_ctx_t *att_extract_from_hdr(GBytes *se_hdr, GError **error) ++{ ++ g_autofree att_meas_ctx_t *meas = NULL; ++ const struct pv_hdr *hdr = NULL; ++ size_t se_hdr_tag_offset; ++ size_t se_hdr_size; ++ uint8_t *hdr_u8; ++ ++ pv_wrapped_g_assert(se_hdr); ++ ++ hdr = g_bytes_get_data(se_hdr, &se_hdr_size); ++ hdr_u8 = (uint8_t *)hdr; ++ ++ if (se_hdr_size < PV_V1_PV_HDR_MIN_SIZE) { ++ g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR, ++ _("Invalid SE header provided.")); ++ return NULL; ++ } ++ ++ if (GUINT32_FROM_BE(hdr->head.phs) != se_hdr_size || ++ GUINT64_FROM_BE(hdr->head.magic) != PV_MAGIC_NUMBER) { ++ g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR, ++ _("Invalid SE header provided.")); ++ return NULL; ++ } ++ ++ se_hdr_tag_offset = GUINT32_FROM_BE(hdr->head.phs) - sizeof(hdr->tag); ++ meas = g_new0(att_meas_ctx_t, 1); ++ ++ memcpy(meas->pld, hdr->head.pld, sizeof(meas->pld)); ++ memcpy(meas->ald, hdr->head.ald, sizeof(meas->ald)); ++ memcpy(meas->tld, hdr->head.tld, sizeof(meas->tld)); ++ memcpy(meas->tag, hdr_u8 + se_hdr_tag_offset, sizeof(meas->tag)); ++ ++ return g_steal_pointer(&meas); ++} ++ ++void att_add_uid(att_meas_ctx_t *meas_ctx, GBytes *config_uid) ++{ ++ pv_wrapped_g_assert(meas_ctx); ++ pv_wrapped_g_assert(config_uid); ++ ++ g_assert(g_bytes_get_size(config_uid) == ATT_CONFIG_UID_SIZE); ++ pv_gbytes_memcpy(meas_ctx->config_uid, ATT_CONFIG_UID_SIZE, config_uid); ++} ++ ++gboolean att_verify_measurement(const GBytes *calculated_measurement, ++ const GBytes *uvio_measurement, GError **error) ++{ ++ pv_wrapped_g_assert(calculated_measurement); ++ pv_wrapped_g_assert(uvio_measurement); ++ ++ if (g_bytes_compare(calculated_measurement, uvio_measurement) != 0) { ++ g_set_error(error, ATT_ERROR, ATT_ERR_MEASUREMENT_VERIFICATION_FAILED, ++ _("Calculated and received attestation measurement are not equal.")); ++ return FALSE; ++ } ++ return TRUE; ++} +--- /dev/null ++++ b/pvattest/src/attestation.h +@@ -0,0 +1,99 @@ ++/* ++ * Attestation related functions and definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_ATTESTATION_H ++#define PVATTEST_ATTESTATION_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++ ++#include "libpv/glib-helper.h" ++#include "libpv/crypto.h" ++#include "libpv/cert.h" ++ ++#include "common.h" ++#include "types.h" ++#include "arcb.h" ++ ++#define ATT_CONFIG_UID_SIZE 16 ++ ++typedef struct { ++ uint8_t pld[SHA512_DIGEST_LENGTH]; ++ uint8_t ald[SHA512_DIGEST_LENGTH]; ++ uint8_t tld[SHA512_DIGEST_LENGTH]; ++ uint8_t tag[AES_256_GCM_TAG_SIZE]; ++ uint8_t config_uid[ATT_CONFIG_UID_SIZE]; ++} __packed att_meas_ctx_t; ++G_STATIC_ASSERT(sizeof(att_meas_ctx_t) == 224); ++ ++/** ++ * att_gen_measurement_hmac_sha512: ++ * ++ * @meas_ctx: measurement context. ++ * @measurement_key: AES-256-GCM key for generating the measurement calculation. ++ * @optional_user_data: NULL or up to 256 bytes GBytes. ++ * @optional_nonce: NULL or a nonce of exactly `ARCB_V1_NONCE_SIZE` bytes ++ * @optional_additional_data: NULL or up to 0x8000 bytes of GBytes. ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Calculates the measurement value. ++ * If the input data is the same which the UV used in the Retrieve Attestation Measurement ++ * the result should be identical to the data in the ´Measurement Data Address´ UVC. ++ * ++ * Returns: (nullable) (transfer full): a hmac_sha512 of the given data. ++ */ ++GBytes *att_gen_measurement_hmac_sha512(const att_meas_ctx_t *meas_ctx, GBytes *measurement_key, ++ GBytes *optional_user_data, GBytes *optional_nonce, ++ GBytes *optional_additional_data, GError **error) ++ PV_NONNULL(1, 2); ++ ++/** ++ * att_extract_from_hdr: ++ * ++ * @se_hdr: binary SE guest header. ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Verifies that SE header size and magic ,but no cryptographical verification. ++ * Then, find and extracts pld, ald, tld, and SE tagi and adds it to the context. ++ * ++ * Returns: new attestation measurement context. ++ */ ++att_meas_ctx_t *att_extract_from_hdr(GBytes *se_hdr, GError **error) PV_NONNULL(1); ++ ++/** att_add_uid: ++ * ++ * @meas_ctx: measurement context. ++ * @config_uid: pointer to config UID. Must be `ATT_CONFIG_UID_SIZE` bytes long. ++ * ++ * Copies the config UID to the measurement context. ++ * Wrong size is considered as a Programming error. ++ */ ++void att_add_uid(att_meas_ctx_t *meas_ctx, GBytes *config_uid) PV_NONNULL(1, 2); ++ ++/** att_verify_measurement: ++ * ++ * @calculated_measurement: measurement calculated by a trusted system ++ * @uvio_measurement: measurement generated by an UV ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Returns: TRUE if measurements are identical, otherwise FALSE ++ */ ++gboolean att_verify_measurement(const GBytes *calculated_measurement, ++ const GBytes *uvio_measurement, GError **error) PV_NONNULL(1, 2); ++ ++#define ATT_ERROR g_quark_from_static_string("pv-att_error-quark") ++typedef enum att_error { ++ ATT_ERR_INVALID_HDR, ++ ATT_ERR_INVALID_USER_DATA, ++ ATT_ERR_MEASUREMENT_VERIFICATION_FAILED, ++ ATT_ERR_PHKH_NO_FIT_IN_USER_DATA, ++ ATT_ERR_PHKH_NO_MATCH, ++} att_error_e; ++ ++#endif /* PVATTEST_ATTESTATION_H */ +--- /dev/null ++++ b/pvattest/src/common.c +@@ -0,0 +1,47 @@ ++/* ++ * Common functions for pvattest. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "libpv/glib-helper.h" ++ ++#include "types.h" ++#include "common.h" ++ ++gboolean wrapped_g_file_set_content(const char *filename, GBytes *bytes, mode_t mode, ++ GError **error) ++{ ++ const void *data; ++ size_t size; ++ gboolean rc; ++ ++ data = g_bytes_get_data(bytes, &size); ++ rc = g_file_set_contents(filename, data, (ssize_t)size, error); ++ if (rc && mode != 0666) ++ chmod(filename, mode); ++ return rc; ++} ++ ++GBytes *secure_gbytes_concat(GBytes *lh, GBytes *rh) ++{ ++ g_autoptr(GByteArray) lha = NULL; ++ ++ if (!lh && !rh) ++ return NULL; ++ if (!lh) ++ return g_bytes_ref(rh); ++ if (!rh) ++ return g_bytes_ref(lh); ++ lha = g_bytes_unref_to_array(g_bytes_ref(lh)); ++ g_byte_array_append(lha, g_bytes_get_data(rh, NULL), (guint)g_bytes_get_size(rh)); ++ return pv_sec_gbytes_new(lha->data, lha->len); ++} +--- /dev/null ++++ b/pvattest/src/common.h +@@ -0,0 +1,37 @@ ++/* ++ * Common functions for pvattest. ++ * ++ * IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_COMMON_H ++#define PVATTEST_COMMON_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "libpv/glib-helper.h" ++#include "libpv/macros.h" ++#include "lib/zt_common.h" ++ ++#include "types.h" ++ ++#define COPYRIGHT_NOTICE "Copyright IBM Corp. 2022" ++ ++#define AES_256_GCM_TAG_SIZE 16 ++ ++gboolean wrapped_g_file_set_content(const char *filename, GBytes *bytes, mode_t mode, ++ GError **error); ++ ++/** ++ * just ref's up if one of them is NULL. ++ * If both NULL returns NULL. ++ * Otherwise returns lh ++ rh ++ */ ++GBytes *secure_gbytes_concat(GBytes *lh, GBytes *rh); ++ ++#endif /* PVATTEST_COMMON_H */ +--- /dev/null ++++ b/pvattest/src/config.h +@@ -0,0 +1,31 @@ ++/* ++ * Config file. ++ * Must be include before any other header. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ * ++ */ ++#ifndef PVATTEST_CONFIG_H ++#define PVATTEST_CONFIG_H ++#define GETTEXT_PACKAGE "pvattest" ++ ++#ifdef __GNUC__ ++#ifdef __s390x__ ++#ifndef PVATTEST_NO_PERFORM ++#define PVATTEST_COMPILE_PERFORM ++#endif ++#endif ++#endif ++ ++#ifdef __clang__ ++#ifdef __zarch__ ++#ifndef PVATTEST_NO_PERFORM ++#define PVATTEST_COMPILE_PERFORM ++#endif ++#endif ++#endif ++ ++#endif /* PVATTEST_CONFIG_H */ +--- /dev/null ++++ b/pvattest/src/exchange_format.c +@@ -0,0 +1,480 @@ ++/* ++ * Functions for the pvattest exchange format to send attestation requests and responses between ++ * machines . ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "common.h" ++ ++#include "exchange_format.h" ++#include "log.h" ++ ++struct exchange_shared_hdr { ++ be64_t magic; ++ be32_t version; ++ be32_t size; ++} __packed; ++ ++/* ++ * If size == 0 ++ * offset ignored. ++ * (part does not exist) ++ * if offset >0 and <0x50 -> invalid format ++ * if offset == 0 and size > 0 no data saved, however the request will need this amount of memory to ++ * succeed. ++ * Only makes sense for measurement and additional data. This however, is not enforced. ++ */ ++struct entry { ++ be32_t size; ++ be32_t offset; ++} __packed; ++G_STATIC_ASSERT(sizeof(struct entry) == 8); ++ ++struct _exchange_format_v1_hdr { ++ be64_t magic; ++ be32_t version; ++ be32_t size; ++ uint64_t reserved; ++ struct entry serialized_arcb; ++ struct entry measurement; ++ struct entry additional_data; ++ struct entry user_data; ++ struct entry config_uid; ++} __packed; ++G_STATIC_ASSERT(sizeof(exchange_format_v1_hdr_t) == 0x40); ++ ++struct _exchange_format_ctx { ++ uint32_t version; ++ uint32_t req_meas_size; ++ uint32_t req_add_size; ++ GBytes *serialized_arcb; ++ GBytes *measurement; ++ GBytes *additional_data; ++ GBytes *user_data; ++ GBytes *config_uid; ++}; ++ ++/* Use a byte array to avoid any byteorder issues while checking */ ++ ++#define PVATTEST_EXCHANGE_MAGIC 0x7076617474657374 /* pvattest */ ++static const uint8_t exchange_magic[] = { 0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74 }; ++ ++exchange_format_ctx_t *exchange_ctx_new(uint32_t version, GBytes *serialized_arcb, ++ uint32_t req_measurement_size, uint32_t req_additional_size, ++ GError **error) ++{ ++ g_autoptr(exchange_format_ctx_t) ctx = NULL; ++ ++ pv_wrapped_g_assert(serialized_arcb); ++ ++ if (version != PVATTEST_EXCHANGE_VERSION_1_00) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_UNSUPPORTED_VERSION, ++ _("'%d' unsupported version."), version); ++ return NULL; ++ } ++ ++ ctx = g_malloc0(sizeof(*ctx)); ++ ctx->version = version; ++ ++ exchange_set_serialized_arcb(ctx, serialized_arcb); ++ ctx->req_meas_size = req_measurement_size; ++ ctx->req_add_size = req_additional_size; ++ ++ return g_steal_pointer(&ctx); ++} ++ ++static GBytes *get_content(GBytes *file_content, const struct entry *entry, const size_t max_size, ++ GError **error) ++{ ++ uint64_t size = GUINT32_FROM_BE(entry->size); ++ uint64_t offset = GUINT32_FROM_BE(entry->offset); ++ size_t file_size = 0; ++ const uint8_t *file_content_u8 = g_bytes_get_data(file_content, &file_size); ++ ++ if (size == 0 || offset == 0) ++ return NULL; ++ ++ if (offset < sizeof(exchange_format_v1_hdr_t) || offset + size > file_size || ++ size > max_size) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ _("Input file is not in a valid format.")); ++ return NULL; ++ } ++ return g_bytes_new(file_content_u8 + offset, size); ++} ++ ++static gboolean check_format(const struct exchange_shared_hdr *hdr) ++{ ++ if (memcmp(exchange_magic, &hdr->magic, sizeof(exchange_magic)) == 0) ++ return TRUE; ++ return FALSE; ++} ++ ++exchange_format_ctx_t *exchange_ctx_from_file(const char *filename, GError **error) ++{ ++ g_autoptr(exchange_format_ctx_t) ctx = g_malloc0(sizeof(*ctx)); ++ const struct exchange_shared_hdr *hdr = NULL; ++ const exchange_format_v1_hdr_t *hdr_v1 = NULL; ++ g_autoptr(GBytes) file_content = NULL; ++ size_t config_uid_size = 0; ++ size_t file_size; ++ ++ pv_wrapped_g_assert(filename); ++ ++ file_content = pv_file_get_content_as_g_bytes(filename, error); ++ if (!file_content) ++ return NULL; ++ hdr = (const struct exchange_shared_hdr *)g_bytes_get_data(file_content, &file_size); ++ ++ if (file_size < sizeof(*hdr) || !check_format(hdr)) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ _("'%s' is not in a valid format."), filename); ++ return NULL; ++ } ++ ++ if (GUINT32_FROM_BE(hdr->version) != PVATTEST_EXCHANGE_VERSION_1_00) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ _("The version (%#x) of '%s' is not supported"), ++ GUINT32_FROM_BE(hdr->version), filename); ++ return NULL; ++ } ++ ++ /* get the header */ ++ if (file_size < sizeof(exchange_format_v1_hdr_t)) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ _("'%s' is not in a valid format."), filename); ++ return NULL; ++ } ++ hdr_v1 = (const exchange_format_v1_hdr_t *)hdr; ++ ++ /* get entries if present */ ++ ctx->serialized_arcb = ++ get_content(file_content, &hdr_v1->serialized_arcb, PVATTEST_ARCB_MAX_SIZE, error); ++ if (*error) ++ return NULL; ++ ctx->measurement = get_content(file_content, &hdr_v1->measurement, ++ PVATTEST_MEASUREMENT_MAX_SIZE, error); ++ if (*error) ++ return NULL; ++ ctx->additional_data = get_content(file_content, &hdr_v1->additional_data, ++ PVATTEST_ADDITIONAL_MAX_SIZE, error); ++ if (*error) ++ return NULL; ++ ctx->user_data = ++ get_content(file_content, &hdr_v1->user_data, PVATTEST_USER_DATA_MAX_SIZE, error); ++ if (*error) ++ return NULL; ++ ctx->config_uid = get_content(file_content, &hdr_v1->config_uid, PVATTEST_UID_SIZE, error); ++ if (*error) ++ return NULL; ++ ++ if (ctx->config_uid) ++ config_uid_size = g_bytes_get_size(ctx->config_uid); ++ ++ if (config_uid_size != PVATTEST_UID_SIZE && config_uid_size != 0) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ _("'%s' is not in a valid format."), filename); ++ return NULL; ++ } ++ ctx->req_meas_size = GUINT32_FROM_BE(hdr_v1->measurement.size); ++ ctx->req_add_size = GUINT32_FROM_BE(hdr_v1->additional_data.size); ++ ctx->version = GUINT32_TO_BE(hdr->version); ++ ++ return g_steal_pointer(&ctx); ++} ++ ++void clear_free_exchange_ctx(exchange_format_ctx_t *ctx) ++{ ++ if (!ctx) ++ return; ++ ++ if (ctx->serialized_arcb) ++ g_bytes_unref(ctx->serialized_arcb); ++ if (ctx->measurement) ++ g_bytes_unref(ctx->measurement); ++ if (ctx->additional_data) ++ g_bytes_unref(ctx->additional_data); ++ if (ctx->user_data) ++ g_bytes_unref(ctx->user_data); ++ if (ctx->config_uid) ++ g_bytes_unref(ctx->config_uid); ++ ++ g_free(ctx); ++} ++ ++void exchange_set_serialized_arcb(exchange_format_ctx_t *ctx, GBytes *serialized_arcb) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(serialized_arcb); ++ ++ g_bytes_ref(serialized_arcb); ++ g_bytes_unref(ctx->serialized_arcb); ++ ctx->serialized_arcb = serialized_arcb; ++} ++ ++void exchange_set_measurement(exchange_format_ctx_t *ctx, GBytes *measurement) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(measurement); ++ ++ g_bytes_ref(measurement); ++ g_bytes_unref(ctx->measurement); ++ ctx->measurement = measurement; ++} ++ ++void exchange_set_additional_data(exchange_format_ctx_t *ctx, GBytes *additional_data) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(additional_data); ++ ++ g_bytes_ref(additional_data); ++ g_bytes_unref(ctx->additional_data); ++ ctx->additional_data = additional_data; ++} ++ ++void exchange_set_user_data(exchange_format_ctx_t *ctx, GBytes *user_data) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(user_data); ++ ++ g_bytes_ref(user_data); ++ g_bytes_unref(ctx->user_data); ++ ctx->user_data = user_data; ++} ++ ++void exchange_set_config_uid(exchange_format_ctx_t *ctx, GBytes *config_uid) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(config_uid); ++ ++ g_bytes_ref(config_uid); ++ g_bytes_unref(ctx->config_uid); ++ ctx->config_uid = config_uid; ++} ++ ++static GBytes *gbytes_ref0(GBytes *bytes) ++{ ++ if (!bytes) ++ return NULL; ++ return g_bytes_ref(bytes); ++} ++ ++GBytes *exchange_get_serialized_arcb(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return gbytes_ref0(ctx->serialized_arcb); ++} ++ ++GBytes *exchange_get_measurement(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return gbytes_ref0(ctx->measurement); ++} ++ ++GBytes *exchange_get_additional_data(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return gbytes_ref0(ctx->additional_data); ++} ++ ++GBytes *exchange_get_user_data(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return gbytes_ref0(ctx->user_data); ++} ++ ++GBytes *exchange_get_config_uid(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return gbytes_ref0(ctx->config_uid); ++} ++ ++uint32_t exchange_get_requested_measurement_size(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return ctx->req_meas_size; ++} ++ ++uint32_t exchange_get_requested_additional_data_size(const exchange_format_ctx_t *ctx) ++{ ++ pv_wrapped_g_assert(ctx); ++ ++ return ctx->req_add_size; ++} ++ ++static struct entry add_g_bytes(GBytes *bytes, FILE *file, GError **error) ++{ ++ struct entry result = {}; ++ long offset; ++ size_t size; ++ const void *data = g_bytes_get_data(bytes, &size); ++ ++ g_assert(size <= G_MAXUINT32); ++ ++ offset = pv_file_tell(file, error); ++ g_assert(offset <= G_MAXUINT32); ++ if (offset < 0) ++ return result; ++ ++ result.offset = GUINT32_TO_BE((uint32_t)offset); ++ result.size = GUINT32_TO_BE((uint32_t)size); ++ pv_file_write(file, data, size, error); ++ return result; ++} ++ ++int exchange_write_to_file(const exchange_format_ctx_t *ctx, const char *filename, GError **error) ++{ ++ exchange_format_v1_hdr_t hdr = { ++ .magic = GUINT64_TO_BE(PVATTEST_EXCHANGE_MAGIC), ++ .version = GUINT32_TO_BE(ctx->version), ++ }; ++ size_t file_size = sizeof(hdr); ++ g_autoptr(FILE) file = NULL; ++ struct stat file_stat; ++ long actual_file_size; ++ size_t tmp_size; ++ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(filename); ++ ++ file = pv_file_open(filename, "w", error); ++ if (!file) ++ return -1; ++ ++ if (fstat(fileno(file), &file_stat) != 0 || !S_ISREG(file_stat.st_mode)) { ++ g_set_error(error, EXCHANGE_FORMAT_ERROR, ++ EXCHANGE_FORMAT_ERROR_UNSUPPORTED_FILE_TYPE, ++ "Only regular files are supported: '%s'", filename); ++ return -1; ++ } ++ ++ if (pv_file_seek(file, sizeof(exchange_format_v1_hdr_t), SEEK_SET, error)) ++ return -1; ++ ++ if (ctx->serialized_arcb) { ++ hdr.serialized_arcb = add_g_bytes(ctx->serialized_arcb, file, error); ++ if (*error) ++ return -1; ++ file_size += g_bytes_get_size(ctx->serialized_arcb); ++ } ++ if (ctx->measurement) { ++ hdr.measurement = add_g_bytes(ctx->measurement, file, error); ++ if (*error) ++ return -1; ++ file_size += g_bytes_get_size(ctx->measurement); ++ } else { ++ hdr.measurement.size = GUINT32_TO_BE(ctx->req_meas_size); ++ } ++ ++ if (ctx->additional_data) { ++ hdr.additional_data = add_g_bytes(ctx->additional_data, file, error); ++ if (*error) ++ return -1; ++ file_size += g_bytes_get_size(ctx->additional_data); ++ } else { ++ hdr.additional_data.size = GUINT32_TO_BE(ctx->req_add_size); ++ } ++ ++ if (ctx->user_data) { ++ tmp_size = g_bytes_get_size(ctx->user_data); ++ g_assert(tmp_size <= PVATTEST_USER_DATA_MAX_SIZE); ++ tmp_size = MIN(tmp_size, PVATTEST_USER_DATA_MAX_SIZE); /* should be a noop */ ++ hdr.user_data = add_g_bytes(ctx->user_data, file, error); ++ if (*error) ++ return -1; ++ file_size += g_bytes_get_size(ctx->user_data); ++ } ++ if (ctx->config_uid) { ++ tmp_size = g_bytes_get_size(ctx->config_uid); ++ g_assert(tmp_size == PVATTEST_UID_SIZE); ++ tmp_size = MIN(tmp_size, PVATTEST_UID_SIZE); /* should be a noop */ ++ hdr.config_uid = add_g_bytes(ctx->config_uid, file, error); ++ if (*error) ++ return -1; ++ file_size += g_bytes_get_size(ctx->config_uid); ++ } ++ ++ /* ++ * This case should never happen. It could be seen as a programming error as: ++ * ARCB is restricted by kernel (and this tool) to be max 1M, Additional+meas to max 8pages ++ * userdata to 256B and config uid to 16b this is way less than 4G. ++ * ++ * However, lets be conservative and trow an error instead of an assertion. ++ */ ++ if (file_size > UINT32_MAX) { ++ g_set_error( ++ error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ "The exchange file format cannot handle this much data in one blob. (%#lx bytes)", ++ file_size); ++ return -1; ++ } ++ hdr.size = GUINT32_TO_BE((uint32_t)file_size); ++ if (pv_file_seek(file, 0, SEEK_SET, error) != 0) ++ return -1; ++ if (sizeof(hdr) != pv_file_write(file, &hdr, sizeof(hdr), error)) ++ return -1; ++ if (pv_file_seek(file, 0, SEEK_END, error) != 0) ++ return -1; ++ actual_file_size = pv_file_tell(file, error); ++ if (actual_file_size < 0) ++ return -1; ++ if (actual_file_size != (uint32_t)file_size) { ++ g_set_error( ++ error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_WRITE, ++ "The exchange file size doesn't match the expectations: %ld bytes != %lu bytes", ++ actual_file_size, file_size); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static void print_entry(const char *name, GBytes *data, const gboolean print_data, FILE *stream) ++{ ++ if (!data) ++ return; ++ fprintf(stream, _("%s (%#lx bytes)"), name, g_bytes_get_size(data)); ++ if (print_data) { ++ fprintf(stream, ":\n"); ++ printf_hexdump(g_bytes_get_data(data, NULL), g_bytes_get_size(data), 16, " ", ++ stream); ++ } ++ fprintf(stream, "\n"); ++} ++ ++void exchange_info_print(const exchange_format_ctx_t *ctx, const gboolean print_data, FILE *stream) ++{ ++ pv_wrapped_g_assert(ctx); ++ pv_wrapped_g_assert(stream); ++ ++ fprintf(stream, _("Version: %#x\n"), ctx->version); ++ fprintf(stream, _("Sections:\n")); ++ print_entry(_(" ARCB"), ctx->serialized_arcb, print_data, stream); ++ print_entry(_(" Measurement"), ctx->measurement, print_data, stream); ++ print_entry(_(" Additional Data"), ctx->additional_data, print_data, stream); ++ print_entry(_(" User Data"), ctx->user_data, print_data, stream); ++ print_entry(_(" Config UID"), ctx->config_uid, print_data, stream); ++ if (!ctx->measurement) ++ fprintf(stream, _("Required measurement size: %#x\n"), ctx->req_meas_size); ++ if (!ctx->additional_data) ++ fprintf(stream, _("Required additional data size: %#x\n"), ctx->req_add_size); ++} +--- /dev/null ++++ b/pvattest/src/exchange_format.h +@@ -0,0 +1,166 @@ ++/* ++ * Definitions for the pvattest exchange format to send attestation requests and responses between ++ * machines. The "exchange format" is a simple file format to send labeled binary blobs between ++ * pvattest instances on different machines. All sizes, etc are in big endian. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_EXCHANGE_FORMAT_H ++#define PVATTEST_EXCHANGE_FORMAT_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/glib-helper.h" ++ ++#include "types.h" ++#include "common.h" ++ ++/* Similar to linux/arch/s390x/include/uapi/uvdevice.h as this part needs to be ++ * architecture independent. ++ */ ++#define PVATTEST_UID_SIZE 0x10UL ++#define PVATTEST_USER_DATA_MAX_SIZE 0x100UL ++#define PVATTEST_ARCB_MAX_SIZE 0x100000 ++#define PVATTEST_MEASUREMENT_MAX_SIZE 0x8000 ++#define PVATTEST_ADDITIONAL_MAX_SIZE 0x8000 ++ ++#define PVATTEST_EXCHANGE_V_INVALID 0 ++#define PVATTEST_EXCHANGE_VERSION_1_00 0x0100 ++ ++typedef struct _exchange_format_v1_hdr exchange_format_v1_hdr_t; ++typedef struct _exchange_format_ctx exchange_format_ctx_t; ++ ++/** ++ * exchange_ctx_new: ++ * ++ * @version: Format version. Currently, only version 1 supported. ++ * @serialized_arcb: ARCB as #GBytes ++ * @req_measurement_size: Measurement size the given ARCB needs. ++ * @req_measurement_size: Additional Data size the given ARCB needs. ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Returns: (nullable) (transfer full): new, empty exchange format context ++ * ++ */ ++exchange_format_ctx_t *exchange_ctx_new(const uint32_t version, GBytes *serialized_arcb, ++ const uint32_t req_measurement_size, ++ const uint32_t req_additional_size, GError **error) ++ PV_NONNULL(2); ++ ++/** ++ * exchange_ctx_from_file: ++ * ++ * @filename: name of the file to be loaded ++ * @error: GError. *error will != NULL if error occurs. ++ * ++ * Loads all blobs from file and caches them in the context structure. ++ * ++ * Returns: (nullable) (transfer full): exchange format context filled with data from file ++ * ++ */ ++exchange_format_ctx_t *exchange_ctx_from_file(const char *filename, GError **error) PV_NONNULL(1); ++void clear_free_exchange_ctx(exchange_format_ctx_t *ctx); ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(exchange_format_ctx_t, clear_free_exchange_ctx) ++ ++/** ++ * exchange_set_serialized_arcb: ++ * ++ * @ctx: exchange format context ++ * @serialized_arcb: blob to add. ++ * ++ * Adds blob to the exchange format. Unreferences old data if already set. ++ */ ++void exchange_set_serialized_arcb(exchange_format_ctx_t *ctx, GBytes *serialized_arcb) ++ PV_NONNULL(1, 2); ++ ++/** ++ * exchange_set_measurement: ++ * ++ * @ctx: exchange format context ++ * @measurement: blob to add. ++ * ++ * Adds blob to the exchange format. Unreferences old data if already set. ++ */ ++void exchange_set_measurement(exchange_format_ctx_t *ctx, GBytes *measurement) PV_NONNULL(1, 2); ++ ++/** ++ * exchange_set_additional_data: ++ * ++ * @ctx: exchange format context ++ * @additional_data: blob to add. ++ * ++ * Adds blob to the exchange format. Unreferences old data if already set. ++ */ ++void exchange_set_additional_data(exchange_format_ctx_t *ctx, GBytes *additional_data) ++ PV_NONNULL(1, 2); ++ ++/** ++ * exchange_set_user_data: ++ * ++ * @ctx: exchange format context ++ * @user_data: blob to add. ++ * ++ * Adds blob to the exchange format. Unreferences old data if already set. ++ */ ++void exchange_set_user_data(exchange_format_ctx_t *ctx, GBytes *user_data) PV_NONNULL(1, 2); ++ ++/** ++ * exchange_set_config_uid: ++ * ++ * @ctx: exchange format context ++ * @config_uid: blob to add. ++ * ++ * Adds blob to the exchange format. Unreferences old data if already set. ++ */ ++void exchange_set_config_uid(exchange_format_ctx_t *ctx, GBytes *config_uid) PV_NONNULL(1, 2); ++ ++GBytes *exchange_get_serialized_arcb(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++GBytes *exchange_get_measurement(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++GBytes *exchange_get_additional_data(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++GBytes *exchange_get_user_data(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++GBytes *exchange_get_config_uid(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++ ++uint32_t exchange_get_requested_measurement_size(const exchange_format_ctx_t *ctx) PV_NONNULL(1); ++uint32_t exchange_get_requested_additional_data_size(const exchange_format_ctx_t *ctx) ++ PV_NONNULL(1); ++ ++/** ++ * exchange_write_to_file: ++ * ++ * @ctx: exchange format context ++ * @filename: name of the file to be loaded ++ * @error: GError. *error will != NULL if error occours. ++ * ++ * Takes all Data in the context and writes them into a file. ++ * Places the exchange format header before the data. ++ * ++ * Returns: 0 in case of success, -1 otherwise. ++ */ ++int exchange_write_to_file(const exchange_format_ctx_t *ctx, const char *filename, GError **error) ++ PV_NONNULL(1, 2); ++ ++/** ++ * exchange_info_print: ++ * ++ * @ctx: exchange format context ++ * @print_data: TRUE: print present data + label names ++ * FALSE: just print label names of present data ++ * @stream: FILE* stream to print data ++ * ++ * Prints the content of @ctx to @stream. ++ */ ++void exchange_info_print(const exchange_format_ctx_t *ctx, const gboolean print_data, FILE *stream) ++ PV_NONNULL(1, 3); ++ ++#define EXCHANGE_FORMAT_ERROR g_quark_from_static_string("pv-exchange-format_error-quark") ++typedef enum { ++ EXCHANGE_FORMAT_ERROR_INVALID_FORMAT, ++ EXCHANGE_FORMAT_ERROR_UNSUPPORTED_VERSION, ++ EXCHANGE_FORMAT_ERROR_WRITE, ++ EXCHANGE_FORMAT_ERROR_UNSUPPORTED_FILE_TYPE, ++} exchange_error_e; ++ ++#endif /* PVATTEST_EXCHANGE_FORMAT_H */ +--- /dev/null ++++ b/pvattest/src/log.c +@@ -0,0 +1,181 @@ ++/* ++ * Functions used for logging. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include "types.h" ++#include "common.h" ++#include "log.h" ++ ++void pvattest_log_increase_log_lvl(int *log_lvl) ++{ ++ if (*log_lvl >= PVATTEST_LOG_LVL_MAX) ++ return; ++ *log_lvl = *log_lvl << 1; ++} ++ ++void pvattest_log_error(const char *format, ...) ++{ ++ va_list argp; ++ ++ va_start(argp, format); ++ g_logv(NULL, PVATTEST_LOG_LVL_ERROR, format, argp); ++ va_end(argp); ++} ++ ++void pvattest_log_warning(const char *format, ...) ++{ ++ va_list argp; ++ ++ va_start(argp, format); ++ g_logv(NULL, PVATTEST_LOG_LVL_WARNING, format, argp); ++ va_end(argp); ++} ++ ++void pvattest_log_info(const char *format, ...) ++{ ++ va_list argp; ++ ++ va_start(argp, format); ++ g_logv(NULL, PVATTEST_LOG_LVL_INFO, format, argp); ++ va_end(argp); ++} ++ ++void pvattest_log_debug(const char *format, ...) ++{ ++ va_list argp; ++ ++ va_start(argp, format); ++ g_logv(NULL, PVATTEST_LOG_LVL_DEBUG, format, argp); ++ va_end(argp); ++} ++ ++static void _log_print(FILE *stream, const char *prefix, const char *message, const char *postfix) ++{ ++ g_autofree char *prefix_empty = NULL, *new_msg = NULL; ++ size_t prefix_len = strlen(prefix); ++ char **message_v; ++ ++ if (!prefix || prefix_len == 0) { ++ printf("%s%s", message, postfix); ++ return; ++ } ++ ++ message_v = g_strsplit(message, "\n", 0); ++ prefix_empty = g_malloc0(prefix_len + 2); ++ ++ snprintf(prefix_empty, prefix_len + 2, "\n%*c\b", (int)prefix_len, ' '); ++ new_msg = g_strjoinv(prefix_empty, message_v); ++ ++ fprintf(stream, "%s%s%s", prefix, new_msg, postfix); ++ ++ g_strfreev(message_v); ++} ++ ++static void _log_logger(GLogLevelFlags level, const char *message, int log_level, ++ gboolean use_prefix, const char *postfix) ++{ ++ const char *prefix = ""; ++ ++ /* filter out messages depending on debugging level */ ++ if ((level & PVATTEST_LOG_LVL_DEBUG) && log_level < PVATTEST_LOG_LVL_DEBUG) ++ return; ++ ++ if ((level & PVATTEST_LOG_LVL_INFO) && log_level < PVATTEST_LOG_LVL_INFO) ++ return; ++ ++ if (use_prefix && level & (G_LOG_LEVEL_WARNING | PVATTEST_LOG_LVL_WARNING)) ++ prefix = _("WARNING: "); ++ ++ if (use_prefix && level & (G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_ERROR)) ++ prefix = _("ERROR: "); ++ ++ if (use_prefix && level & (G_LOG_LEVEL_DEBUG | PVATTEST_LOG_LVL_DEBUG)) ++ prefix = _("DEBUG: "); ++ ++ if (level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_WARNING | ++ PVATTEST_LOG_LVL_ERROR)) ++ _log_print(stderr, prefix, message, postfix); ++ else ++ _log_print(stdout, prefix, message, postfix); ++} ++ ++/** ++ * prefixes type. and adds a "\n" ad the end. ++ */ ++void pvattest_log_default_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level, ++ const char *message, void *user_data) ++{ ++ int log_level = *(int *)user_data; ++ ++ _log_logger(level, message, log_level, TRUE, "\n"); ++} ++ ++/* ++ * writes message as it is if log level is high enough. ++ */ ++void pvattest_log_plain_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level, ++ const char *message, void *user_data) ++{ ++ int log_level = *(int *)user_data; ++ ++ _log_logger(level, message, log_level, FALSE, ""); ++} ++ ++void hexdump(const void *data, size_t size, size_t len, const char *prefix, GLogLevelFlags log_lvl) ++{ ++ const uint8_t *data_b = data; ++ ++ pv_wrapped_g_assert(data); ++ ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "%s0x0000 ", prefix); ++ for (size_t i = 0; i < size; i++) { ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "%02x", data_b[i]); ++ if (i % 2 == 1) ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, " "); ++ if (i == size - 1) ++ break; ++ if (i % len == len - 1) ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "\n%s0x%04lx ", prefix, i + 1); ++ } ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "\n"); ++} ++ ++void printf_hexdump(const void *data, size_t size, size_t len, const char *prefix, FILE *stream) ++{ ++ const uint8_t *data_b = data; ++ ++ pv_wrapped_g_assert(data); ++ pv_wrapped_g_assert(stream); ++ ++ fprintf(stream, "%s0x0000 ", prefix); ++ for (size_t i = 0; i < size; i++) { ++ fprintf(stream, "%02x", data_b[i]); ++ if (i % 2 == 1) ++ fprintf(stream, " "); ++ if (i == size - 1) ++ break; ++ if (i % len == len - 1) ++ fprintf(stream, "\n%s0x%04lx ", prefix, i + 1); ++ } ++ fprintf(stream, "\n"); ++} ++ ++void pvattest_log_GError(const char *info, GError *error) ++{ ++ pv_wrapped_g_assert(info); ++ ++ if (!error) ++ return; ++ ++ pvattest_log_error("%s:\n%s", info, error->message); ++} +--- /dev/null ++++ b/pvattest/src/log.h +@@ -0,0 +1,67 @@ ++/* ++ * Definitions used for logging. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_LOG_H ++#define PVATTEST_LOG_H ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include "libpv/glib-helper.h" ++#include "libpv/macros.h" ++ ++#define PVATTEST_LOG_LVL_TOOL_ALL (1 << (G_LOG_LEVEL_USER_SHIFT)) ++#define PVATTEST_LOG_LVL_ERROR (1 << (G_LOG_LEVEL_USER_SHIFT)) ++#define PVATTEST_LOG_LVL_WARNING (1 << (G_LOG_LEVEL_USER_SHIFT + 1)) ++#define PVATTEST_LOG_LVL_INFO (1 << (G_LOG_LEVEL_USER_SHIFT + 2)) ++#define PVATTEST_LOG_LVL_DEBUG (1 << (G_LOG_LEVEL_USER_SHIFT + 3)) ++ ++#define PVATTEST_LOG_LVL_DEFAULT PVATTEST_LOG_LVL_WARNING ++#define PVATTEST_LOG_LVL_MAX PVATTEST_LOG_LVL_DEBUG ++ ++#define PVATTEST_HEXDUMP_LOG_DOMAIN "pvattest_hdump" ++ ++void pvattest_log_increase_log_lvl(int *log_lvl); ++void pvattest_log_error(const char *format, ...); ++void pvattest_log_warning(const char *format, ...); ++void pvattest_log_info(const char *format, ...); ++void pvattest_log_debug(const char *format, ...); ++ ++/** pvattest_log_default_logger: ++ * ++ * A #GLogFunc implementation. ++ * Prefixes log level and adds a "\n" ad the end. ++ */ ++void pvattest_log_default_logger(const char *log_domain, GLogLevelFlags level, const char *message, ++ void *user_data); ++/* pvattest_log_plain_logger: ++ * ++ * A #GLogFunc implementation. ++ * Writes message as it is if log level is high enough. ++ */ ++void pvattest_log_plain_logger(const char *log_domain, GLogLevelFlags level, const char *message, ++ void *user_data); ++#define dhexdump(v, s) \ ++ { \ ++ pvattest_log_debug("%s (%li byte):", #v, s); \ ++ hexdump(v, s, 16L, " ", PVATTEST_LOG_LVL_DEBUG); \ ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, PVATTEST_LOG_LVL_DEBUG, "\n"); \ ++ } ++#define gbhexdump(v) \ ++ { \ ++ pvattest_log_debug("%s:(%li byte):", #v, g_bytes_get_size(v)); \ ++ hexdump(g_bytes_get_data(v, NULL), g_bytes_get_size(v), 16L, " ", \ ++ PVATTEST_LOG_LVL_DEBUG); \ ++ g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, PVATTEST_LOG_LVL_DEBUG, "\n"); \ ++ } ++void hexdump(const void *data, size_t size, size_t len, const char *prefix, GLogLevelFlags log_lvl) ++ PV_NONNULL(1); ++void printf_hexdump(const void *data, size_t size, size_t len, const char *prefix, FILE *stream) ++ PV_NONNULL(1, 5); ++void pvattest_log_GError(const char *info, GError *error) PV_NONNULL(1); ++ ++#endif /* PVATTEST_LOG_H */ +--- /dev/null ++++ b/pvattest/src/pvattest.c +@@ -0,0 +1,392 @@ ++/* ++ * Entry point for the pvattest tool. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#include ++#include ++ ++#include ++ ++#include "libpv/crypto.h" ++#include "libpv/cert.h" ++ ++#include "uvio.h" ++#include "common.h" ++#include "attestation.h" ++#include "arcb.h" ++#include "argparse.h" ++#include "exchange_format.h" ++#include "log.h" ++ ++#define PVATTEST_NID NID_secp521r1 ++#define PVATTEST_UV_PATH "/dev/uv" ++#define PVATTEST_EXIT_MEASURE_NOT_VERIFIED 2 ++ ++enum pvattest_error { ++ PVATTEST_ERROR_INVAL_ATT_RESULT, ++}; ++ ++static arcb_v1_t *create_arcb(char **host_key_paths, const gboolean use_nonce, ++ const gboolean phkh_img, const gboolean phkh_att, ++ const uint64_t user_paf, GError **error) ++{ ++ g_autoptr(GBytes) arpk = NULL, meas_key = NULL, nonce = NULL, iv = NULL; ++ g_autoslist(PvX509WithPath) host_keys_with_path = NULL; ++ g_autoslist(EVP_PKEY) evp_host_keys = NULL; ++ const uint32_t mai = MAI_HMAC_SHA512; ++ g_autoptr(EVP_PKEY) evp_cpk = NULL; ++ g_autoptr(arcb_v1_t) arcb = NULL; ++ uint64_t paf = user_paf; ++ ++ g_assert(host_key_paths); ++ ++ arpk = pv_generate_key(EVP_aes_256_gcm(), error); ++ if (!arpk) ++ return NULL; ++ iv = pv_generate_iv(EVP_aes_256_gcm(), error); ++ if (!iv) ++ return NULL; ++ evp_cpk = pv_generate_ec_key(PVATTEST_NID, error); ++ if (!evp_cpk) ++ return NULL; ++ meas_key = pv_generate_rand_data(HMAC_SHA512_KEY_SIZE, error); ++ if (!meas_key) ++ return NULL; ++ ++ if (phkh_img) ++ paf |= ARCB_V1_PAF_AAD_PHKH_HEADER; ++ if (phkh_att) ++ paf |= ARCB_V1_PAF_AAD_PHKH_ATTEST; ++ ++ arcb = arcb_v1_new(arpk, iv, mai, evp_cpk, meas_key, paf, error); ++ if (!arcb) ++ return NULL; ++ if (use_nonce) { ++ nonce = pv_generate_rand_data(ARCB_V1_NONCE_SIZE, error); ++ if (!nonce) ++ return NULL; ++ arcb_v1_set_nonce(arcb, nonce); ++ } ++ ++ host_keys_with_path = pv_load_certificates(host_key_paths, error); ++ if (!host_keys_with_path) ++ return NULL; ++ ++ /* Extract EVP_PKEY structures and verify that the correct elliptic ++ * curve is used. ++ */ ++ evp_host_keys = pv_get_ec_pubkeys(host_keys_with_path, PVATTEST_NID, error); ++ if (!evp_host_keys) ++ return NULL; ++ for (GSList *iter = evp_host_keys; iter; iter = iter->next) { ++ EVP_PKEY *host_key = iter->data; ++ ++ if (arcb_v1_add_key_slot(arcb, host_key, error) < 0) ++ return NULL; ++ } ++ return g_steal_pointer(&arcb); ++} ++ ++#define __PVATTEST_CREATE_ERROR_MSG _("Creating the attestation request failed") ++static int do_create(const pvattest_create_config_t *create_config) ++{ ++ g_autoptr(exchange_format_ctx_t) output_ctx = NULL; ++ uint32_t measurement_size, additional_data_size; ++ g_autoptr(GBytes) serialized_arcb = NULL; ++ g_autoptr(arcb_v1_t) arcb = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autoptr(GBytes) arpk = NULL; ++ ++ if (!create_config->use_nonce) ++ pvattest_log_warning(_("No nonce used. (Experimental setting)")); ++ ++ if (create_config->no_verify) { ++ pvattest_log_warning(_("Host-key document verification is disabled.\n" ++ "The attestation result could be compromised!")); ++ pvattest_log_debug(_("Verification skipped.")); ++ } else { ++ if (pv_verify_host_key_docs_by_path( ++ create_config->host_key_document_paths, create_config->root_ca_path, ++ create_config->crl_paths, create_config->certificate_paths, ++ create_config->online, &error) < 0) ++ goto err_exit; ++ pvattest_log_debug(_("Verification passed.")); ++ } ++ ++ /* build attestation request */ ++ arcb = create_arcb(create_config->host_key_document_paths, create_config->use_nonce, ++ create_config->phkh_img, create_config->phkh_att, create_config->paf, ++ &error); ++ if (!arcb) ++ goto err_exit; ++ ++ additional_data_size = arcb_v1_get_required_additional_size(arcb); ++ if (create_config->x_aad_size >= 0) { ++ g_assert_cmpint(create_config->x_aad_size, <=, UINT32_MAX); ++ additional_data_size = (uint32_t)create_config->x_aad_size; ++ } ++ measurement_size = arcb_v1_get_required_measurement_size(arcb, &error); ++ if (error) ++ goto err_exit; ++ ++ serialized_arcb = arcb_v1_serialize(arcb, &error); ++ if (!serialized_arcb) ++ goto err_exit; ++ ++ /* write attestation request data to file */ ++ output_ctx = exchange_ctx_new(PVATTEST_EXCHANGE_VERSION_1_00, serialized_arcb, ++ measurement_size, additional_data_size, &error); ++ if (!output_ctx) ++ goto err_exit; ++ if (exchange_write_to_file(output_ctx, create_config->output_path, &error) < 0) ++ goto err_exit; ++ pvattest_log_debug(_("ARCB written to file.")); ++ ++ /* write attestation request protection key to file */ ++ arpk = arcb_v1_get_arp_key(arcb); ++ wrapped_g_file_set_content(create_config->arp_key_out_path, arpk, 0600, &error); ++ if (error) ++ goto err_exit; ++ pvattest_log_debug(_("ARPK written to file.")); ++ ++ return EXIT_SUCCESS; ++ ++err_exit: ++ pvattest_log_GError(__PVATTEST_CREATE_ERROR_MSG, error); ++ return EXIT_FAILURE; ++} ++ ++#ifdef PVATTEST_COMPILE_PERFORM ++#define __PVATTEST_MEASURE_ERROR_MSG _("Performing the attestation measurement failed") ++static int do_perform(pvattest_perform_config_t *perform_config) ++{ ++ g_autoptr(GBytes) serialized_arcb = NULL, user_data = NULL, measurement = NULL, ++ additional_data = NULL, config_uid = NULL; ++ size_t uv_measurement_data_size, uv_addidtional_data_size; ++ g_autoptr(exchange_format_ctx_t) exchange_ctx = NULL; ++ uint32_t measurement_size, additional_data_size; ++ g_autoptr(uvio_attest_t) uvio_attest = NULL; ++ g_autoptr(GError) error = NULL; ++ be16_t uv_rc; ++ int uv_fd; ++ ++ exchange_ctx = exchange_ctx_from_file(perform_config->input_path, &error); ++ if (!exchange_ctx) ++ goto err_exit; ++ ++ serialized_arcb = exchange_get_serialized_arcb(exchange_ctx); ++ if (!serialized_arcb) { ++ g_set_error(&error, PVATTEST_ERROR, ARCB_ERR_INVALID_ARCB, ++ _("The input does not provide an attestation request.")); ++ ++ goto err_exit; ++ } ++ ++ measurement_size = exchange_get_requested_measurement_size(exchange_ctx); ++ additional_data_size = exchange_get_requested_additional_data_size(exchange_ctx); ++ ++ pvattest_log_debug(_("Input data loaded.")); ++ ++ if (perform_config->user_data_path) { ++ user_data = pv_file_get_content_as_g_bytes(perform_config->user_data_path, &error); ++ if (!user_data) ++ goto err_exit; ++ pvattest_log_debug(_("Added user data from '%s'"), perform_config->user_data_path); ++ } ++ uvio_attest = build_attestation_v1_ioctl(serialized_arcb, user_data, measurement_size, ++ additional_data_size, &error); ++ if (!uvio_attest) ++ goto err_exit; ++ ++ pvattest_log_debug(_("attestation context generated.")); ++ ++ /* execute attestation */ ++ uv_fd = uvio_open(PVATTEST_UV_PATH, &error); ++ if (uv_fd < 0) ++ goto err_exit; ++ ++ uv_rc = uvio_ioctl_attest(uv_fd, uvio_attest, &error); ++ close(uv_fd); ++ if (uv_rc != UVC_EXECUTED) ++ goto err_exit; ++ pvattest_log_debug(_("attestation measurement successful. rc = %#x"), uv_rc); ++ ++ /* write to file */ ++ measurement = uvio_get_measurement(uvio_attest); ++ additional_data = uvio_get_additional_data(uvio_attest); ++ config_uid = uvio_get_config_uid(uvio_attest); ++ ++ uv_measurement_data_size = measurement == NULL ? 0 : g_bytes_get_size(measurement); ++ if (uv_measurement_data_size != measurement_size) { ++ g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT, ++ "The measurement size returned by Ultravisor is not as expected."); ++ goto err_exit; ++ } ++ ++ uv_addidtional_data_size = additional_data == NULL ? 0 : g_bytes_get_size(additional_data); ++ if (uv_addidtional_data_size != additional_data_size) { ++ g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT, ++ "The additional data size returned by Ultravisor is not as expected."); ++ goto err_exit; ++ } ++ ++ exchange_set_measurement(exchange_ctx, measurement); ++ if (additional_data) ++ exchange_set_additional_data(exchange_ctx, additional_data); ++ exchange_set_config_uid(exchange_ctx, config_uid); ++ if (user_data) ++ exchange_set_user_data(exchange_ctx, user_data); ++ ++ if (exchange_write_to_file(exchange_ctx, perform_config->output_path, &error) < 0) ++ goto err_exit; ++ ++ pvattest_log_debug(_("Output written to file.")); ++ ++ return EXIT_SUCCESS; ++ ++err_exit: ++ pvattest_log_GError(__PVATTEST_MEASURE_ERROR_MSG, error); ++ return EXIT_FAILURE; ++} ++#endif /* PVATTEST_COMPILE_PERFORM */ ++ ++#define __PVATTEST_VERIFY_ERROR_MSG _("Attestation measurement verification failed") ++static int do_verify(pvattest_verify_config_t *verify_config) ++{ ++ g_autoptr(GBytes) user_data = NULL, uv_measurement = NULL, additional_data = NULL, ++ image_hdr = NULL, calc_measurement = NULL, config_uid = NULL, ++ meas_key = NULL, arp_key = NULL, nonce = NULL, serialized_arcb = NULL; ++ g_autofree att_meas_ctx_t *measurement_hdr = NULL; ++ g_autoptr(exchange_format_ctx_t) input_ctx = NULL; ++ g_autoptr(GError) error = NULL; ++ gboolean rc; ++ ++ image_hdr = pv_file_get_content_as_g_bytes(verify_config->hdr_path, &error); ++ if (!image_hdr) ++ goto err_exit; ++ ++ measurement_hdr = att_extract_from_hdr(image_hdr, &error); ++ if (!measurement_hdr) ++ goto err_exit; ++ ++ pvattest_log_debug(_("Image header loaded.")); ++ ++ input_ctx = exchange_ctx_from_file(verify_config->input_path, &error); ++ if (!input_ctx) ++ goto err_exit; ++ ++ config_uid = exchange_get_config_uid(input_ctx); ++ uv_measurement = exchange_get_measurement(input_ctx); ++ user_data = exchange_get_user_data(input_ctx); ++ additional_data = exchange_get_additional_data(input_ctx); ++ serialized_arcb = exchange_get_serialized_arcb(input_ctx); ++ ++ if (!uv_measurement || !serialized_arcb) { ++ g_set_error(&error, PVATTEST_ERROR, PVATTEST_SUBC_INVALID, ++ _("Input data has no measurement")); ++ goto err_exit; ++ } ++ pvattest_log_debug(_("Input data loaded.")); ++ ++ att_add_uid(measurement_hdr, config_uid); ++ ++ arp_key = pv_file_get_content_as_g_bytes(verify_config->arp_key_in_path, &error); ++ if (!arp_key) ++ goto err_exit; ++ pvattest_log_debug(_("ARPK loaded.")); ++ ++ rc = arcb_v1_verify_serialized_arcb(serialized_arcb, arp_key, &meas_key, &nonce, &error); ++ if (!rc) ++ goto err_exit; ++ ++ pvattest_log_debug(_("Input ARCB verified.")); ++ ++ calc_measurement = att_gen_measurement_hmac_sha512(measurement_hdr, meas_key, user_data, ++ nonce, additional_data, &error); ++ if (!calc_measurement) ++ goto err_exit; ++ pvattest_log_debug(_("Measurement calculated.")); ++ ++ if (!att_verify_measurement(calc_measurement, uv_measurement, &error)) { ++ pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error); ++ pvattest_log_debug(_("Measurement values:")); ++ gbhexdump(uv_measurement); ++ gbhexdump(calc_measurement); ++ return PVATTEST_EXIT_MEASURE_NOT_VERIFIED; ++ } ++ ++ pvattest_log_debug(_("Measurement verified.")); ++ ++ return EXIT_SUCCESS; ++ ++err_exit: ++ pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error); ++ return EXIT_FAILURE; ++} ++ ++/* ++ * Will not free the config structs, but the nested char* etc. ++ * that's what we need to do as we will receive a statically allocated config_t ++ * Not defined in the parse header as someone might incorrectly assume ++ * that the config pointers will be freed. ++ */ ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pvattest_config_t, pvattest_parse_clear_config) ++int main(int argc, char *argv[]) ++{ ++ int appl_log_lvl = PVATTEST_LOG_LVL_DEFAULT; ++ g_autoptr(pvattest_config_t) config = NULL; ++ g_autoptr(GError) error = NULL; ++ enum pvattest_command command; ++ int rc; ++ ++ /* setting up the default log handler to filter messages based on the ++ * log level specified by the user. ++ */ ++ g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, ++ &pvattest_log_default_logger, &appl_log_lvl); ++ /* setting up the log handler for hexdumps (no prefix and '\n' at end of ++ * message)to filter messages based on the log level specified by the ++ * user. ++ */ ++ g_log_set_handler(PVATTEST_HEXDUMP_LOG_DOMAIN, ++ G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, ++ &pvattest_log_plain_logger, &appl_log_lvl); ++ ++ command = pvattest_parse(&argc, &argv, &config, &error); ++ if (command == PVATTEST_SUBC_INVALID) { ++ pvattest_log_error(_("%s\nTry '%s --help' for more information"), error->message, ++ GETTEXT_PACKAGE); ++ exit(EXIT_FAILURE); ++ } ++ g_assert(config); ++ appl_log_lvl = config->general.log_level; ++ ++ pv_init(); ++ ++ switch (command) { ++ case PVATTEST_SUBC_CREATE: ++ rc = do_create(&config->create); ++ break; ++#ifdef PVATTEST_COMPILE_PERFORM ++ case PVATTEST_SUBC_PERFORM: ++ rc = do_perform(&config->perform); ++ break; ++#endif /* PVATTEST_COMPILE_PERFORM */ ++ case PVATTEST_SUBC_VERIFY: ++ rc = do_verify(&config->verify); ++ break; ++ default: ++ g_return_val_if_reached(EXIT_FAILURE); ++ } ++ ++ pv_cleanup(); ++ ++ return rc; ++} +--- /dev/null ++++ b/pvattest/src/types.h +@@ -0,0 +1,18 @@ ++/* ++ * Common data type definitions and functions ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_TYPES_H ++#define PVATTEST_TYPES_H ++#include ++ ++/* Types to mark values as big endian. */ ++typedef uint16_t be16_t; ++typedef uint32_t be32_t; ++typedef uint64_t be64_t; ++ ++#endif /* PVATTEST_TYPES_H */ +--- /dev/null ++++ b/pvattest/src/uvio.c +@@ -0,0 +1,177 @@ ++/* ++ * UV device (uvio) related functions and definitions. ++ * uses s390 only (kernel) features. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++/* Must be included before any other header */ ++#include "config.h" ++ ++#ifdef PVATTEST_COMPILE_PERFORM ++#include ++#include ++#include ++#include ++#include ++ ++#include "attestation.h" ++#include "uvio.h" ++#include "common.h" ++#include "log.h" ++ ++/* some helper macros */ ++#define U64_TO_PTR(v) ((void *)(v)) ++#define PTR_TO_U64(ptr) ((uint64_t)(ptr)) ++ ++uvio_attest_t *build_attestation_v1_ioctl(GBytes *serialized_arcb, GBytes *user_data, ++ const uint32_t measurement_size, ++ const uint32_t add_data_size, GError **error) ++{ ++ g_autoptr(uvio_attest_t) uvio_attest = NULL; ++ size_t arcb_size; ++ void *arcb; ++ ++ pv_wrapped_g_assert(serialized_arcb); ++ ++ g_bytes_ref(serialized_arcb); ++ arcb = g_bytes_unref_to_data(serialized_arcb, &arcb_size); ++ ++ uvio_attest = g_malloc0(sizeof(*uvio_attest)); ++ uvio_attest->arcb_addr = PTR_TO_U64(g_steal_pointer(&arcb)); ++ g_assert_cmpuint(arcb_size, <, UINT32_MAX); ++ uvio_attest->arcb_len = GUINT32_TO_BE((uint32_t)arcb_size); ++ /* transferred the local ownership of the arcb from this function to uvio_attest; nullify pointer */ ++ g_steal_pointer(&serialized_arcb); ++ ++ if (user_data) { ++ if (g_bytes_get_size(user_data) > sizeof(uvio_attest->user_data)) { ++ g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_USER_DATA, ++ _("User data larger than %li bytes"), ++ sizeof(uvio_attest->user_data)); ++ return NULL; ++ } ++ uvio_attest->user_data_len = GUINT16_TO_BE((uint16_t)g_bytes_get_size(user_data)); ++ pv_gbytes_memcpy(uvio_attest->user_data, uvio_attest->user_data_len, user_data); ++ } ++ ++ uvio_attest->meas_len = GUINT32_TO_BE(measurement_size); ++ uvio_attest->meas_addr = PTR_TO_U64(g_malloc0(uvio_attest->meas_len)); ++ ++ uvio_attest->add_data_len = GUINT32_TO_BE(add_data_size); ++ uvio_attest->add_data_addr = PTR_TO_U64(g_malloc0(uvio_attest->add_data_len)); ++ ++ return g_steal_pointer(&uvio_attest); ++} ++ ++void uvio_attest_free(uvio_attest_t *attest) ++{ ++ if (!attest) ++ return; ++ ++ g_free(U64_TO_PTR(attest->arcb_addr)); ++ g_free(U64_TO_PTR(attest->meas_addr)); ++ g_free(U64_TO_PTR(attest->add_data_addr)); ++ g_free(attest); ++} ++ ++GBytes *uvio_get_measurement(const uvio_attest_t *attest) ++{ ++ pv_wrapped_g_assert(attest); ++ ++ if (attest->meas_addr == (__u64)0) ++ return NULL; ++ return g_bytes_new(U64_TO_PTR(attest->meas_addr), GUINT32_FROM_BE(attest->meas_len)); ++} ++ ++GBytes *uvio_get_additional_data(const uvio_attest_t *attest) ++{ ++ pv_wrapped_g_assert(attest); ++ ++ if (attest->add_data_addr == (__u64)0) ++ return NULL; ++ return g_bytes_new(U64_TO_PTR(attest->add_data_addr), ++ GUINT32_FROM_BE(attest->add_data_len)); ++} ++ ++GBytes *uvio_get_config_uid(const uvio_attest_t *attest) ++{ ++ pv_wrapped_g_assert(attest); ++ ++ return g_bytes_new(attest->config_uid, sizeof(attest->config_uid)); ++} ++ ++uint16_t uvio_ioctl(const int uv_fd, const unsigned int cmd, const uint32_t flags, ++ const void *argument, const uint32_t argument_size, GError **error) ++{ ++ g_autofree struct uvio_ioctl_cb *uv_ioctl = g_malloc0(sizeof(*uv_ioctl)); ++ int rc, cached_errno; ++ ++ pv_wrapped_g_assert(argument); ++ ++ uv_ioctl->flags = flags; ++ uv_ioctl->argument_addr = PTR_TO_U64(argument); ++ uv_ioctl->argument_len = argument_size; ++ rc = ioctl(uv_fd, cmd, uv_ioctl); ++ cached_errno = errno; ++ ++ if (rc < 0) { ++ g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_IOCTL, _("ioctl failed: %s "), ++ g_strerror(cached_errno)); ++ return 0; ++ } ++ ++ if (uv_ioctl->uv_rc != UVC_EXECUTED) ++ g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_NOT_OK, ++ _("Ultravisor call returned '%#x' (%s)"), uv_ioctl->uv_rc, ++ uvio_uv_rc_to_str(uv_ioctl->uv_rc)); ++ return GUINT16_FROM_BE(uv_ioctl->uv_rc); ++} ++ ++uint16_t uvio_ioctl_attest(const int uv_fd, uvio_attest_t *attest, GError **error) ++{ ++ pv_wrapped_g_assert(attest); ++ ++ return uvio_ioctl(uv_fd, UVIO_IOCTL_ATT, 0, attest, sizeof(*attest), error); ++} ++ ++int uvio_open(const char *uv_path, GError **error) ++{ ++ pv_wrapped_g_assert(uv_path); ++ ++ int uv_fd; ++ int cached_errno; ++ ++ uv_fd = open(uv_path, O_RDWR); ++ cached_errno = errno; ++ if (uv_fd < 0) ++ g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_OPEN, ++ _("Cannot open uv driver at %s: %s"), uv_path, ++ g_strerror(cached_errno)); ++ return uv_fd; ++} ++ ++const char *uvio_uv_rc_to_str(const int rc) ++{ ++ switch (rc) { ++ case 0x106: ++ return _("Unsupported attestation request version"); ++ case 0x108: ++ return _("Number of key slots is greater than the maximum number supported"); ++ case 0x10a: ++ return _("Unsupported plaintext attestation flags"); ++ case 0x10c: ++ return _( ++ "Unable to decrypt attestation request control block. No valid host-key was provided"); ++ case 0x10d: ++ return _("Measurement data length is too small to store measurement"); ++ case 0x10e: ++ return _("Additional data length is too small to store measurement"); ++ default: ++ return _("Unknown code"); ++ } ++} ++ ++#endif /* PVATTEST_COMPILE_PERFORM */ +--- /dev/null ++++ b/pvattest/src/uvio.h +@@ -0,0 +1,110 @@ ++/* ++ * UV device (uvio) related functions and definitions. ++ * ++ * Copyright IBM Corp. 2022 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++#ifndef PVATTEST_UVIO_H ++#define PVATTEST_UVIO_H ++#include "config.h" ++ ++#ifdef PVATTEST_COMPILE_PERFORM ++ ++#include ++#include ++ ++#include "libpv/glib-helper.h" ++ ++#include "arcb.h" ++#include "common.h" ++ ++#define UVC_EXECUTED 0x0001 ++ ++typedef struct uvio_attest uvio_attest_t; ++G_STATIC_ASSERT(sizeof(uvio_attest_t) == 0x138); ++G_STATIC_ASSERT(sizeof(struct uvio_ioctl_cb) == 0x40); ++ ++/** ++ * build_attestation_v1_ioctl: ++ * @serialized_arcb: A ARCB in binary format ++ * @user_data (optional): up to 256 bytes of user data to be added to the measurement ++ * @measurement_size: Size of the measurement result to be allocated ++ * @add_data_size: Size of the additional data to be allocated ++ * @error: return location for a #GError ++ * ++ * Builds the structure to be passed to `/dev/uv` for attestation IOCTLs and ++ * allocates any required memory. ++ * ++ * Returns: (nullable) (transfer full): Pointer to a uvio_attest_t to be passed to `/dev/uv` ++ */ ++uvio_attest_t *build_attestation_v1_ioctl(GBytes *serialized_arcb, GBytes *user_data, ++ const uint32_t measurement_size, ++ const uint32_t add_data_size, GError **error) ++ PV_NONNULL(1); ++GBytes *uvio_get_measurement(const uvio_attest_t *attest) PV_NONNULL(1); ++GBytes *uvio_get_additional_data(const uvio_attest_t *attest) PV_NONNULL(1); ++GBytes *uvio_get_config_uid(const uvio_attest_t *attest) PV_NONNULL(1); ++void uvio_attest_free(uvio_attest_t *attest); ++WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(uvio_attest_t, uvio_attest_free) ++ ++/** ++ * uvio_ioctl: ++ * @uv_fd: file descriptor to the UV-device ++ * @cmd: IOCTL cmd ++ * @flags: flags for the uv IOCTL ++ * @argument: pointer to the payload ++ * @argument_size: size of #argument ++ * @error: return location for a #GError ++ * ++ * Builds the IOCTL structure using, flags and argument, performs the IOCTL, and returns the UV rc in big endian. ++ * If the device driver emits an error code, a corresponding #GError will be created. ++ * Use the specialized calls (uvio_ioctl_*). ++ * ++ * Returns: UV rc if no device error occurred (>0) ++ * 0 on #GError ++ */ ++uint16_t uvio_ioctl(const int uv_fd, const unsigned int cmd, const uint32_t flags, ++ const void *argument, const uint32_t argument_size, GError **error) ++ PV_NONNULL(4); ++/** ++ * uvio_ioctl_attest: ++ * @uv_fd: file descriptor to the UV-device ++ * @attest: pointer to the attestation request ++ * @error: return location for a #GError ++ * ++ * Wraps 'uvio_ioctl' for attestation. ++ * ++ * Returns: UV rc if no device error occurred (>0) ++ * 0 on #GError ++ */ ++uint16_t uvio_ioctl_attest(const int uv_fd, uvio_attest_t *attest, GError **error) PV_NONNULL(2); ++ ++/** ++ * uvio_open: ++ * @uv_path: path of the UV-device usually at /dev/uv ++ * @error: return location for a #GError ++ * ++ * Returns: File descriptor for the UV-device ++ * 0 on #GError ++ */ ++int uvio_open(const char *uv_path, GError **error) PV_NONNULL(1); ++ ++/** ++ * uvio_uv_rc_to_str: ++ * @rc: UV return code ++ * ++ * Returns: Pointer to an error string corresponding to the given UV-rc. ++ */ ++const char *uvio_uv_rc_to_str(const int rc); ++ ++#define UVIO_ERROR g_quark_from_static_string("pv-uvio_error-quark") ++typedef enum { ++ UVIO_ERR_UV_IOCTL, ++ UVIO_ERR_UV_OPEN, ++ UVIO_ERR_UV_NOT_OK, ++} uvio_error_e; ++ ++#endif /* PVATTEST_COMPILE_PERFORM */ ++#endif /* PVATTEST_UVIO_H */ diff -Nru s390-tools-2.20.0/debian/patches/6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch s390-tools-2.20.0/debian/patches/6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch --- s390-tools-2.20.0/debian/patches/6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch 1970-01-01 01:00:00.000000000 +0100 +++ s390-tools-2.20.0/debian/patches/6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch 2022-08-30 08:04:08.000000000 +0200 @@ -0,0 +1,41 @@ +From 6ff8202fa9e172199e995298d336d9dd87ca8180 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Mon, 25 Jul 2022 12:57:53 +0200 +Subject: [PATCH] zipl: Add missing check for a nullpointer. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fixes a bug that leads to a segmentation fault when no parmline is +provided. + +Fixes: 11b401b5 ("zipl: move and make check for maximum command line length dynamic") +Signed-off-by: Steffen Eiden +Reviewed-by: Marc Hartmayer +Reviewed-by: Stefan Haberland +Signed-off-by: Jan Höppner + +Origin: upstream, https://github.com/ibm-s390-linux/s390-tools/commit/6ff8202fa9e172199e995298d336d9dd87ca8180 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1974109 +Last-Update: 2022-08-30 + +--- + zipl/src/job.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/zipl/src/job.c b/zipl/src/job.c +index ffdc297a..b5bf5b20 100644 +--- a/zipl/src/job.c ++++ b/zipl/src/job.c +@@ -790,7 +790,7 @@ check_common_ipl_data(struct job_common_ipl_data *common, const char *section, + if (!max_parm_size) + max_parm_size = LEGACY_MAXIMUM_PARMLINE_SIZE; + +- len = strlen(common->parmline); ++ len = common->parmline ? strlen(common->parmline) : 0; + if (len > max_parm_size) { + error_text("The length of the parameters line " + "(%d bytes) exceeds the allowed maximum " +-- +2.25.1 + diff -Nru s390-tools-2.20.0/debian/patches/series s390-tools-2.20.0/debian/patches/series --- s390-tools-2.20.0/debian/patches/series 2022-05-20 13:48:34.000000000 +0200 +++ s390-tools-2.20.0/debian/patches/series 2022-08-30 08:13:26.000000000 +0200 @@ -50,3 +50,12 @@ # LP: #1978323 0981df6-cmsfs-fuse-fix-enabling-of-hard_remove-option.patch + +# LP: #1974109 +6ff8202f-zipl-Add-missing-check-for-a-nullpointer.patch + +# LP: #1959987 +38639269-libpv-New-library-for-PV-tools.patch +3ab06d77-pvattest-Create-perform-and-verify-attestation-measu.patch +26148740-pvattest-tools-Add-tool-for-attestation.patch +