import keylime-6.5.1-1.el9_1.4

This commit is contained in:
CentOS Sources 2023-01-23 09:32:07 -05:00 committed by Stepan Oksanichenko
parent 6dc174d7f0
commit 443bb1fa5a
5 changed files with 940 additions and 2 deletions

View File

@ -0,0 +1,42 @@
From de8bbb63dca836bcf07586186218c3227749d2e7 Mon Sep 17 00:00:00 2001
From: Stefan Berger <stefanb@linux.ibm.com>
Date: Fri, 4 Nov 2022 11:20:15 -0400
Subject: [PATCH] ima: Fix log evaluation on quick-succession execution of
scripts
In case the attested-to host quickly executes files measured by IMA we may
run into the case that the keylime agent retrieved the state of the PCR at
'state n' but then IMA appended the log with several entries leading to a
log representing 'state n + x' (with x>=1), which may not just be the
previously anticipated single additional entry (state n+1). Therefore,
remove the check for the number of entries in the log and always compare
the running_hash that iterative attestation was resumed with against the
provided PCR value from 'state n'.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
keylime/ima/ima.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/keylime/ima/ima.py b/keylime/ima/ima.py
index b88b1af..c4c2ae6 100644
--- a/keylime/ima/ima.py
+++ b/keylime/ima/ima.py
@@ -299,9 +299,11 @@ def _process_measurement_list(
# Iterative attestation may send us no log [len(lines) == 1]; compare last know PCR 10 state
# against current PCR state.
- # Since IMA log append and PCR extend is not atomic, we may get a quote that does not yet take
- # into account the next appended measurement's [len(lines) == 2] PCR extension.
- if not found_pcr and len(lines) <= 2:
+ # Since IMA's append to the log and PCR extend as well as Keylime's retrieval of the quote, reading
+ # of PCR 10 and retrieval of the log are not atomic, we may get a quote that does not yet take into
+ # account the next-appended measurements' [len(lines) >= 2] PCR extension(s). In fact, the value of
+ # the PCR may lag the log by several entries.
+ if not found_pcr:
found_pcr = running_hash == pcrval_bytes
for linenum, line in enumerate(lines):
--
2.37.3

View File

@ -0,0 +1,70 @@
From 2fee03637d3a1d0c9c004b958af69f4b0e4b57f3 Mon Sep 17 00:00:00 2001
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
Date: Fri, 4 Nov 2022 17:41:31 +0100
Subject: [PATCH 2/2] tpm_bootlog_enrich: Get DevicePath length from
LengthOfDevicePath
In enrich_device_path(), get the length of DevicePath from the field
LengthOfDevicePath instead of calculating the length from the bytes
array.
This avoids a segmentation fault when processing the measured boot event
log in create_mb_refstate script.
This is called for the events "EV_EFI_BOOT_SERVICES_APPLICATION",
"EV_EFI_BOOT_SERVICES_DRIVER", and "EV_EFI_RUNTIME_SERVICES_DRIVER".
Fixes: #1153
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
---
keylime/tpm_bootlog_enrich.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/keylime/tpm_bootlog_enrich.py b/keylime/tpm_bootlog_enrich.py
index ef8e9f7..621bc67 100644
--- a/keylime/tpm_bootlog_enrich.py
+++ b/keylime/tpm_bootlog_enrich.py
@@ -46,14 +46,14 @@ yaml.add_representer(hexint, representer)
efivarlib_functions = CDLL(config.LIBEFIVAR)
-def getDevicePath(b):
- ret = efivarlib_functions.efidp_format_device_path(0, 0, b, len(b))
+def getDevicePath(b, l):
+ ret = efivarlib_functions.efidp_format_device_path(0, 0, b, l)
if ret < 0:
raise Exception(f"getDevicePath: efidp_format_device_path({b}) returned {ret}")
s = create_string_buffer(ret + 1)
- ret = efivarlib_functions.efidp_format_device_path(s, ret + 1, b, len(b))
+ ret = efivarlib_functions.efidp_format_device_path(s, ret + 1, b, l)
if ret < 0:
raise Exception(f"getDevicePath: efidp_format_device_path({b}) returned {ret}")
@@ -174,7 +174,7 @@ def getVar(event, b):
c = w.decode("utf-16", errors="ignore")
description += c
r["Description"] = description
- devicePath = getDevicePath(b[i:])
+ devicePath = getDevicePath(b[i:], len(b[i:]))
r["DevicePath"] = devicePath
return r
return None
@@ -184,10 +184,11 @@ def enrich_device_path(d: dict) -> None:
if isinstance(d.get("DevicePath"), str):
try:
b = bytes.fromhex(d["DevicePath"])
+ l = int(d["LengthOfDevicePath"])
except Exception:
return
try:
- p = getDevicePath(b)
+ p = getDevicePath(b, l)
# Deal with garbage devicePath
except Exception:
return
--
2.38.1

View File

@ -0,0 +1,672 @@
From 57c67e2b359e9544ecd5a0ba264adf7aa7c67991 Mon Sep 17 00:00:00 2001
From: rpm-build <rpm-build>
Date: Mon, 14 Nov 2022 21:47:40 -0300
Subject: [PATCH 3/3] Backport upsteam PR#1156
From: https://github.com/keylime/keylime/pull/1156
We had partially addressed the issue we encountered when parsing some
certificates with python-cryptography by writing cert_utils module that
provided a single helper to parse the pubkey from a certificate.
However, we still were doing the EK validation in tpm_main using
python-cryptography, which means we would fall into the same parsing
problem again, since during the validation it would read all the certs
in the tpm cert store to check if it is signing the presented EK.
We address this issue by moving the EK cert verification to cert_utils:
we use the same approach as before, i.e. we parse the cert with pyasn1
when python-cryptography fails, and then use python-cryptography to do
the actual signature verification, as before.
By moving the method to cert_utils, it also becomes simpler to test it,
so in this commit we more tests to verify the methods work as expected.
Additionally, the updated EK verification is also capable of handling
ECDSA signatures
---
keylime.conf | 2 +
keylime/cert_utils.py | 144 ++++++++++++++++++++++++++++++++++--
keylime/registrar_common.py | 7 +-
keylime/tenant.py | 21 ++----
keylime/tpm/tpm_main.py | 47 +-----------
keylime/tpm_ek_ca.py | 10 +--
scripts/ek-openssl-verify | 98 ++++++++++++++++++++++++
test/run_tests.sh | 8 +-
test/test_cert_utils.py | 142 ++++++++++++++++++++++++++++++-----
9 files changed, 380 insertions(+), 99 deletions(-)
create mode 100755 scripts/ek-openssl-verify
diff --git a/keylime.conf b/keylime.conf
index 331e57a..d896f9f 100644
--- a/keylime.conf
+++ b/keylime.conf
@@ -501,6 +501,8 @@ require_ek_cert = True
# PROVKEYS - contains a json document containing EK, EKcert, and AIK from the
# provider. EK and AIK are in PEM format. The EKcert is in base64 encoded
# DER format.
+# TPM_CERT_STORE - contains the path to the TPM certificates store, e.g.:
+# "/var/lib/keylime/tpm_cert_store".
#
# Set to blank to disable this check. See warning above if require_ek_cert
# is "False".
diff --git a/keylime/cert_utils.py b/keylime/cert_utils.py
index d014aed..d2fc54d 100644
--- a/keylime/cert_utils.py
+++ b/keylime/cert_utils.py
@@ -1,13 +1,143 @@
-from cryptography.hazmat.primitives.serialization import load_der_public_key
+import io
+import os.path
+import subprocess
+import sys
+
+from cryptography import exceptions as crypto_exceptions
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import ec, padding
+from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from pyasn1.codec.der import decoder, encoder
-from pyasn1_modules import rfc2459
+from pyasn1_modules import pem, rfc2459
+from keylime import config, keylime_logging, tpm_ek_ca
# Issue #944 -- python-cryptography won't parse malformed certs,
# such as some Nuvoton ones we have encountered in the field.
# Unfortunately, we still have to deal with such certs anyway.
-# Let's read the EK cert with pyasn1 instead of python-cryptography.
-def read_x509_der_cert_pubkey(der_cert_data):
- """Returns the public key of a DER-encoded X.509 certificate"""
- der509 = decoder.decode(der_cert_data, asn1Spec=rfc2459.Certificate())[0]
- return load_der_public_key(encoder.encode(der509["tbsCertificate"]["subjectPublicKeyInfo"]))
+
+# Here we provide some helpers that use pyasn1 to parse the certificates
+# when parsing them with python-cryptography fails, and in this case, we
+# try to read the parsed certificate again into python-cryptograhy.
+
+logger = keylime_logging.init_logging("cert_utils")
+
+
+def x509_der_cert(der_cert_data: bytes):
+ """Load an x509 certificate provided in DER format
+ :param der_cert_data: the DER bytes of the certificate
+ :type der_cert_data: bytes
+ :returns: cryptography.x509.Certificate
+ """
+ try:
+ return x509.load_der_x509_certificate(data=der_cert_data, backend=default_backend())
+ except Exception as e:
+ logger.warning("Failed to parse DER data with python-cryptography: %s", e)
+ pyasn1_cert = decoder.decode(der_cert_data, asn1Spec=rfc2459.Certificate())[0]
+ return x509.load_der_x509_certificate(data=encoder.encode(pyasn1_cert), backend=default_backend())
+
+
+def x509_pem_cert(pem_cert_data: str):
+ """Load an x509 certificate provided in PEM format
+ :param pem_cert_data: the base-64 encoded PEM certificate
+ :type pem_cert_data: str
+ :returns: cryptography.x509.Certificate
+ """
+ try:
+ return x509.load_pem_x509_certificate(data=pem_cert_data.encode("utf-8"), backend=default_backend())
+ except Exception as e:
+ logger.warning("Failed to parse PEM data with python-cryptography: %s", e)
+ # Let's read the DER bytes from the base-64 PEM.
+ der_data = pem.readPemFromFile(io.StringIO(pem_cert_data))
+ # Now we can load it as we do in x509_der_cert().
+ pyasn1_cert = decoder.decode(der_data, asn1Spec=rfc2459.Certificate())[0]
+ return x509.load_der_x509_certificate(data=encoder.encode(pyasn1_cert), backend=default_backend())
+
+
+def verify_ek(ekcert, tpm_cert_store=config.get("tenant", "tpm_cert_store")):
+ """Verify that the provided EK certificate is signed by a trusted root
+ :param ekcert: The Endorsement Key certificate in DER format
+ :returns: True if the certificate can be verified, False otherwise
+ """
+ try:
+ trusted_certs = tpm_ek_ca.cert_loader(tpm_cert_store)
+ except Exception as e:
+ logger.warning("Error loading trusted certificates from the TPM cert store: %s", e)
+ return False
+
+ try:
+ ek509 = x509_der_cert(ekcert)
+ for cert_file, pem_cert in trusted_certs.items():
+ signcert = x509_pem_cert(pem_cert)
+ if ek509.issuer != signcert.subject:
+ continue
+
+ signcert_pubkey = signcert.public_key()
+ try:
+ if isinstance(signcert_pubkey, RSAPublicKey):
+ signcert_pubkey.verify(
+ ek509.signature,
+ ek509.tbs_certificate_bytes,
+ padding.PKCS1v15(),
+ ek509.signature_hash_algorithm,
+ )
+ elif isinstance(signcert_pubkey, EllipticCurvePublicKey):
+ signcert_pubkey.verify(
+ ek509.signature,
+ ek509.tbs_certificate_bytes,
+ ec.ECDSA(ek509.signature_hash_algorithm),
+ )
+ else:
+ logger.warning("Unsupported public key type: %s", type(signcert_pubkey))
+ continue
+ except crypto_exceptions.InvalidSignature:
+ continue
+
+ logger.debug("EK cert matched cert: %s", cert_file)
+ return True
+ except Exception as e:
+ # Log the exception so we don't lose the raw message
+ logger.exception(e)
+ raise Exception("Error processing ek/ekcert. Does this TPM have a valid EK?").with_traceback(sys.exc_info()[2])
+
+ logger.error("No Root CA matched EK Certificate")
+ return False
+
+
+def verify_ek_script(script, env, cwd):
+ if script is None:
+ logger.warning("External check script (%s) not specified", script)
+ return False
+
+ script_path = os.path.abspath(script)
+ if not os.path.isfile(script_path):
+ if cwd is None or not os.path.isfile(os.path.abspath(os.path.join(cwd, script))):
+ logger.warning("External check script (%s) not found; please make sure its path is correct", script)
+ return False
+ script_path = os.path.abspath(os.path.join(cwd, script))
+
+ try:
+ proc = subprocess.run(
+ [script_path],
+ env=env,
+ shell=False,
+ cwd=cwd,
+ stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE,
+ check=False,
+ )
+ if proc.returncode != 0:
+ errmsg = ""
+ if proc.stdout is not None:
+ errmsg = proc.stdout.decode("utf-8")
+ logger.error("External check script failed to validate EK: %s", errmsg)
+ return False
+ logger.debug("External check script successfully to validated EK")
+ if proc.stdout is not None:
+ logger.info("ek_check output: %s", proc.stdout.decode("utf-8"))
+ except subprocess.CalledProcessError as e:
+ logger.error("Error while trying to run external check script to validate EK: %s", e)
+ return False
+ return True
diff --git a/keylime/registrar_common.py b/keylime/registrar_common.py
index 2c32d19..fb37e5b 100644
--- a/keylime/registrar_common.py
+++ b/keylime/registrar_common.py
@@ -261,11 +261,8 @@ class UnprotectedHandler(BaseHTTPRequestHandler, SessionManager):
# Note, we don't validate the EKCert here, other than the implicit
# "is it a valid x509 cert" check. So it's still untrusted.
# This will be validated by the tenant.
- ek_tpm = base64.b64encode(
- tpm2_objects.ek_low_tpm2b_public_from_pubkey(
- cert_utils.read_x509_der_cert_pubkey(base64.b64decode(ekcert))
- )
- ).decode()
+ cert = cert_utils.x509_der_cert(base64.b64decode(ekcert))
+ ek_tpm = base64.b64encode(tpm2_objects.ek_low_tpm2b_public_from_pubkey(cert.public_key())).decode()
aik_attrs = tpm2_objects.get_tpm2b_public_object_attributes(
base64.b64decode(aik_tpm),
diff --git a/keylime/tenant.py b/keylime/tenant.py
index cc53623..dd9c09c 100644
--- a/keylime/tenant.py
+++ b/keylime/tenant.py
@@ -5,7 +5,6 @@ import io
import json
import logging
import os
-import subprocess
import sys
import tempfile
import time
@@ -15,7 +14,7 @@ import requests
from cryptography.hazmat.primitives import serialization as crypto_serialization
from keylime import api_version as keylime_api_version
-from keylime import ca_util, config, crypto, keylime_logging, registrar_client, signing, web_util
+from keylime import ca_util, cert_utils, config, crypto, keylime_logging, registrar_client, signing, web_util
from keylime.agentstates import AgentAttestState
from keylime.cli import options, policies
from keylime.cmd import user_data_encrypt
@@ -516,19 +515,11 @@ class Tenant:
env["EK_CERT"] = ""
env["PROVKEYS"] = json.dumps(self.registrar_data.get("provider_keys", {}))
- with subprocess.Popen(
- script, env=env, shell=True, cwd=config.WORK_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
- ) as proc:
- retval = proc.wait()
- if retval != 0:
- raise UserError("External check script failed to validate EK")
- logger.debug("External check script successfully to validated EK")
- while True:
- line = proc.stdout.readline().decode()
- if line == "":
- break
- logger.debug("ek_check output: %s", line.strip())
- return True
+
+ # Define the TPM cert store for the external script.
+ env["TPM_CERT_STORE"] = config.get("tenant", "tpm_cert_store")
+
+ return cert_utils.verify_ek_script(script, env, config.WORK_DIR)
def do_cv(self):
"""Initiate v, agent_id and ip and initiate the cloudinit sequence"""
diff --git a/keylime/tpm/tpm_main.py b/keylime/tpm/tpm_main.py
index 7bab4fd..35f0a2f 100644
--- a/keylime/tpm/tpm_main.py
+++ b/keylime/tpm/tpm_main.py
@@ -12,14 +12,10 @@ import time
import typing
import zlib
-from cryptography import exceptions as crypto_exceptions
-from cryptography import x509
-from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization as crypto_serialization
-from cryptography.hazmat.primitives.asymmetric import padding
from packaging.version import Version
-from keylime import cmd_exec, config, keylime_logging, secure_mount, tpm_ek_ca
+from keylime import cert_utils, cmd_exec, config, keylime_logging, secure_mount
from keylime.agentstates import TPMClockInfo
from keylime.common import algorithms, retry
from keylime.failure import Component, Failure
@@ -785,46 +781,7 @@ class tpm(tpm_abstract.AbstractTPM):
:param ekcert: The Endorsement Key certificate in DER format
:returns: True if the certificate can be verified, false otherwise
"""
- # openssl x509 -inform der -in certificate.cer -out certificate.pem
- try:
- tpm_ek_ca.check_tpm_cert_store()
-
- ek509 = x509.load_der_x509_certificate(
- data=ekcert,
- backend=default_backend(),
- )
-
- trusted_certs = tpm_ek_ca.cert_loader()
- for cert in trusted_certs:
- signcert = x509.load_pem_x509_certificate(
- data=cert.encode(),
- backend=default_backend(),
- )
-
- if ek509.issuer.rfc4514_string() != signcert.subject.rfc4514_string():
- continue
-
- try:
- signcert.public_key().verify(
- ek509.signature,
- ek509.tbs_certificate_bytes,
- padding.PKCS1v15(),
- ek509.signature_hash_algorithm,
- )
- except crypto_exceptions.InvalidSignature:
- continue
-
- logger.debug("EK cert matched cert: %s", cert)
- return True
- except Exception as e:
- # Log the exception so we don't lose the raw message
- logger.exception(e)
- raise Exception("Error processing ek/ekcert. Does this TPM have a valid EK?").with_traceback(
- sys.exc_info()[2]
- )
-
- logger.error("No Root CA matched EK Certificate")
- return False
+ return cert_utils.verify_ek(ekcert)
def get_tpm_manufacturer(self, output=None):
vendorStr = None
diff --git a/keylime/tpm_ek_ca.py b/keylime/tpm_ek_ca.py
index 3695f0b..fb66c07 100644
--- a/keylime/tpm_ek_ca.py
+++ b/keylime/tpm_ek_ca.py
@@ -7,8 +7,7 @@ logger = keylime_logging.init_logging("tpm_ek_ca")
trusted_certs = {}
-def check_tpm_cert_store():
- tpm_cert_store = config.get("tenant", "tpm_cert_store")
+def check_tpm_cert_store(tpm_cert_store=config.get("tenant", "tpm_cert_store")):
if not os.path.isdir(tpm_cert_store):
logger.error("The directory %s does not exist.", tpm_cert_store)
raise Exception(f"The directory {tpm_cert_store} does not exist.")
@@ -21,11 +20,10 @@ def check_tpm_cert_store():
raise Exception(f"The directory {tpm_cert_store} does not contain " f"any .pem files")
-def cert_loader():
- tpm_cert_store = config.get("tenant", "tpm_cert_store")
+def cert_loader(tpm_cert_store=config.get("tenant", "tpm_cert_store")):
file_list = glob.glob(os.path.join(tpm_cert_store, "*.pem"))
- my_trusted_certs = []
+ my_trusted_certs = {}
for file_path in file_list:
with open(file_path, encoding="utf-8") as f_input:
- my_trusted_certs.append(f_input.read())
+ my_trusted_certs[file_path] = f_input.read()
return my_trusted_certs
diff --git a/scripts/ek-openssl-verify b/scripts/ek-openssl-verify
new file mode 100755
index 0000000..f91e9b5
--- /dev/null
+++ b/scripts/ek-openssl-verify
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+# This script can be used as the `ek_check_script' (tenant configuration),
+# to attempt to verify a provided EK_CERT via env var using openssl.
+
+# EK - contains a PEM-encoded version of the public EK
+# EK_CERT - contains a base64 DER-encoded EK certificate if one is
+# available.
+# PROVKEYS - contains a json document containing EK, EKcert, and AIK
+# from the provider. EK and AIK are in PEM format. The
+# EKcert is in base64-encoded DER format
+# TPM_CERT_STORE - contains the path of the TPM certificate store.
+EK=${EK:-}
+EK_CERT=${EK_CERT:-}
+PROVKEYS=${PROVKEYS:-}
+
+EK_VERIFICATION_LOG=${EK_VERIFICATION_LOG:-/var/log/keylime/ek-verification.log}
+LOG="${EK_VERIFICATION_LOG}"
+
+# Setting log fallback in case we cannot write to the specified file.
+touch "${LOG}" 2>/dev/null || LOG=/dev/stderr
+
+log() {
+ _stderr=${2:-}
+ echo "[$(date)] ${1}" >&2 >> "${LOG}"
+ [ -n "${_stderr}" ] && [ "${LOG}" != '/dev/stderr' ] && echo "${1}" >&2
+}
+
+die() {
+ log "ERROR: ${1}" _
+ exit 1
+}
+
+command -v openssl >/dev/null \
+ || die "openssl CLI was not found in the PATH; please make sure it is installed"
+
+[ -n "${EK_CERT}" ] || die "EK_CERT was not provided as an env var"
+
+# Cert store directory. If one is not provided via TPM_CERT_STORE env var,
+# we start by attempting to read tenant.conf.
+CERT_STORE=${TPM_CERT_STORE:-}
+
+if [ -z "${CERT_STORE}" ]; then
+ KEYLIME_CONFIG_DIR=${KEYLIME_CONFIG_DIR:-/etc/keylime}
+ [ -d "${KEYLIME_CONFIG_DIR}" ] \
+ || die "KEYLIME_CONFIG_DIR (${KEYLIME_CONFIG_DIR}) does not seem to exist"
+
+ if [ -r "${KEYLIME_CONFIG_DIR}"/tenant.conf ]; then
+ CERT_STORE="$(grep -w ^tpm_cert_store "${KEYLIME_CONFIG_DIR}"/tenant.conf \
+ | tail -1 | cut -d'=' -f2 | tr -d "[:blank:]")"
+ fi
+
+ # Next we try to read any snippets in tenant.conf.d/
+ if [ -d "${KEYLIME_CONFIG_DIR}"/tenant.conf.d ]; then
+ for _s in "${KEYLIME_CONFIG_DIR}"/tenant.conf.d/*.conf; do
+ [ -e "${_s}" ] || continue
+ _store="$(grep -w ^tpm_cert_store "${_s}" \
+ | tail -1 | cut -d'=' -f2 | tr -d "[:blank:]")"
+ [ -n "${_store}" ] && CERT_STORE="${_store}"
+ done
+ fi
+fi
+
+[ -n "${CERT_STORE}" ] \
+ || die "It was not possible to determine the TPM cert store dir from tenant.conf or tenant.conf.d/ snippets"
+[ -d "${CERT_STORE}" ] \
+ || die "TPM cert store is not a valid directory (${CERT_STORE})"
+
+EK_VERIFICATION_TMPDIR=
+ek_verification_cleanup() {
+ [ -d "${EK_VERIFICATION_TMPDIR}" ] || return 0
+ rm -rf "${EK_VERIFICATION_TMPDIR}"
+}
+trap ek_verification_cleanup EXIT
+
+mkdir -p "${TMPDIR:-/tmp}"
+EK_VERIFICATION_TMPDIR="$(mktemp -d)" || \
+ die "Creating a temp dir for EK verification failed"
+
+EK_CERT_FILE_DER="${EK_VERIFICATION_TMPDIR}"/ek.der
+EK_CERT_FILE_PEM="${EK_VERIFICATION_TMPDIR}"/ek.pem
+
+printf '%s' "${EK_CERT}" > "${EK_CERT_FILE_DER}".b64
+base64 -d "${EK_CERT_FILE_DER}".b64 > "${EK_CERT_FILE_DER}"
+openssl x509 -inform der -in "${EK_CERT_FILE_DER}" > "${EK_CERT_FILE_PEM}"
+
+for c in "${CERT_STORE}"/*.pem; do
+ [ -e "${c}" ] || continue
+ log "Checking if ${c} is the issuer of EK cert..."
+ if openssl verify -partial_chain -CAfile "${c}" "${EK_CERT_FILE_PEM}" \
+ >>"${LOG}" 2>>"${LOG}"; then
+ log "${EK_CERT} successfully verified by $(basename "${c}")" _
+ exit 0
+ fi
+done
+
+die "EK signature did not match certificates from TPM cert store"
+# vim:set ts=2 sw=2 et:
diff --git a/test/run_tests.sh b/test/run_tests.sh
index fc43113..81a6dff 100755
--- a/test/run_tests.sh
+++ b/test/run_tests.sh
@@ -107,19 +107,19 @@ fi
# Set correct dependencies
# Fedora
if [ $PACKAGE_MGR = "dnf" ]; then
- PYTHON_PREIN="python3"
+ PYTHON_PREIN="python3 openssl"
PYTHON_DEPS="python3-pip python3-dbus"
# RHEL / CentOS etc
elif [ $PACKAGE_MGR = "yum" ]; then
- PYTHON_PREIN="epel-release python36"
+ PYTHON_PREIN="epel-release python36 openssl"
PYTHON_DEPS="python36-pip python36-dbus"
# Ubuntu / Debian
elif [ $PACKAGE_MGR = "apt-get" ]; then
- PYTHON_PREIN="python3"
+ PYTHON_PREIN="python3 openssl"
PYTHON_DEPS="python3-pip python3-dbus"
# SUSE
elif [ $PACKAGE_MGR = "zypper" ]; then
- PYTHON_PREIN="python3"
+ PYTHON_PREIN="python3 openssl"
PYTHON_DEPS="python3-pip python3-dbus"
else
echo "No recognized package manager found on this system!" 1>&2
diff --git a/test/test_cert_utils.py b/test/test_cert_utils.py
index 4666c0f..bdf6090 100644
--- a/test/test_cert_utils.py
+++ b/test/test_cert_utils.py
@@ -4,17 +4,19 @@ Copyright 2022 Red Hat, Inc.
"""
import base64
+import os
import unittest
-from keylime import cert_utils
+import cryptography
+from keylime import cert_utils, tpm_ek_ca
-class Cert_Utils_Test(unittest.TestCase):
- def test_read_x509_der_cert_pubkey(self):
- # The certificate listed in issue #944, from Nuvoton. It fails to
- # be parsed by python-cryptography with the following error:
- # ValueError: error parsing asn1 value: ParseError { kind: InvalidSetOrdering, location: ["RawCertificate::tbs_cert", "TbsCertificate::issuer", "0", "2"] }
- nuvoton_ecdsa_sha256_der = """\
+CERT_STORE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "tpm_cert_store"))
+
+# The certificate listed in issue #944, from Nuvoton. It fails to
+# be parsed by python-cryptography with the following error:
+# ValueError: error parsing asn1 value: ParseError { kind: InvalidSetOrdering, location: ["RawCertificate::tbs_cert", "TbsCertificate::issuer", "0", "2"] }
+nuvoton_ecdsa_sha256_der = """\
MIICBjCCAaygAwIBAgIIP5MvnZk8FrswCgYIKoZIzj0EAwIwVTFTMB8GA1UEAxMYTnV2b3RvbiBU
UE0gUm9vdCBDQSAyMTEwMCUGA1UEChMeTnV2b3RvbiBUZWNobm9sb2d5IENvcnBvcmF0aW9uMAkG
A1UEBhMCVFcwHhcNMTUxMDE5MDQzMjAwWhcNMzUxMDE1MDQzMjAwWjBVMVMwHwYDVQQDExhOdXZv
@@ -26,10 +28,10 @@ ajW+9zAfBgNVHSMEGDAWgBSfu3mqD1JieL7RUJKacXHpajW+9zAKBggqhkjOPQQDAgNIADBFAiEA
/jiywhOKpiMOUnTfDmXsXfDFokhKVNTXB6Xtqm7J8L4CICjT3/Y+rrSnf8zrBXqWeHDh8Wi41+w2
ppq6Ev9orZFI
"""
- # This cert from STMicroelectronics presents a different issue when
- # parsed by python-cryptography:
- # ValueError: error parsing asn1 value: ParseError { kind: ExtraData }
- st_sha256_with_rsa_der = """\
+# This cert from STMicroelectronics presents a different issue when
+# parsed by python-cryptography:
+# ValueError: error parsing asn1 value: ParseError { kind: ExtraData }
+st_sha256_with_rsa_der = """\
MIIEjTCCA3WgAwIBAgIUTL0P5h7nYu2yjVCyaPw1hv89XoIwDQYJKoZIhvcNAQELBQAwVTELMAkG
A1UEBhMCQ0gxHjAcBgNVBAoTFVNUTWljcm9lbGVjdHJvbmljcyBOVjEmMCQGA1UEAxMdU1RNIFRQ
TSBFSyBJbnRlcm1lZGlhdGUgQ0EgMDUwHhcNMTgwNzExMDAwMDAwWhcNMjgwNzExMDAwMDAwWjAA
@@ -60,10 +62,116 @@ rTJ1x4NA2ZtQMYyT29Yy1UlkjocAaXL5u0m3Hvz/////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////w==
"""
- certs = [nuvoton_ecdsa_sha256_der, st_sha256_with_rsa_der]
- for c in certs:
+
+st_ecdsa_sha256_der = """\
+MIIDAzCCAqmgAwIBAgIUIymn2ai+UaVx1bM26/wU7I+sJd8wCgYIKoZIzj0EAwIwVjELMAkGA1UE
+BhMCQ0gxHjAcBgNVBAoTFVNUTWljcm9lbGVjdHJvbmljcyBOVjEnMCUGA1UEAxMeU1RNIFRQTSBF
+Q0MgSW50ZXJtZWRpYXRlIENBIDAxMB4XDTE4MDcyNjAwMDAwMFoXDTI4MDcyNjAwMDAwMFowADBZ
+MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBsTz5y2cedVZxG/GsbXQ9bL6EQylWNjx1b/SSp2EHlN
+aJjtn43iz2zb+qot2UOhQIwPxS5hMCXhasw4XsFXgnijggGpMIIBpTAfBgNVHSMEGDAWgBR+uDbO
++9+KY3H/czP5utcUYWyWyzBCBgNVHSAEOzA5MDcGBFUdIAAwLzAtBggrBgEFBQcCARYhaHR0cDov
+L3d3dy5zdC5jb20vVFBNL3JlcG9zaXRvcnkvMFkGA1UdEQEB/wRPME2kSzBJMRYwFAYFZ4EFAgEM
+C2lkOjUzNTQ0RDIwMRcwFQYFZ4EFAgIMDFNUMzNIVFBIQUhCNDEWMBQGBWeBBQIDDAtpZDowMDQ5
+MDAwNDBmBgNVHQkEXzBdMBYGBWeBBQIQMQ0wCwwDMi4wAgEAAgF0MEMGBWeBBQISMTowOAIBAAEB
+/6ADCgEBoQMKAQCiAwoBAKMQMA4WAzMuMQoBBAoBAgEB/6QPMA0WBTE0MC0yCgECAQEAMAwGA1Ud
+EwEB/wQCMAAwEAYDVR0lBAkwBwYFZ4EFCAEwDgYDVR0PAQH/BAQDAgMIMEsGCCsGAQUFBwEBBD8w
+PTA7BggrBgEFBQcwAoYvaHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9zdG10cG1lY2NpbnQw
+MS5jcnQwCgYIKoZIzj0EAwIDSAAwRQIgcNiZkn7poyk6J8Y1Cnwz4nV7YGPb5pBesBg6bk9n6KIC
+IQCE/jkHb/aPP/T3GtfLNHAdHL4JnofAbsDEuLQxAseeZA==
+"""
+
+
+def has_strict_x509_parsing():
+ """Indicates whether python-cryptography has strict x509 parsing."""
+
+ # Major release where python-cryptography started being strict
+ # when parsing x509 certificates.
+ PYCRYPTO_STRICT_X509_MAJOR = 35
+ return int(cryptography.__version__.split(".", maxsplit=1)[0]) >= PYCRYPTO_STRICT_X509_MAJOR
+
+
+def expectedFailureIf(condition):
+ """The test is marked as an expectedFailure if the condition is satisfied."""
+
+ def wrapper(func):
+ if condition:
+ return unittest.expectedFailure(func)
+ return func
+
+ return wrapper
+
+
+class Cert_Utils_Test(unittest.TestCase):
+ def test_tpm_cert_store(self):
+ tpm_ek_ca.check_tpm_cert_store(CERT_STORE_DIR)
+ my_trusted_certs = tpm_ek_ca.cert_loader(CERT_STORE_DIR)
+
+ self.assertNotEqual(len(my_trusted_certs), 0)
+
+ def test_cert_store_certs(self):
+ my_trusted_certs = tpm_ek_ca.cert_loader(CERT_STORE_DIR)
+ for fname, pem_cert in my_trusted_certs.items():
try:
- pubkey = cert_utils.read_x509_der_cert_pubkey(base64.b64decode(c))
- except Exception:
- self.fail("read_x509_der_cert_pubkey() is not expected to raise an exception here")
- self.assertIsNotNone(pubkey)
+ cert = cert_utils.x509_pem_cert(pem_cert)
+ except Exception as e:
+ self.fail(f"Failed to load certificate {fname}: {e}")
+ self.assertIsNotNone(cert)
+
+ def test_verify_ek(self):
+ tests = [
+ {"cert": st_sha256_with_rsa_der, "expected": True}, # RSA, signed by STM_RSA_05I.pem.
+ {"cert": st_ecdsa_sha256_der, "expected": True}, # ECC, signed by STM_ECC_01I.pem.
+ ]
+ for t in tests:
+ self.assertEqual(
+ cert_utils.verify_ek(base64.b64decode(t["cert"]), CERT_STORE_DIR),
+ t["expected"],
+ msg=f"Test failed for cert {t['cert']}; expected: {t['expected']}",
+ )
+
+ @expectedFailureIf(has_strict_x509_parsing())
+ def test_verify_ek_expected_failures(self):
+ # The following certificates are not compliant, and will fail the
+ # signature verification with python-cryptography, even though they
+ # should validate. Marking as expected failure for now.
+ tests = [
+ {"cert": nuvoton_ecdsa_sha256_der, "expected": True}, # ECC, signed by NUVO_2110.pem.
+ ]
+ for t in tests:
+ self.assertEqual(
+ cert_utils.verify_ek(base64.b64decode(t["cert"]), CERT_STORE_DIR),
+ t["expected"],
+ msg=f"Test failed for cert {t['cert']}; expected: {t['expected']}",
+ )
+
+ def test_verify_ek_script(self):
+ # We will be using `nuvoton_ecdsa_sha256_der', which is signed by
+ # NUVO_2110.pem but fails verification when using python-cryptography
+ # as it is a malformed cert -- it is the same one we use in
+ # test_verify_ek_expected_failures().
+ # With an external script `ek_script_check' that uses openssl, the
+ # validation works.
+ cert = nuvoton_ecdsa_sha256_der.replace("\n", "")
+
+ self.assertFalse(cert_utils.verify_ek_script(None, None, None))
+ self.assertFalse(cert_utils.verify_ek_script("/foo/bar", None, None))
+
+ script = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "scripts", "ek-openssl-verify"))
+ # Testing ek-openssl-verify script, but without specifying the
+ # EK_CERT env var.
+ self.assertFalse(cert_utils.verify_ek_script(script, None, None))
+
+ # Now let's specify the EK_CERT.
+ env = os.environ.copy()
+ env["EK_CERT"] = cert
+ env["TPM_CERT_STORE"] = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "tpm_cert_store"))
+ self.assertTrue(cert_utils.verify_ek_script(script, env, None))
+
+ # Now, let us specify the ek_check_script with a relative path.
+ script = "ek-openssl-verify"
+ cwd = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "scripts"))
+ self.assertTrue(cert_utils.verify_ek_script(script, env, cwd))
+
+ # And now we try a bad TPM cert store.
+ env["TPM_CERT_STORE"] = "/some/bad/directory"
+ self.assertFalse(cert_utils.verify_ek_script(script, env, cwd))
--
2.38.1

