File s390-tools-sles15sp3-04-genprotimg-add-host-key-document-verification-suppor.patch of Package s390-tools.23193

Subject: [PATCH] [FEAT VS2010] genprotimg: add host-key document verification support
From: Marc Hartmayer <mhartmay@linux.ibm.com>

Summary:     genprotimg: add host-key document verification
Description: Add host-key document verification support to genprotimg. This
             ensures that a host-key document is genuine and provided by
             IBM. For this the user must provide the IBM Z signing key, the
             intermediate CA certificate (signed by the root CA used) so a
             chain of trust starting from the host-key document and ending in
             the root CA can be established.
Upstream-ID: 074de1e14ed785c18f55ecf9762ac3f5de3465b4
Problem-ID:  VS2010

Upstream-Description:

             genprotimg: add host-key document verification support

             Add host-key document verification support to genprotimg. This ensures
             that a host-key document is genuine and provided by IBM. For this the
             user must provide the IBM Z signing key, the intermediate CA
             certificate (signed by the root CA used) so a chain of trust starting
             from the host-key document and ending in the root CA can be
             established.

             By default, genprotimg tries to download all revocation lists needed
             by looking up in the corresponding certificate on how CRL information
             can be obtained (see https://tools.ietf.org/html/rfc5280#section-4.2.1.13
             for details).

             Acked-by: Patrick Steuer <patrick.steuer@de.ibm.com>
             Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
             Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com>


Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Index: s390-tools-service/genprotimg/man/genprotimg.8
===================================================================
--- s390-tools-service.orig/genprotimg/man/genprotimg.8
+++ s390-tools-service/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 virtual
 .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 virtual
 .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
 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
Index: s390-tools-service/genprotimg/src/Makefile
===================================================================
--- s390-tools-service.orig/genprotimg/src/Makefile
+++ s390-tools-service/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 --sil
 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
 		"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 $@
Index: s390-tools-service/genprotimg/src/genprotimg.c
===================================================================
--- s390-tools-service.orig/genprotimg/src/genprotimg.c
+++ s390-tools-service/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
 		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();
+}
Index: s390-tools-service/genprotimg/src/include/pv_crypto_def.h
===================================================================
--- s390-tools-service.orig/genprotimg/src/include/pv_crypto_def.h
+++ s390-tools-service/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];
Index: s390-tools-service/genprotimg/src/pv/pv_args.c
===================================================================
--- s390-tools-service.orig/genprotimg/src/pv/pv_args.c
+++ s390-tools-service/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(PvA
 		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(con
 {
 	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,
 			_("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,
 		  .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);
Index: s390-tools-service/genprotimg/src/pv/pv_args.h
===================================================================
--- s390-tools-service.orig/genprotimg/src/pv/pv_args.h
+++ s390-tools-service/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;
Index: s390-tools-service/genprotimg/src/pv/pv_error.h
===================================================================
--- s390-tools-service.orig/genprotimg/src/pv/pv_error.h
+++ s390-tools-service/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
Index: s390-tools-service/genprotimg/src/pv/pv_image.c
===================================================================
--- s390-tools-service.orig/genprotimg/src/pv/pv_image.c
+++ s390-tools-service/genprotimg/src/pv/pv_image.c
@@ -10,6 +10,7 @@
 #include <errno.h>
 #include <glib.h>
 #include <openssl/evp.h>
+#include <openssl/x509.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -138,22 +139,18 @@ static EVP_PKEY *pv_img_get_cust_pub_pri
 	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;
-
-		g_assert(path);
+		X509 *cert = cert_with_path->cert;
 
-		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(PvI
 	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
 	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
 	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;
Index: s390-tools-service/genprotimg/src/utils/crypto.c
===================================================================
--- s390-tools-service.orig/genprotimg/src/utils/crypto.c
+++ s390-tools-service/genprotimg/src/utils/crypto.c
@@ -16,6 +16,11 @@
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 #include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/x509_vfy.h>
+#include <openssl/err.h>
+#include <stdio.h>
 #include <stdint.h>
 #include <string.h>
 
@@ -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
 	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)
+	gint pkey_nid;
+
+	if (!pkey) {
+		g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_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(err, PV_CRYPTO_ERROR,
+			    PV_CRYPTO_ERROR_INVALID_SIGNATURE_ALGORITHM,
+			    _("unsupported signature algorithm"));
+		return -1;
+	}
+
+	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_CRL *load_crl_from_bio(BIO *bio)
+{
+	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();
 
-	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;
+	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;
+}
 
-	if (X509_verify_cert(csc) != 1) {
-		g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION,
-			    _("Failed to verify host-key document"));
+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,
+			    _("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();
+	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 *load_certificate(const gchar *path, GError **err)
+static X509_NAME *x509_name_reorder_attributes(const X509_NAME *name, const gint nids[],
+					       gsize nids_len)
 {
-	g_autoptr(X509) ret = NULL;
-	g_autoptr(BIO) bio = BIO_new_file(path, "rb");
+	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);
 
-	if (!bio) {
 		g_set_error(err, PV_CRYPTO_ERROR,
-			    PV_CRYPTO_ERROR_READ_CERTIFICATE,
-			    _("Failed to read 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 = PEM_read_bio_X509(bio, NULL, 0, NULL);
+	ret = X509_STORE_CTX_get1_crls(ctx, subject);
 	if (!ret) {
-		g_set_error(err, PV_CRYPTO_ERROR,
-			    PV_CRYPTO_ERROR_READ_CERTIFICATE,
-			    _("Failed to load host-key document: '%s'"), path);
+		/* 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);
 }
 
-EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path,
-			      GError **err)
+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);
+}
+
+static X509 *get_cert(const x509_with_path *cert_with_path, G_GNUC_UNUSED GError **err)
 {
-	g_autoptr(EVP_PKEY) ret = NULL;
 	g_autoptr(X509) cert = NULL;
 
-	cert = load_certificate(path, err);
+	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;
+	}
 
-	if (store && !verify_certificate(store, cert, err)) {
-		g_prefix_error(err,
-			       _("Failed to load host-key document: '%s': "),
-			       path);
+	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;
+
 	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;
 	}
 
Index: s390-tools-service/genprotimg/src/utils/crypto.h
===================================================================
--- s390-tools-service.orig/genprotimg/src/utils/crypto.h
+++ s390-tools-service/genprotimg/src/utils/crypto.h
@@ -11,14 +11,18 @@
 #define PV_UTILS_CRYPTO_H
 
 #include <glib.h>
+#include <openssl/asn1.h>
 #include <openssl/bio.h>
 #include <openssl/bn.h>
 #include <openssl/ec.h>
 #include <openssl/ecdh.h>
 #include <openssl/evp.h>
+#include <openssl/ossl_typ.h>
 #include <openssl/rand.h>
+#include <openssl/safestack.h>
 #include <openssl/sha.h>
 #include <openssl/x509.h>
+#include <openssl/x509v3.h>
 #include <stdint.h>
 
 #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(EV
 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);
Index: s390-tools-service/genprotimg/src/utils/curl.c
===================================================================
--- /dev/null
+++ s390-tools-service/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 <stdio.h>
+#include <glib.h>
+#include <glib/gtypes.h>
+#include <curl/curl.h>
+
+#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;
+}
Index: s390-tools-service/genprotimg/src/utils/curl.h
===================================================================
--- /dev/null
+++ s390-tools-service/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 <glib.h>
+#include <curl/curl.h>
+
+#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 */
openSUSE Build Service is sponsored by