From f524b0b8dcc920e89d6752cdb4a7ebd1b0811ca9 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Tue, 26 Nov 2024 17:29:19 +0000 Subject: [PATCH] rust/pvimg: Add 'tools/check_hostkeydoc' script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is done in preparation for the Rust port of genprotimg. Reviewed-by: Steffen Eiden Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner --- rust/Makefile | 4 + rust/pvimg/tools/check_hostkeydoc | 303 ++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100755 rust/pvimg/tools/check_hostkeydoc diff --git a/rust/Makefile b/rust/Makefile index 4233f570..3ef8d862 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -44,6 +44,8 @@ pvimg-bootloaders: $(MAKE) -C pvimg/boot/ .PHONY: pvimg-bootloaders +PVIMG_PKGDATADIR := $(TOOLS_DATADIR)/pvimg +export PVIMG_PKGDATADIR BUILD_TARGETS += $(PV_BUILD_TARGETS) pvimg-bootloaders INSTALL_TARGETS += pvimg-bootloaders @@ -83,6 +85,8 @@ all: $(BUILD_TARGETS) install: $(INSTALL_TARGETS) $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvattest/tools/pvextract-hdr $(DESTDIR)$(USRBINDIR) + $(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR) $(MAKE) -C pvimg/boot install print-rust-targets: diff --git a/rust/pvimg/tools/check_hostkeydoc b/rust/pvimg/tools/check_hostkeydoc new file mode 100755 index 00000000..de8178ba --- /dev/null +++ b/rust/pvimg/tools/check_hostkeydoc @@ -0,0 +1,303 @@ +#!/bin/sh +# +# check_hostkeydoc - Verify an IBM Secure Execution host key document +# +# Sample script to verify that a host key document is genuine by +# verifying the issuer, the validity date and the signature. +# Optionally verify the full trust chain using a CA certificate. +# +# Sample invocation: +# +# ./check_hostkeydoc HKD1234.crt ibm-z-host-key-signing.crt -c DigiCertCA.crt -r ibm-z-host-key.crl +# +# 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. + +# Allocate temporary files +ISSUER_PUBKEY_FILE=$(mktemp) +SIGNATURE_FILE=$(mktemp) +BODY_FILE=$(mktemp) +ISSUER_DN_FILE=$(mktemp) +SUBJECT_DN_FILE=$(mktemp) +DEF_ISSUER_ARMONK_DN_FILE=$(mktemp) +DEF_ISSUER_POUGHKEEPSIE_DN_FILE=$(mktemp) +CANONICAL_ISSUER_DN_FILE=$(mktemp) +CRL_SERIAL_FILE=$(mktemp) + +# Cleanup on exit +cleanup() +{ + rm -f "$ISSUER_PUBKEY_FILE" "$SIGNATURE_FILE" "$BODY_FILE" \ + "$ISSUER_DN_FILE" "$SUBJECT_DN_FILE" "$DEF_ISSUER_ARMONK_DN_FILE" "$DEF_ISSUER_POUGHKEEPSIE_DN_FILE" \ + "$CANONICAL_ISSUER_DN_FILE" "$CRL_SERIAL_FILE" +} +trap cleanup EXIT + +# Enhanced error checking for bash +if [ -n "${BASH}" ]; then + # shellcheck disable=SC3040 + set -o posix + # shellcheck disable=SC3040 + set -o pipefail + # shellcheck disable=SC3040 + set -o nounset +fi +set -e + +# Usage +usage() +{ + cat <<-EOF +Usage: $(basename "$1") [-d] [-c CA-cert] [-r CRL] host-key-doc signing-key-cert + +Verify an IBM Secure Execution host key document against +a signing key. + +Options: +-d disable default issuer check of host-key-doc +-c CA-cert trusted CA certificate +-r CRL list of revoked host-key-docs + +Note that in order to have the full trust chain verified +it is necessary to provide the issuing CA's certificate. +The default issuer check may be disabled if a non-default +signing key certificate needs to be verified against the +CA certificate. + +EOF +} + +check_verify_chain() +{ + # Verify certificate chain in case a CA certificate file/bundle + # was specified on the command line. + if [ -z "$2" ]; then + cat >&2 <<-EOF +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +No CA certificate specified! Skipping trust chain verification. +Make sure that '$1' is a valid certificate. +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +EOF + else + openssl verify -crl_download -crl_check "$2" && + openssl verify -crl_download -crl_check -untrusted "$2" "$1" || + exit 1 + fi +} + +extract_pubkey() +{ + openssl x509 -in "$1" -pubkey -noout >"$2" +} + +extract_signature() +{ + # Assuming that the last field is the signature + SIGOFFSET=$(openssl asn1parse -in "$1" | tail -1 | cut -d : -f 1) + + openssl asn1parse -in "$1" -out "$2" -strparse "$SIGOFFSET" -noout +} + +extract_body() +{ + # Assuming that the first field is the full cert body + SIGOFFSET=$(openssl asn1parse -in "$1" | head -2 | tail -1 | cut -d : -f 1) + + openssl asn1parse -in "$1" -out "$2" -strparse "$SIGOFFSET" -noout +} + +verify_signature() +{ + # Assuming that the signature algorithm is SHA512 with RSA + openssl sha512 -verify "$1" -signature "$2" "$3" +} + +canonical_dn() +{ + OBJTYPE=$1 + OBJ=$2 + DNTYPE=$3 + OUTPUT=$4 + + openssl "$OBJTYPE" -in "$OBJ" -"$DNTYPE" -noout -nameopt multiline | + LC_ALL=C sort | grep -v "$DNTYPE"= >"$OUTPUT" +} + +default_issuer_armonk() +{ + cat <<-EOF + commonName = International Business Machines Corporation + countryName = US + localityName = Armonk + organizationName = International Business Machines Corporation + organizationalUnitName = Key Signing Service + stateOrProvinceName = New York +EOF +} + +default_issuer_pougkeepsie() +{ + cat <<-EOF + commonName = International Business Machines Corporation + countryName = US + localityName = Poughkeepsie + organizationName = International Business Machines Corporation + organizationalUnitName = Key Signing Service + stateOrProvinceName = New York +EOF +} + +# As organizationalUnitName can have an arbitrary prefix but must +# end with "Key Signing Service" let's normalize the OU name by +# stripping off the prefix +verify_default_issuer() +{ + default_issuer_pougkeepsie >"$DEF_ISSUER_POUGHKEEPSIE_DN_FILE" + default_issuer_armonk >"$DEF_ISSUER_ARMONK_DN_FILE" + + sed "s/\(^[ ]*organizationalUnitName[ ]*=[ ]*\).*\(Key Signing Service$\)/\1\2/" \ + "$ISSUER_DN_FILE" >"$CANONICAL_ISSUER_DN_FILE" + + if ! { + diff "$CANONICAL_ISSUER_DN_FILE" "$DEF_ISSUER_POUGHKEEPSIE_DN_FILE" || + diff "$CANONICAL_ISSUER_DN_FILE" "$DEF_ISSUER_ARMONK_DN_FILE" + } >/dev/null 2>&1; then + echo Incorrect default issuer >&2 && exit 1 + fi +} + +verify_issuer_files() +{ + if [ "$1" -eq 1 ]; then + verify_default_issuer + fi +} + +cert_time() +{ + DATE=$(openssl x509 -in "$1" -"$2" -noout | sed "s/^.*=//") + + date -d "$DATE" +%s +} + +crl_time() +{ + DATE=$(openssl crl -in "$1" -"$2" -noout | sed "s/^.*=//") + + date -d "$DATE" +%s +} + +verify_dates() +{ + START="$1" + END="$2" + MSG="${3:-Certificate}" + NOW=$(date +%s) + + if [ "$START" -le "$NOW" ] && [ "$NOW" -le "$END" ]; then + echo "${MSG} dates are OK" + else + echo "${MSG} date verification failed" >&2 && exit 1 + fi +} + +crl_serials() +{ + openssl crl -in "$1" -text -noout | + grep "Serial Number" >"$CRL_SERIAL_FILE" +} + +check_serial() +{ + CERT_SERIAL=$(openssl x509 -in "$1" -noout -serial | cut -d = -f 2) + + grep -q "$CERT_SERIAL" "$CRL_SERIAL_FILE" +} + +check_file() +{ + [ -e "$1" ] || + (echo "File '$1' not found" >&2 && exit 1) +} + +# check args +CRL_FILE= +CA_FILE= +CHECK_DEFAULT_ISSUER=1 + +while getopts 'dr:c:h' opt; do + case $opt in + d) CHECK_DEFAULT_ISSUER=0 ;; + r) CRL_FILE=$OPTARG ;; + c) CA_FILE=$OPTARG ;; + h) + usage "$0" + exit 0 + ;; + ?) + usage "$0" + exit 1 + ;; + esac +done +shift "$((OPTIND - 1))" + +if [ $# -ne 2 ]; then + usage "$0" >&2 + exit 1 +fi + +HKD_FILE=$1 +HKSK_FILE=$2 + +# Check whether all specified files exist +check_file "$HKD_FILE" +check_file "$HKSK_FILE" +# CA and CRL are optional arguments +[ -n "$CA_FILE" ] && check_file "$CA_FILE" +[ -n "$CRL_FILE" ] && check_file "$CRL_FILE" + +# Check trust chain +check_verify_chain "$HKSK_FILE" "$CA_FILE" + +# Verify host key document signature +printf "Checking host key document signature: " +extract_pubkey "$HKSK_FILE" "$ISSUER_PUBKEY_FILE" && + extract_signature "$HKD_FILE" "$SIGNATURE_FILE" && + extract_body "$HKD_FILE" "$BODY_FILE" && + verify_signature "$ISSUER_PUBKEY_FILE" "$SIGNATURE_FILE" "$BODY_FILE" || + exit 1 + +# Verify the issuer +canonical_dn x509 "$HKD_FILE" issuer "$ISSUER_DN_FILE" +canonical_dn x509 "$HKSK_FILE" subject "$SUBJECT_DN_FILE" +verify_issuer_files $CHECK_DEFAULT_ISSUER + +# Verify dates +verify_dates "$(cert_time "$HKD_FILE" startdate)" "$(cert_time "$HKD_FILE" enddate)" + +# Check CRL if specified +if [ -n "$CRL_FILE" ]; then + printf "Checking CRL signature: " + extract_signature "$CRL_FILE" "$SIGNATURE_FILE" && + extract_body "$CRL_FILE" "$BODY_FILE" && + verify_signature "$ISSUER_PUBKEY_FILE" "$SIGNATURE_FILE" "$BODY_FILE" || + exit 1 + + printf "CRL " + canonical_dn crl "$CRL_FILE" issuer "$ISSUER_DN_FILE" + canonical_dn x509 "$HKSK_FILE" subject "$SUBJECT_DN_FILE" + verify_issuer_files $CHECK_DEFAULT_ISSUER + + verify_dates "$(crl_time "$CRL_FILE" lastupdate)" "$(crl_time "$CRL_FILE" nextupdate)" 'CRL' + + crl_serials "$CRL_FILE" + check_serial "$HKD_FILE" && + echo "Certificate is revoked, do not use it anymore!" >&2 && + exit 1 +fi + +# We made it +echo All checks requested for \'"$HKD_FILE"\' were successful