View File

@ -0,0 +1,130 @@
From 76cbd7bbcce1793db9a3d64d962cfdb518ef4eff Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
Date: Tue, 15 Nov 2022 07:09:13 -0300
Subject: [PATCH 4/4] Do not use default values that need reading the config in
methods
Following up from the recent refactoring that moved the EK validation
to cert_utils, in a few places were added default method values that
were reading the configuration files directly.
It was not such a great idea becasue it then made those config files as
required to even import the modules.
Example "from keylime import cert_utils" now also requires that the
tenant configuration be available for getting the path for the TPM
cert store.
Let's stop doing that.
Signed-off-by: Sergio Correia <scorreia@redhat.com>
---
keylime/cert_utils.py | 5 +++--
keylime/tenant.py | 2 +-
keylime/tpm/tpm_abstract.py | 2 +-
keylime/tpm/tpm_main.py | 4 ++--
keylime/tpm_ek_ca.py | 6 +++---
5 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/keylime/cert_utils.py b/keylime/cert_utils.py
index d2fc54d..3576c64 100644
--- a/keylime/cert_utils.py
+++ b/keylime/cert_utils.py
@@ -12,7 +12,7 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from pyasn1.codec.der import decoder, encoder
from pyasn1_modules import pem, rfc2459
-from keylime import config, keylime_logging, tpm_ek_ca
+from keylime import keylime_logging, tpm_ek_ca
# Issue #944 -- python-cryptography won't parse malformed certs,
# such as some Nuvoton ones we have encountered in the field.
@@ -56,9 +56,10 @@ def x509_pem_cert(pem_cert_data: str):
return x509.load_der_x509_certificate(data=encoder.encode(pyasn1_cert), backend=default_backend())
-def verify_ek(ekcert, tpm_cert_store=config.get("tenant", "tpm_cert_store")):
+def verify_ek(ekcert: bytes, tpm_cert_store: str) -> bool:
"""Verify that the provided EK certificate is signed by a trusted root
:param ekcert: The Endorsement Key certificate in DER format
+ :param tpm_cert_store: The path for the TPM certificate store
:returns: True if the certificate can be verified, False otherwise
"""
try:
diff --git a/keylime/tenant.py b/keylime/tenant.py
index dd9c09c..118f8c4 100644
--- a/keylime/tenant.py
+++ b/keylime/tenant.py
@@ -430,7 +430,7 @@ class Tenant:
elif ekcert is None:
logger.warning("No EK cert provided, require_ek_cert option in config set to True")
return False
- elif not self.tpm_instance.verify_ek(base64.b64decode(ekcert)):
+ elif not self.tpm_instance.verify_ek(base64.b64decode(ekcert), config.get("tenant", "tpm_cert_store")):
logger.warning("Invalid EK certificate")
return False
diff --git a/keylime/tpm/tpm_abstract.py b/keylime/tpm/tpm_abstract.py
index ff41837..df6222c 100644
--- a/keylime/tpm/tpm_abstract.py
+++ b/keylime/tpm/tpm_abstract.py
@@ -97,7 +97,7 @@ class AbstractTPM(metaclass=ABCMeta):
pass
@abstractmethod
- def verify_ek(self, ekcert):
+ def verify_ek(self, ekcert, tpm_cert_store):
pass
@abstractmethod
diff --git a/keylime/tpm/tpm_main.py b/keylime/tpm/tpm_main.py
index 35f0a2f..09af0d0 100644
--- a/keylime/tpm/tpm_main.py
+++ b/keylime/tpm/tpm_main.py
@@ -776,12 +776,12 @@ class tpm(tpm_abstract.AbstractTPM):
os.remove(sesspath)
return key
- def verify_ek(self, ekcert):
+ def verify_ek(self, ekcert, tpm_cert_store):
"""Verify that the provided EK certificate is signed by a trusted root
:param ekcert: The Endorsement Key certificate in DER format
:returns: True if the certificate can be verified, false otherwise
"""
- return cert_utils.verify_ek(ekcert)
+ return cert_utils.verify_ek(ekcert, tpm_cert_store)
def get_tpm_manufacturer(self, output=None):
vendorStr = None
diff --git a/keylime/tpm_ek_ca.py b/keylime/tpm_ek_ca.py
index fb66c07..bc84571 100644
--- a/keylime/tpm_ek_ca.py
+++ b/keylime/tpm_ek_ca.py
@@ -1,13 +1,13 @@
import glob
import os
-from keylime import config, keylime_logging
+from keylime import keylime_logging
logger = keylime_logging.init_logging("tpm_ek_ca")
trusted_certs = {}
-def check_tpm_cert_store(tpm_cert_store=config.get("tenant", "tpm_cert_store")):
+def check_tpm_cert_store(tpm_cert_store):
if not os.path.isdir(tpm_cert_store):
logger.error("The directory %s does not exist.", tpm_cert_store)
raise Exception(f"The directory {tpm_cert_store} does not exist.")
@@ -20,7 +20,7 @@ def check_tpm_cert_store(tpm_cert_store=config.get("tenant", "tpm_cert_store")):
raise Exception(f"The directory {tpm_cert_store} does not contain " f"any .pem files")
-def cert_loader(tpm_cert_store=config.get("tenant", "tpm_cert_store")):
+def cert_loader(tpm_cert_store):
file_list = glob.glob(os.path.join(tpm_cert_store, "*.pem"))
my_trusted_certs = {}
for file_path in file_list:
--
2.38.1

