diff --git a/CHANGELOG.md b/CHANGELOG.md index eee10ed6..3aca846e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Release history for s390-tools (MIT version) Changes of existing tools: - hsci: New tool to manage HSCI interfaces + - genprotimg: Add host-key document verification support Bug Fixes: diff --git a/README.md b/README.md index 581353fd..5a2153c9 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ build options: | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | | json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb | | glib2 | `HAVE_GLIB2` | genprotimg | -| libcurl | `HAVE_LIBCURL` | libekmfweb | +| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb | This table lists additional build or install options: diff --git a/genprotimg/man/genprotimg.8 b/genprotimg/man/genprotimg.8 index 597106e0..c08336be 100644 --- a/genprotimg/man/genprotimg.8 +++ b/genprotimg/man/genprotimg.8 @@ -2,7 +2,7 @@ .\" 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 GENPROTIMG 8 "March 2020" "s390-tools" +.TH GENPROTIMG 8 "November 2020" "s390-tools" .SH NAME genprotimg \- Create a protected virtualization image @@ -10,6 +10,7 @@ genprotimg \- Create a protected virtualization image .SY .B genprotimg \fB\-k\fR \fIHOST_KEY_DOCUMENT\fR... +\fB\-C\fR \fICERTIFICATE\fR... \fB\-i\fR \fIVMLINUZ\fR [\fB\-r\fR \fIRAMDISK\fR] [\fB\-p\fR \fIPARMFILE\fR] @@ -21,15 +22,19 @@ genprotimg \- Create a protected virtualization image .PP Use \fBgenprotimg\fR to generate a single bootable image file with encrypted and integrity-protected parts. The command requires a kernel -image, a host-key document, and an output file name. Optionally, -specify an initial RAM filesystem, and a file containing the kernel -parameters. Should special circumstances require it, you can +image, a host-key document, certificates for the host-key document +verification, and an output file name. Optionally, specify an initial +RAM filesystem, and a file containing the kernel parameters. If the +command should be run offline, use the \fB\-\-offline\fR option and +specify the certificate revocation lists (CRLs) by using the +\fB\-\-crl\fR option. Should special circumstances require it, you can optionally specify your own keys for the encryption by using the -experimental options. In the resulting image file, a plain text boot -loader, the encrypted components for kernel, initial RAM disk, kernel -parameters, and the encrypted and integrity-protected header are -concatenated. The header contains metadata necessary for running the -guest in protected mode. +experimental options. For all certificates, CRLs, and host-key +documents, both the PEM and DER input formats are supported. In the +resulting image file, a plain text boot loader, the encrypted +components for kernel, initial RAM disk, kernel parameters, and the +encrypted and integrity-protected header are concatenated. The header +contains metadata necessary for running the guest in protected mode. .PP Use this image file as a kernel image for zipl or for a direct kernel boot using QEMU. @@ -53,6 +58,12 @@ Specifies a host-key document. At least one is required. Specify this option multiple times to enable the image to run on more than one host. .TP +\fB\-C\fR, \fB\-\-cert\fR=\fI\,FILE\/\fR +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. +.TP \fB\-o\fR, \fB\-\-output\fR=\fI\,OUTPUT_FILE\/\fR Specifies the output file. Required. .TP @@ -65,6 +76,20 @@ Specifies the RAM disk image. Optional. \fB\-p\fR, \fB\-\-parmfile\fR=\fI\,PARMFILE\/\fR Specifies the kernel command line stored in \fI\,PARMFILE\/\fR. Optional. .TP +\fB\-\-crl\fR=\fI\,FILE\/\fR +Specifies the revocation list that is used to check whether a +certificate of the chain of trust is revoked. Specify this option +multiple times to use multiple CRLs. Optional. +.TP +\fB\-\-offline\fR +Specifies offline mode, in which no attempt is made to download +CRLs. Optional. +.TP +\fB\-\-root\-ca\fR=\fI\,FILE\/\fR +Specifies the root CA certificate for the verification. If omitted, +the DigiCert root CA certificate installed on the system is used. Use +this only if you trust the specified certificate. Optional. +.TP \fB\-\-no-verify\fR Do not require the host-key documents to be valid. For testing purposes, do not use for a production image. Optional. @@ -77,11 +102,13 @@ Prints version information, then exits. Generate a protected virtualization image in \fI\,/boot/vmlinuz.pv\/\fR, using the kernel file \fI\,vmlinuz\/\fR, the initrd in \fI\,initramfs\/\fR, the kernel parameters contained in -\fI\,parmfile\/\fR, and the host-key document in \fI\,host_key.crt\/\fR: +\fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR, +the IBM Z signing key in \fI\,ibm-z-host-key-signing.crt\/\fR, and the +host-key document in \fI\,host_key.crt\/\fR: .PP .Vb 1 .EX -\& genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-o \fI\,/boot/vmlinuz.pv\/\fR +\& genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt \-o \fI\,/boot/vmlinuz.pv\/\fR .EE .Ve .PP diff --git a/genprotimg/src/Makefile b/genprotimg/src/Makefile index 1adeac35..2da15da3 100644 --- a/genprotimg/src/Makefile +++ b/genprotimg/src/Makefile @@ -23,16 +23,16 @@ WARNINGS := -Wall -Wextra -Wshadow \ $(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \ pv/pv_comp.c pv/pv_hdr.c pv/pv_ipib.c utils/crypto.c utils/file_utils.c \ pv/pv_args.c utils/buffer.c pv/pv_comps.c pv/pv_error.c \ - pv/pv_opt_item.c \ + pv/pv_opt_item.c utils/curl.c \ $(NULL) $(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o) ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \ - $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) \ + $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \ $(WARNINGS) \ $(NULL) ALL_CPPFLAGS += $(INCLUDE_PARMS) -LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) +LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS) ifneq ($(shell sh -c 'command -v pkg-config'),) @@ -40,21 +40,27 @@ 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) LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto) +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 +LIBCURL_CFLAGS := +LIBCURL_LIBS := -lcurl endif 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) @@ -98,4 +104,9 @@ $($(bin_PROGRAM)_OBJS): .check-dep-$(bin_PROGRAM) "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 $@ diff --git a/genprotimg/src/genprotimg.c b/genprotimg/src/genprotimg.c index c6de381a..2041fa28 100644 --- a/genprotimg/src/genprotimg.c +++ b/genprotimg/src/genprotimg.c @@ -18,6 +18,8 @@ #include "common.h" #include "pv/pv_args.h" #include "pv/pv_image.h" +#include "utils/crypto.h" +#include "utils/curl.h" enum { LOG_LEVEL_CRITICAL = 0, @@ -117,6 +119,8 @@ static void remove_signal_handler(const gint *signals, const gsize signals_n) signal(signals[i], SIG_DFL); } +static void __attribute__((constructor)) __init(void); +static void __attribute__((destructor)) __cleanup(void); gint main(gint argc, gchar *argv[]) { g_autoptr(PvArgs) args = pv_args_new(); @@ -181,3 +185,16 @@ error: g_clear_pointer(&args, pv_args_free); exit(ret); } + +static void __init(void) +{ + pv_crypto_init(); + if (curl_init() != 0) + g_abort(); +} + +static void __cleanup(void) +{ + curl_cleanup(); + pv_crypto_cleanup(); +} diff --git a/genprotimg/src/include/pv_crypto_def.h b/genprotimg/src/include/pv_crypto_def.h index ddb8652d..53984a3d 100644 --- a/genprotimg/src/include/pv_crypto_def.h +++ b/genprotimg/src/include/pv_crypto_def.h @@ -14,6 +14,24 @@ #include "lib/zt_common.h" +/* IBM signing key subject */ +#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_ORGANIZATIONONAL_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 + +/* SKID for DigiCert Assured ID Root CA */ +#define DIGICERT_ASSURED_ID_ROOT_CA_SKID "45EBA2AFF492CB82312D518BA7A7219DF36DC80F" + union ecdh_pub_key { struct { uint8_t x[80]; diff --git a/genprotimg/src/pv/pv_args.c b/genprotimg/src/pv/pv_args.c index 4bb78b55..3ba1f943 100644 --- a/genprotimg/src/pv/pv_args.c +++ b/genprotimg/src/pv/pv_args.c @@ -18,7 +18,9 @@ static gchar summary[] = "Use genprotimg to create a protected virtualization kernel image file,\n" - "which can be loaded using zipl or QEMU."; + "which can be loaded using zipl or QEMU. For all certificates, revocation\n" + "lists, and host-key documents, both the PEM and DER input formats are\n" + "supported."; static gint pv_arg_compare(gconstpointer arg_1, gconstpointer arg_2) { @@ -97,9 +99,14 @@ static gint pv_args_validate_options(PvArgs *args, GError **err) return -1; } - if (!args->no_verify) { - g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, - _("Use the option '--no-verify' as the verification support is not available yet.")); + if (!args->no_verify && + (!args->untrusted_cert_paths || + g_strv_length(args->untrusted_cert_paths) == 0)) { + g_set_error( + err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Either specify the IBM Z signing key and (DigiCert) intermediate CA certificate\n" + "by using the '--cert' option, or use the '--no-verify' flag to disable the\n" + "host-key document verification completely (at your own risk).")); return -1; } @@ -141,6 +148,8 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value, { gchar **args_option = NULL; + if (g_str_equal(option, "--root-ca")) + args_option = &args->root_ca_path; if (g_str_equal(option, "-o") || g_str_equal(option, "--output")) args_option = &args->output_path; if (g_str_equal(option, "--x-comp-key")) @@ -211,6 +220,18 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], _("FILE specifies a host-key document. At least\n" INDENT "one is required."), .arg_description = _("FILE") }, + { .long_name = "cert", + .short_name = 'C', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->untrusted_cert_paths, + .description = _( + "FILE contains a certificate that is used to\n" INDENT + "establish a chain of trust for the verification\n" INDENT + "of the host-key documents. The IBM Z signing\n" INDENT + "key and intermediate CA certificate (signed\n" INDENT + "by the root CA) are required."), + .arg_description = _("FILE") }, { .long_name = "output", .short_name = 'o', .flags = G_OPTION_FLAG_FILENAME, @@ -241,6 +262,31 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], .description = _("Use the kernel parameters stored in PARMFILE\n" INDENT "(optional)."), .arg_description = _("PARMFILE") }, + { .long_name = "crl", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->crl_paths, + .description = _( + "FILE contains a certificate revocation list\n" INDENT + "(optional)."), + .arg_description = _("FILE") }, + { .long_name = "offline", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &args->offline, + .description = _("Don't download CRLs (optional)."), + .arg_description = NULL }, + { .long_name = "root-ca", + .short_name = 0, + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Set FILE as the trusted root CA and don't use the\n" INDENT + "root CAs that are installed on the system (optional)."), + .arg_description = _("FILE") }, { .long_name = "no-verify", .short_name = 0, .flags = G_OPTION_FLAG_NONE, @@ -378,6 +424,9 @@ void pv_args_free(PvArgs *args) g_free(args->cust_root_key_path); g_free(args->cust_comm_key_path); g_free(args->gcm_iv_path); + g_free(args->root_ca_path); + g_strfreev(args->crl_paths); + g_strfreev(args->untrusted_cert_paths); g_strfreev(args->host_keys); g_free(args->xts_key_path); g_slist_free_full(args->comps, (GDestroyNotify)pv_arg_free); diff --git a/genprotimg/src/pv/pv_args.h b/genprotimg/src/pv/pv_args.h index f17e7b85..8939232a 100644 --- a/genprotimg/src/pv/pv_args.h +++ b/genprotimg/src/pv/pv_args.h @@ -25,6 +25,7 @@ void pv_arg_free(PvArg *arg); typedef struct { gint log_level; gint no_verify; + gboolean offline; gchar *pcf; gchar *scf; gchar *psw_addr; /* PSW address which will be used for the start of @@ -34,6 +35,11 @@ typedef struct { gchar *cust_comm_key_path; gchar *gcm_iv_path; gchar **host_keys; + gchar *root_ca_path; /* Trusted root CA used for the verification of the + * chain of trust (if specified). + */ + gchar **untrusted_cert_paths; + gchar **crl_paths; gchar *xts_key_path; GSList *comps; gchar *output_path; diff --git a/genprotimg/src/pv/pv_error.h b/genprotimg/src/pv/pv_error.h index 1dd24fcb..abe47aee 100644 --- a/genprotimg/src/pv/pv_error.h +++ b/genprotimg/src/pv/pv_error.h @@ -28,6 +28,8 @@ typedef enum { PV_ERROR_IPIB_SIZE, PV_ERROR_PV_HDR_SIZE, PV_ERROR_INTERNAL, + PV_ERROR_CURL_INIT_FAILED, + PV_ERROR_DOWNLOAD_FAILED, } PvErrors; typedef enum { @@ -57,6 +59,31 @@ typedef enum { PV_CRYPTO_ERROR_RANDOMIZATION, PV_CRYPTO_ERROR_INVALID_PARM, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + PV_CRYPTO_ERROR_INVALID_VALIDITY_PERIOD, + PV_CRYPTO_ERROR_EXPIRED, + PV_CRYPTO_ERROR_NOT_VALID_YET, + PV_CRYPTO_ERROR_LOAD_CRL, + PV_CRYPTO_ERROR_NO_PUBLIC_KEY, + PV_CRYPTO_ERROR_INVALID_SIGNATURE_ALGORITHM, + PV_CRYPTO_ERROR_SIGNATURE_ALGORITHM_MISMATCH, + PV_CRYPTO_ERROR_INVALID_URI, + PV_CRYPTO_ERROR_CRL_DOWNLOAD_FAILED, + PV_CRYPTO_ERROR_CERT_SIGNATURE_INVALID, + PV_CRYPTO_ERROR_CRL_SIGNATURE_INVALID, + PV_CRYPTO_ERROR_CERT_SUBJECT_ISSUER_MISMATCH, + PV_CRYPTO_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, + PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + PV_CRYPTO_ERROR_MALFORMED_CERTIFICATE, + PV_CRYPTO_ERROR_NO_CRL, + PV_CRYPTO_ERROR_LOAD_ROOT_CA, + PV_CRYPTO_ERROR_LOAD_DEFAULT_CA, + PV_CRYPTO_ERROR_MALFORMED_ROOT_CA, + PV_CRYPTO_ERROR_WRONG_CA_USED, + PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + PV_CRYPTO_ERROR_NO_ISSUER_IBM_Z_FOUND, + PV_CRYPTO_ERROR_FAILED_DOWNLOAD_CRL, + PV_CRYPTO_ERROR_NO_CRLDP, + PV_CRYPTO_ERROR_CERT_REVOKED, } PvCryptoErrors; #endif diff --git a/genprotimg/src/pv/pv_image.c b/genprotimg/src/pv/pv_image.c index 7ec5fe9c..59eca5e3 100644 --- a/genprotimg/src/pv/pv_image.c +++ b/genprotimg/src/pv/pv_image.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -138,22 +139,18 @@ static EVP_PKEY *pv_img_get_cust_pub_priv_key(gint nid, GError **err) return generate_ec_key(nid, err); } -static HostKeyList *pv_img_get_host_keys(gchar **host_cert_paths, - X509_STORE *store, gint nid, +static HostKeyList *pv_img_get_host_keys(GSList *host_keys_with_path, gint nid, GError **err) { g_autoslist(EVP_PKEY) ret = NULL; - g_assert(host_cert_paths); - - for (gchar **iterator = host_cert_paths; iterator != NULL && *iterator != NULL; - iterator++) { + for (GSList *iterator = host_keys_with_path; iterator; + iterator = iterator->next) { + x509_with_path *cert_with_path = iterator->data; g_autoptr(EVP_PKEY) host_key = NULL; - const gchar *path = *iterator; + X509 *cert = cert_with_path->cert; - g_assert(path); - - host_key = read_ec_pubkey_cert(store, nid, path, err); + host_key = read_ec_pubkey_cert(cert, nid, err); if (!host_key) return NULL; @@ -253,10 +250,172 @@ static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s, return 0; } +static gint pv_img_hostkey_verify(GSList *host_key_certs, + const gchar *root_ca_path, + const gchar *const *crl_paths, + const gchar *const *untrusted_cert_paths, + gboolean offline, GError **err) +{ + g_autoslist(x509_with_path) untrusted_certs_with_path = NULL; + g_autoptr(STACK_OF_X509) ibm_signing_certs = NULL; + g_autoptr(STACK_OF_X509) untrusted_certs = NULL; + g_autoslist(x509_pair) ibm_z_pairs = NULL; + g_autoptr(X509_STORE) trusted = NULL; + gint ibm_signing_certs_count; + + /* 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 = store_setup(root_ca_path, crl_paths, err); + if (!trusted) + goto error; + + if (!offline) { + g_autoptr(STACK_OF_X509_CRL) downloaded_ibm_signing_crls = NULL; + + /* Set up the download routine for the lookup of CRLs. */ + 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 = try_load_crls_by_certs(host_key_certs); + + /* 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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to load CRL")); + goto error; + } + } + } + + /* Load all untrusted certificates (e.g. IBM Z signing key and + * DigiCert 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's the DigiCert Assured ID Root CA). + */ + untrusted_certs_with_path = load_certificates(untrusted_cert_paths, err); + if (!untrusted_certs_with_path) + goto error; + + /* Convert to STACK_OF(X509) */ + untrusted_certs = get_x509_stack(untrusted_certs_with_path); + + /* 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 = delete_ibm_signing_certs(untrusted_certs); + ibm_signing_certs_count = sk_X509_num(ibm_signing_certs); + if (ibm_signing_certs_count < 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + _("please specify at least one IBM Z signing key")); + goto error; + } else if (ibm_signing_certs_count > 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + _("please specify only one IBM Z signing key")); + goto error; + } + + if (store_set_verify_param(trusted, err) < 0) + goto error; + + /* Verify that the 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 ued? It has either to be the + * 'DigiCert Assured ID Root CA' or the root CA specified via + * command line. + */ + for (gint i = 0; i < sk_X509_num(ibm_signing_certs); ++i) { + X509 *ibm_signing_cert = sk_X509_value(ibm_signing_certs, i); + g_autoptr(STACK_OF_X509_CRL) ibm_signing_crls = NULL; + g_autoptr(X509_STORE_CTX) ctx = NULL; + x509_pair *pair = NULL; + + g_assert(ibm_signing_cert); + + /* Create the verification context and set the trusted + * and chain parameters. + */ + ctx = create_store_ctx(trusted, untrusted_certs, err); + if (!ctx) + goto error; + + /* Verify the IBM Z signing key */ + if (verify_cert(ibm_signing_cert, ctx, err) < 0) + goto error; + + /* Verify the build chain of trust chain. If the user passes a + * trusted root CA on the command line then the check for the + * Subject Key Identifier (SKID) is skipped, otherwise let's + * check if the SKID meets our expectation. + */ + if (!root_ca_path && + check_chain_parameters(X509_STORE_CTX_get0_chain(ctx), + get_digicert_assured_id_root_ca_skid(), + err) < 0) { + goto error; + } + + ibm_signing_crls = store_ctx_find_valid_crls(ctx, ibm_signing_cert, err); + if (!ibm_signing_crls) { + g_prefix_error(err, _("IBM Z signing key: ")); + goto error; + } + + /* Increment reference counter of @ibm_signing_cert as the + * certificate will now also be owned by @ibm_z_pairs. + */ + if (X509_up_ref(ibm_signing_cert) != 1) + g_abort(); + + pair = x509_pair_new(&ibm_signing_cert, &ibm_signing_crls); + ibm_z_pairs = g_slist_append(ibm_z_pairs, pair); + g_assert(!ibm_signing_cert); + g_assert(!ibm_signing_crls); + } + + /* Verify host-key documents by using the IBM Z signing + * certificates and the corresponding certificate revocation + * lists. + */ + for (GSList *iterator = host_key_certs; iterator; iterator = iterator->next) { + x509_with_path *host_key_with_path = iterator->data; + const gchar *host_key_path = host_key_with_path->path; + X509 *host_key = host_key_with_path->cert; + gint flags = X509_V_FLAG_CRL_CHECK; + + if (verify_host_key(host_key, ibm_z_pairs, flags, + PV_CERTS_SECURITY_LEVEL, err) < 0) { + g_prefix_error(err, "'%s': ", host_key_path); + goto error; + } + } + + return 0; +error: + g_prefix_error(err, _("Failed to verify host-key document: ")); + return -1; +} + /* read in the keys or auto-generate them */ static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) { - g_autoptr(X509_STORE) store = NULL; + g_autoslist(x509_with_path) host_key_certs = NULL; g_assert(img->xts_cipher); g_assert(img->cust_comm_cipher); @@ -285,8 +444,25 @@ static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) if (!img->cust_pub_priv_key) return -1; + /* Load all host-key documents specified on the command line */ + host_key_certs = load_certificates((const gchar **)args->host_keys, + err); + if (!host_key_certs) + return -1; + + if (!args->no_verify && + pv_img_hostkey_verify(host_key_certs, args->root_ca_path, + (const gchar * const *)args->crl_paths, + (const gchar * const *)args->untrusted_cert_paths, + args->offline, err) < 0) { + return -1; + } + + /* Loads the public keys stored in the host-key documents and verify + * that the correct elliptic curve is used. + */ img->host_pub_keys = - pv_img_get_host_keys(args->host_keys, store, img->nid, err); + pv_img_get_host_keys(host_key_certs, img->nid, err); if (!img->host_pub_keys) return -1; @@ -406,6 +582,9 @@ PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err) if (args->no_verify) g_warning(_("host-key document verification is disabled. Your workload is not secured.")); + if (args->root_ca_path) + g_warning(_("A different root CA than the default DigiCert root CA is selected. Ensure that this root CA is trusted.")); + ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err); if (!ret->comps) return NULL; diff --git a/genprotimg/src/utils/crypto.c b/genprotimg/src/utils/crypto.c index b0c4aa92..0f774ebe 100644 --- a/genprotimg/src/utils/crypto.c +++ b/genprotimg/src/utils/crypto.c @@ -16,6 +16,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -25,8 +30,49 @@ #include "pv/pv_error.h" #include "buffer.h" +#include "curl.h" #include "crypto.h" +#define DEFINE_GSLIST_MAP(t2, t1) \ + typedef t1 *(*g_slist_map_func_##t2##_##t1)(const t2 *x, \ + GError **err); \ + G_GNUC_UNUSED static GSList *g_slist_map_##t2##_##t1(const GSList *list, \ + g_slist_map_func_##t2##_##t1 func, \ + GError **err) \ + { \ + 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, err); \ + 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(x509_with_path, X509) +DEFINE_GSLIST_TO_STACK(X509) + EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err) { g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new(); @@ -359,79 +405,1340 @@ static gboolean certificate_uses_correct_curve(EVP_PKEY *key, gint nid, return TRUE; } -static gboolean verify_certificate(X509_STORE *store, X509 *cert, GError **err) +/* 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 **err) { - g_autoptr(X509_STORE_CTX) csc = X509_STORE_CTX_new(); - if (!csc) - g_abort(); + gint pkey_nid; - if (X509_STORE_CTX_init(csc, store, cert, NULL) != 1) { - g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INIT, - _("Failed to initialize X.509 store")); - return FALSE; + if (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_PUBLIC_KEY, + _("no public key")); + return -1; } - if (X509_verify_cert(csc) != 1) { - g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION, - _("Failed to verify host-key document")); - return FALSE; + if (OBJ_find_sigid_algs(X509_get_signature_nid(subject), NULL, + &pkey_nid) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_SIGNATURE_ALGORITHM, + _("unsupported signature algorithm")); + return -1; } - return TRUE; + if (EVP_PKEY_type(pkey_nid) != EVP_PKEY_base_id(pkey)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_SIGNATURE_ALGORITHM_MISMATCH, + _("signature algorithm mismatch")); + return -1; + } + + return 0; } -static X509 *load_certificate(const gchar *path, GError **err) +static X509_CRL *load_crl_from_bio(BIO *bio) { - g_autoptr(X509) ret = NULL; - g_autoptr(BIO) bio = BIO_new_file(path, "rb"); + g_autoptr(X509_CRL) crl = PEM_read_bio_X509_CRL(bio, NULL, 0, NULL); + if (crl) + return g_steal_pointer(&crl); + ERR_clear_error(); + BIO_reset(bio); + + /* maybe the CRL is stored in DER format */ + crl = d2i_X509_CRL_bio(bio, NULL); + if (crl) + return g_steal_pointer(&crl); + return NULL; +} + +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 gint load_crl_from_web(const gchar *url, X509_CRL **crl, GError **err) +{ + 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, err); + if (!data) { + g_prefix_error(err, _("unable to download CRL: ")); + return -1; + } + tmp_crl = GByteArray_to_X509_CRL(data); + if (!tmp_crl) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CRL_DOWNLOAD_FAILED, + _("unable to load CRL from '%s'"), url); + return -1; + } + *crl = g_steal_pointer(&tmp_crl); + return 0; +} + +static BIO *bio_read_from_file(const char *path) +{ + g_autoptr(BIO) bio = BIO_new_file(path, "r"); + + if (!bio) + return NULL; + + return g_steal_pointer(&bio); +} + +/* 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. + */ +X509 *load_cert_from_file(const char *path, GError **err) +{ + g_autoptr(BIO) bio = bio_read_from_file(path); + g_autoptr(X509) cert = NULL; if (!bio) { g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, - _("Failed to read host-key document: '%s'"), path); + _("unable to read certificate: '%s'"), path); return NULL; } - ret = PEM_read_bio_X509(bio, NULL, 0, NULL); - if (!ret) { + cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (cert) + return g_steal_pointer(&cert); + ERR_clear_error(); + BIO_reset(bio); + + /* maybe the certificate is stored in DER format */ + cert = d2i_X509_bio(bio, NULL); + if (cert) + return g_steal_pointer(&cert); + + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("unable to load certificate: '%s'"), path); + return NULL; +} + +/* @crl_paths is allowed to be NULL */ +static int load_crls_to_store(X509_STORE *store, const gchar *const *crl_paths, + gboolean err_out_empty_crls, GError **err) +{ + for (const gchar *const *iterator = crl_paths; + iterator != NULL && *iterator != NULL; iterator++) { + const gchar *crl_path = *iterator; + X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + int count; + + g_assert(crl_path); + + if (!lookup) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_CRL, + _("unable to load CRL from: '%s'"), crl_path); + return -1; + } + } + + return 0; +} + +/* 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 gint x509_name_entry_get_data0(X509_NAME_ENTRY *entry, const guchar **data, + gsize *data_len) +{ + const ASN1_STRING *asn1_str; + gint 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 = (gsize)tmp_data_len; + return 0; +} + +/* The caller must not free *data! */ +static gint x509_name_get_data0_by_NID(X509_NAME *name, gint nid, + const guchar **data, gsize *data_len) +{ + + X509_NAME_ENTRY *entry = NULL; + gint 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_get_data0(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, gint nid, + const gchar *y) +{ + const guchar *data = NULL; + gsize y_len = strlen(y); + gsize data_len; + + if (x509_name_get_data0_by_NID(name, nid, &data, &data_len) < 0) + return FALSE; + + if (data_len != y_len) + return FALSE; + + return memcmp(data, y, data_len) == 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); + gint x_len = ASN1_STRING_length(x_data); + gint 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) +{ + gint x_count = X509_NAME_entry_count(x); + gint y_count = X509_NAME_entry_count(y); + + if (x != y && (!x || !y)) + return FALSE; + + if (x_count != y_count) + return FALSE; + + for (gint i = 0; i < x_count; i++) { + const X509_NAME_ENTRY *entry_i = X509_NAME_get_entry(x, i); + gboolean entry_found = FALSE; + + for (gint 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; +} + +/* 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 */ + gint entry_count = X509_NAME_entry_count(subject); + g_autofree gchar *data_str = NULL; + const guchar *data; + gsize 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_get_data0_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 gchar *)data, data_len); + if (!g_str_has_suffix(data_str, + PV_IBM_Z_SUBJECT_ORGANIZATIONONAL_UNIT_NAME_SUFFIX)) + return FALSE; + + return TRUE; +} + +static X509_NAME *x509_name_reorder_attributes(const X509_NAME *name, const gint nids[], + gsize nids_len) +{ + gint entry_count = X509_NAME_entry_count(name); + g_autoptr(X509_NAME) ret = NULL; + + if (entry_count < 0) + return NULL; + + if (nids_len != (gsize) entry_count) + return NULL; + + ret = X509_NAME_new(); + if (!ret) + g_abort(); + + for (gsize i = 0; i < nids_len; i++) { + const X509_NAME_ENTRY *entry = NULL; + gint nid = nids[i]; + gint 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); +} + +/* 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 converts a correct X509_NAME into the broken one. The caller is + * responsible to free the returned value. + */ +X509_NAME *c2b_name(const X509_NAME *name) +{ + gint 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); +} + +/* Verify that: subject(issuer) == issuer(crl) and SKID(issuer) == AKID(crl) */ +static gint check_crl_issuer(X509_CRL *crl, X509 *issuer, GError **err) +{ + 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 = X509_NAME_oneline(issuer_subject, + NULL, 0); + g_autofree char *crl_issuer_str = X509_NAME_oneline(crl_issuer, NULL, 0); + g_set_error(err, PV_CRYPTO_ERROR, - PV_CRYPTO_ERROR_READ_CERTIFICATE, - _("Failed to load host-key document: '%s'"), path); + PV_CRYPTO_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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + _("AKID mismatch")); + return -1; + } + + return 0; +} + +/* 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 doesn't verify whether @cert is allowed to issue a + * CRL. Returns 0 if @crl is valid and issued by @cert, otherwise -1. + */ +gint check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + gint verify_flags, GError **err) +{ + EVP_PKEY *pkey = X509_get0_pubkey(cert); + + g_assert(crl); + + if (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_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, err) < 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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_CRL_SIGNATURE_INVALID, + _("signature is not valid")); + return -1; + } + g_assert(verify_flags == 0); + 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 *store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + GError **err) +{ + g_autoptr(STACK_OF_X509_CRL) ret = NULL; + const gint verify_flags = 0; + X509_NAME *subject = NULL; + + subject = X509_get_subject_name(cert); + if (!subject) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_MALFORMED_CERTIFICATE, + _("certificate is malformed")); + return NULL; + } + + ret = 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 = c2b_name(subject); + + ret = X509_STORE_CTX_get1_crls(ctx, broken_subject); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRL, + _("no CRL found")); + return NULL; + } + } + + /* Filter out non-valid CRLs for @cert */ + for (gint 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 (check_crl_valid_for_cert(crl, cert, verify_flags, err) < 0) { + g_assert(err); + g_warning(_("CRL is not valid: %s"), (*err)->message); + g_clear_error(err); + + /* 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRL, + _("no valid CRL found")); + return NULL; + } + return g_steal_pointer(&ret); +} + +/* 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 *delete_ibm_signing_certs(STACK_OF_X509 *certs) +{ + g_autoptr(STACK_OF_X509) ret = sk_X509_new_null(); + + for (gint 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 beautfiul, 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); +} + +X509_STORE *store_setup(const gchar *root_ca_path, const gchar * const *crl_paths, + GError **err) +{ + g_autoptr(X509_STORE) store = X509_STORE_new(); + + g_assert(store); + + /* 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed")); + return NULL; + } + + count = X509_load_cert_file(lookup, root_ca_path, X509_FILETYPE_PEM); + if (count > 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_ROOT_CA, + _("multiple certificates in one PEM file is not supported: '%s'"), + root_ca_path); + return NULL; + } else if (count < 1) { + count = X509_load_cert_file(lookup, root_ca_path, + X509_FILETYPE_ASN1); + if (count != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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, err) < 0) + return NULL; + + return g_steal_pointer(&store); +} + +int store_set_verify_param(X509_STORE *store, GError **err) +{ + 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 -> (DigiCert) + * intermediate CA -> (DigiCert) 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed")); + return -1; +} + +/* @cert_paths must contain at least one element, otherwise an error is + * reported. + */ +GSList *load_certificates(const gchar *const *cert_paths, GError **err) +{ + g_autoslist(x509_with_path) ret = NULL; + + for (const gchar *const *iterator = cert_paths; + iterator != NULL && *iterator != NULL; iterator++) { + const gchar *cert_path = *iterator; + g_autoptr(X509) cert = NULL; + + g_assert(cert_path); + + cert = load_cert_from_file(cert_path, err); + if (!cert) + return NULL; + + ret = g_slist_append(ret, x509_with_path_new(cert, cert_path)); + } + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("no certificates specified")); return NULL; } return g_steal_pointer(&ret); } -EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, +static X509 *get_cert(const x509_with_path *cert_with_path, G_GNUC_UNUSED GError **err) +{ + 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); +} + +STACK_OF_X509 *get_x509_stack(const GSList *x509_with_path_list) +{ + g_autoslist(X509) certs = NULL; + g_autoptr(GError) err = NULL; + + certs = g_slist_map_x509_with_path_X509(x509_with_path_list, + get_cert, &err); + g_assert_null(err); + return g_slist_to_stack_of_X509(&certs); +} + +x509_with_path *x509_with_path_new(X509 *cert, const gchar *path) +{ + g_autoptr(x509_with_path) ret = g_new(x509_with_path, 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 x509_with_path_free(x509_with_path *cert) +{ + if (!cert) + return; + + X509_free(cert->cert); + g_free((gchar *)cert->path); + g_free(cert); +} + +x509_pair *x509_pair_new(X509 **cert, STACK_OF_X509_CRL **crls) +{ + g_autoptr(x509_pair) ret = g_new0(x509_pair, 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 x509_pair_free(x509_pair *pair) +{ + if (!pair) + return; + + sk_X509_CRL_pop_free(pair->crls, X509_CRL_free); + X509_free(pair->cert); + g_free(pair); +} + +X509_STORE_CTX *create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, + GError **err) +{ + g_autoptr(X509_STORE_CTX) ctx = X509_STORE_CTX_new(); + + if (!ctx || !X509_STORE_CTX_init(ctx, trusted, NULL, chain)) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed: %s"), + X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); + return NULL; + } + + return g_steal_pointer(&ctx); +} + +gint verify_cert(X509 *cert, X509_STORE_CTX *ctx, GError **err) +{ + gint rc; + + X509_STORE_CTX_set_cert(ctx, cert); + rc = X509_verify_cert(ctx); + if (rc != 1) { + X509 *tmp_cert = NULL; + + tmp_cert = X509_STORE_CTX_get_current_cert(ctx); + if (tmp_cert) { + g_autofree char *subj_name = X509_NAME_oneline( + X509_get_subject_name(tmp_cert), NULL, 0); + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to verify certificate '%s': %s"), + subj_name, + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(ctx))); + } else { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to verify certificate: %s"), + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(ctx))); + } + + return -1; + } + + return 0; +} + +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]; +} + +static ASN1_OCTET_STRING *digicert_assured_id_root_ca; + +const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void) +{ + pv_crypto_init(); + return digicert_assured_id_root_ca; +} + +/* Used for the caching of the downloaded CRLs */ +static GHashTable *cached_crls; + +void pv_crypto_init(void) +{ + if (digicert_assured_id_root_ca) + return; + + cached_crls = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)X509_CRL_free); + digicert_assured_id_root_ca = s2i_ASN1_OCTET_STRING( + NULL, NULL, DIGICERT_ASSURED_ID_ROOT_CA_SKID); +} + +void pv_crypto_cleanup(void) +{ + if (!digicert_assured_id_root_ca) + return; + g_clear_pointer(&cached_crls, g_hash_table_destroy); + g_clear_pointer(&digicert_assured_id_root_ca, ASN1_OCTET_STRING_free); +} + +gint check_chain_parameters(const STACK_OF_X509 *chain, + const ASN1_OCTET_STRING *skid, GError **err) +{ + const ASN1_OCTET_STRING *ca_skid = NULL; + gint len = sk_X509_num(chain); + X509 *ca = NULL; + + g_assert(skid); + /* at least one root and one leaf certificate must be defined */ + g_assert(len >= 2); + + /* get the root certificate of the chain of trust */ + ca = sk_X509_value(chain, len - 1); + if (!ca) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("no root certificate found")); + return -1; + } + + ca_skid = X509_get0_subject_key_id(ca); + if (!ca_skid) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_MALFORMED_ROOT_CA, + _("malformed root certificate")); + return -1; + } + + if (ASN1_STRING_cmp(ca_skid, skid) != 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_WRONG_CA_USED, + _("expecting DigiCert root CA to be used")); + 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 gint check_host_key_issued(X509 *cert, X509 *issuer, GError **err) +{ + const X509_NAME *issuer_subject = X509_get_subject_name(issuer); + const X509_NAME *cert_issuer = X509_get_issuer_name(cert); + 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 = + X509_NAME_oneline(issuer_subject, NULL, 0); + g_autofree char *cert_issuer_str = + X509_NAME_oneline(cert_issuer, NULL, 0); + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + _("AKID mismatch")); + return -1; + } + + if (check_signature_algo_match(X509_get0_pubkey(issuer), cert, err) < 0) + return -1; + + if (X509_verify(cert, X509_get0_pubkey(issuer)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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; + gint 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; +} + +/* 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 (gint i = 0; i < sk_GENERAL_NAME_num(general_names); i++) { + GENERAL_NAME *name = sk_GENERAL_NAME_value(general_names, i); + g_autofree const gchar *uri_str = NULL; + ASN1_STRING *uri_asn1; + const gchar *uri_data; + gint uri_data_len; + gint 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 gchar *)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, + (gsize)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; +} + +static gboolean insert_crl(X509_NAME *name, X509_CRL *crl) +{ + g_autofree gchar *key = NULL; + + g_assert(name); + + key = X509_NAME_oneline(name, NULL, 0); + if (!key) + g_abort(); + 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(X509_NAME *name) +{ + g_autoptr(X509_CRL) crl = NULL; + g_autofree gchar *key = NULL; + + g_assert(name); + + key = X509_NAME_oneline(name, NULL, 0); + if (!key) + g_abort(); + 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(X509_STORE_CTX *ctx, 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 = X509_STORE_CTX_get_current_cert(ctx); + if (!cert) + 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 = load_crl_by_cert(cert, NULL); + if (!crl) + return g_steal_pointer(&crls); + g_assert_true(insert_crl(nm, crl)); + } + if (sk_X509_CRL_push(crls, g_steal_pointer(&crl)) == 0) + g_abort(); + return g_steal_pointer(&crls); +} + +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); +} + +/* Downloaded CRLs have a higher precedence than the CRLs specified on the + * command line. + */ +static STACK_OF_X509_CRL *crls_cb(X509_STORE_CTX *ctx, 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 X509_STORE_CTX_get1_crls(ctx, nm); +} + +/* Set up CRL lookup with download support */ +void store_setup_crl_download(X509_STORE *st) +{ + X509_STORE_set_lookup_crls(st, crls_cb); +} + +/* Download a CRL using the URI specified in the distribution @crldp */ +static X509_CRL *load_crl_by_dist_point(DIST_POINT *crldp, GError **err) +{ + const gchar *uri = get_first_dp_url(crldp); + g_autoptr(X509_CRL) crl = NULL; + + if (!uri) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("no valid URL specified in distribution point")); + return NULL; + } + + if (load_crl_from_web(uri, &crl, err) < 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 *load_crl_by_cert(X509 *cert, GError **err) +{ + 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(err, PV_CRYPTO_ERROR, PV_CRYPTO_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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_FAILED_DOWNLOAD_CRL, + _("failed to download CRL")); + return NULL; +} + +STACK_OF_X509_CRL *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) { + x509_with_path *cert_with_path = iterator->data; + X509 *cert = cert_with_path->cert; + g_autoptr(X509_CRL) crl = NULL; + + g_assert(cert); + + /* ignore error */ + crl = load_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); +} + +/* 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 + */ +gint verify_host_key(X509 *host_key, GSList *issuer_pairs, + gint verify_flags, int level, GError **err) +{ + g_assert(host_key); + + const gint exp_security_bits = security_level_to_bits(level); + EVP_PKEY *pkey = X509_get0_pubkey(host_key); + gboolean successfully_checked = FALSE; + gint pkey_security_bits; + + if (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_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(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION, + _("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_get_notBefore(host_key); + const ASN1_TIME *next = X509_get_notAfter(host_key); + + if (!last || !next || check_validity_period(last, next)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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 x509_pair *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 (gint 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(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CERT_REVOKED, + _("certificate revoked")); + return -1; + } + + crl_checked = TRUE; + } + + if (!crl_checked) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("no valid CRL found")); + return -1; + } + successfully_checked = TRUE; + break; + } + } + + if (!successfully_checked) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_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; +} + +EVP_PKEY *read_ec_pubkey_cert(X509 *cert, gint nid, GError **err) { g_autoptr(EVP_PKEY) ret = NULL; - g_autoptr(X509) cert = NULL; - - cert = load_certificate(path, err); - if (!cert) - return NULL; - - if (store && !verify_certificate(store, cert, err)) { - g_prefix_error(err, - _("Failed to load host-key document: '%s': "), - path); - return NULL; - } ret = X509_get_pubkey(cert); if (!ret) { g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, - _("Failed to get public key from host-key document: '%s'"), - path); + _("Failed to get public key from host-key document")); return NULL; } if (!certificate_uses_correct_curve(ret, nid, err)) { g_prefix_error(err, - _("Failed to load host-key document: '%s': "), - path); + _("Host-key document doesn\'t use correct EC curve")); return NULL; } diff --git a/genprotimg/src/utils/crypto.h b/genprotimg/src/utils/crypto.h index 34418ed3..286cf457 100644 --- a/genprotimg/src/utils/crypto.h +++ b/genprotimg/src/utils/crypto.h @@ -11,14 +11,18 @@ #define PV_UTILS_CRYPTO_H #include +#include #include #include #include #include #include +#include #include +#include #include #include +#include #include #include "common.h" @@ -33,6 +37,9 @@ #define AES_256_XTS_TWEAK_SIZE 16 #define AES_256_XTS_KEY_SIZE 64 +#define CRL_DOWNLOAD_TIMEOUT_MS 3000 +#define CRL_DOWNLOAD_MAX_SIZE (1024 * 1024) /* in bytes */ + enum PvCryptoMode { PV_ENCRYPT, PV_DECRYPT, @@ -40,7 +47,34 @@ enum PvCryptoMode { typedef GSList HostKeyList; +/* play nice with g_autoptr */ +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; + +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); + +typedef struct { + X509 *cert; + const gchar *path; +} x509_with_path; + +x509_with_path *x509_with_path_new(X509 *cert, const gchar *path); +void x509_with_path_free(x509_with_path *cert); + +typedef struct { + X509 *cert; + STACK_OF_X509_CRL *crls; +} x509_pair; + +x509_pair *x509_pair_new(X509 **cert, STACK_OF_X509_CRL **crls); +void x509_pair_free(x509_pair *pair); + /* Register auto cleanup functions */ +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(BIGNUM, BN_free) WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIO, BIO_free_all) WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BN_CTX, BN_CTX_free) @@ -51,10 +85,18 @@ WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_CIPHER_CTX, EVP_CIPHER_CTX_free) WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_MD_CTX, EVP_MD_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) +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_pair, 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) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(x509_with_path, x509_with_path_free) union cmp_index { struct { @@ -79,8 +121,37 @@ struct cipher_parms { const Buffer *iv_or_tweak; }; -EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, - GError **err); +int check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + gint verify_flags, GError **err); +void pv_crypto_init(void); +void pv_crypto_cleanup(void); +const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void); +gint verify_host_key(X509 *host_key, GSList *issuer_pairs, + gint verify_flags, int level, GError **err); +X509 *load_cert_from_file(const char *path, GError **err); +X509_CRL *load_crl_from_file(const gchar *path, GError **err); +GSList *load_certificates(const gchar *const *cert_paths, GError **err); +STACK_OF_X509 *get_x509_stack(const GSList *x509_with_path_list); +X509_STORE *store_setup(const gchar *root_ca_path, + const gchar * const *crl_paths, + GError **err); +int store_set_verify_param(X509_STORE *store, GError **err); +X509_CRL *load_crl_by_cert(X509 *cert, GError **err); +STACK_OF_X509_CRL *try_load_crls_by_certs(GSList *certs_with_path); +gint check_chain_parameters(const STACK_OF_X509 *chain, + const ASN1_OCTET_STRING *skid, GError **err); +X509_NAME *c2b_name(const X509_NAME *name); + +STACK_OF_X509 *delete_ibm_signing_certs(STACK_OF_X509 *certs); +STACK_OF_X509_CRL *store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + GError **err); +X509_STORE_CTX *create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, + GError **err); +gint verify_cert(X509 *cert, X509_STORE_CTX *ctx, GError **err); +X509_CRL *get_first_valid_crl(X509_STORE_CTX *ctx, X509 *cert, GError **err); +void store_setup_crl_download(X509_STORE *st); +EVP_PKEY *read_ec_pubkey_cert(X509 *cert, gint nid, GError **err); + Buffer *compute_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err); Buffer *generate_aes_key(guint size, GError **err); Buffer *generate_aes_iv(guint size, GError **err); diff --git a/genprotimg/src/utils/curl.c b/genprotimg/src/utils/curl.c new file mode 100644 index 00000000..a8ef3f6c --- /dev/null +++ b/genprotimg/src/utils/curl.c @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "pv/pv_error.h" + +#include "curl.h" + +struct UserData { + GByteArray *buffer; + guint 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; + guint64 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, (guchar *)ptr, (guint)actual_size); + if (data->buffer->len > data->max_size) + return err; + + return actual_size; +} + +gint curl_init(void) +{ + if (curl_global_init(CURL_GLOBAL_ALL) != 0) + return -1; + return 0; +} + +void curl_cleanup(void) +{ + curl_global_cleanup(); +} + +GByteArray *curl_download(const gchar *url, long timeout_ms, guint max_size, + GError **err) +{ + g_autoptr(GByteArray) ret = NULL; + g_autoptr(CURL) handle = NULL; + g_autofree gchar *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", tool_name, 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_ERROR, PV_ERROR_DOWNLOAD_FAILED, + _("download failed: %s"), curl_easy_strerror(rc)); + return NULL; + } + + return g_steal_pointer(&ret); +curl_err: + g_set_error(err, PV_ERROR, + PV_ERROR_CURL_INIT_FAILED, + _("cURL initialization failed: %s"), + curl_easy_strerror(rc)); + return NULL; +} diff --git a/genprotimg/src/utils/curl.h b/genprotimg/src/utils/curl.h new file mode 100644 index 00000000..4ec1c113 --- /dev/null +++ b/genprotimg/src/utils/curl.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef PV_UTILS_LIBCURL_H +#define PV_UTILS_LIBCURL_H + +#include +#include + +#include "common.h" + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURL, curl_easy_cleanup) + +GByteArray *curl_download(const gchar *url, long timeout_ms, guint max_size, + GError **err); +gint curl_init(void); +void curl_cleanup(void); + +#endif /* PV_UTILS_LIBCURL_H */