View File

@ -8,7 +8,7 @@
Name: keylime
Version: 6.5.1
Release: 1%{?dist}
Release: 1%{?dist}.4
Summary: Open source TPM software for Bootstrapping and Maintaining Trust
URL: https://github.com/keylime/keylime
@ -18,6 +18,11 @@ Source2: %{srcname}.te
Source3: %{srcname}.if
Source4: %{srcname}.fc
Patch: 0001-ima-Fix-log-evaluation-on-quick-succession-execution.patch
Patch: 0002-tpm_bootlog_enrich-Get-DevicePath-length-from-Length.patch
Patch: 0003-Backport-upsteam-PR-1156.patch
Patch: 0004-Do-not-use-default-values-that-need-reading-the-conf.patch
License: ASL 2.0 and MIT
BuildRequires: git-core
@ -86,6 +91,7 @@ Requires: python3-lark-parser
Requires: python3-pyasn1
Requires: python3-pyasn1-modules
Requires: tpm2-tools
Requires: openssl
%description -n python3-%{srcname}
The python3-keylime module implements the functionality used
@ -183,7 +189,8 @@ rm -f %{buildroot}/%{_bindir}/%{srcname}_userdata_encrypt
mkdir -p %{buildroot}/%{_datadir}/%{srcname}/scripts
for s in create_allowlist.sh \
create_mb_refstate \
create_policy; do
create_policy \
ek-openssl-verify; do
install -Dpm 755 scripts/${s} \
%{buildroot}/%{_datadir}/%{srcname}/scripts/${s}
done
@ -333,11 +340,28 @@ fi
%{_tmpfilesdir}/%{srcname}.conf
%{_sysusersdir}/%{srcname}.conf
%{_datadir}/%{srcname}/scripts/create_allowlist.sh
%{_datadir}/%{srcname}/scripts/ek-openssl-verify
%files
%license LICENSE
%changelog
* Tue Nov 15 2022 Sergio Correia <scorreia@redhat.com> - 6.5.1-1.4
- Do not use default values that need reading the config in methods
Resolves: rhbz#2142033 - Registrar may crash during EK validation when require_ek_cert is enabled [rhel-9.1.0.z]
* Mon Nov 14 2022 Sergio Correia <scorreia@redhat.com> - 6.5.1-1.3
- Backport upstream PR#1156
Resolves: rhbz#2142033 - Registrar may crash during EK validation when require_ek_cert is enabled [rhel-9.1.0.z]
* Mon Nov 14 2022 Sergio Correia <scorreia@redhat.com> - 6.5.1-1.2
- Segmentation fault in create_mb_refstate script
Resolves: rhbz#2142034 - Segmentation fault in /usr/share/keylime/create_mb_refstate script [rhel-9.1.0.z]
* Mon Nov 14 2022 Sergio Correia <scorreia@redhat.com> - 6.5.1-1.1
- ima: Fix log evaluation on quick-succession execution of scripts
Resolves: rhbz#2142032 - agent fails IMA attestation when one scripts is executed quickly after the other [rhel-9.1.0.z]
* Thu Oct 13 2022 Sergio Correia <scorreia@redhat.com> - 6.5.1-1
- Update to 6.5.1
Resolves: CVE-2022-3500