Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move EK cert verification to cert_utils #1156

Merged
merged 3 commits into from Nov 10, 2022

Conversation

sergio-correia
Copy link
Contributor

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

except crypto_exceptions.InvalidSignature:
continue

logger.debug("EK cert matched cert: %s", cert_file)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

This expression logs [sensitive data (certificate)](1) as clear text. This expression logs [sensitive data (secret)](2) as clear text. This expression logs [sensitive data (certificate)](3) as clear text. This expression logs [sensitive data (secret)](4) as clear text.
@sergio-correia sergio-correia marked this pull request as draft November 7, 2022 15:20
Copy link
Member

@THS-on THS-on left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactor LGTM, only a few comments.

Where it makes sense also add type hints, to make it easier to check this code with mypy in the future.

.github/workflows/test.yml Outdated Show resolved Hide resolved


def x509_der_cert(der_cert_data):
if isinstance(der_cert_data, str):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add type hints that both bytes and str is accepted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the type hints, but for now I changed to be in line with comments from Stefan, so x509_der_cert() current accepts bytes and x509_pem_cert() accepts str. Please let me know your thoughts.

keylime/cert_utils.py Outdated Show resolved Hide resolved
keylime/tenant.py Show resolved Hide resolved
@@ -0,0 +1,96 @@
#!/bin/sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add maybe also a reference to this script in the documentation? Thanks for adding an example!

logger = keylime_logging.init_logging("cert_utils")


class UserError(Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have UserError defined multiple times in Keylime it is probably a good idea to move it into https://github.com/keylime/keylime/blob/master/keylime/common/exception.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will do this in a separate PR. I removed this definition from here, in any case.

keylime/cert_utils.py Outdated Show resolved Hide resolved
keylime/cert_utils.py Outdated Show resolved Hide resolved
keylime/cert_utils.py Outdated Show resolved Hide resolved
cert = x509.load_pem_x509_certificate(data=pem_cert_data, backend=default_backend())
return cert
except Exception:
return x509_der_cert(pem.readPemFromFile(io.StringIO(pem_cert_data)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That confuses me, if is a call to read a PEM, why do we try a DER here? This make the operation non-symmetric (a call to read a PEM can try a DER, but no vice versa)

If symmetry is required, IMHO is a bit more clear if:

  • The DER function try the pyasm1 decoder after the conversion to bytes, capturing the pyasn1.error.SubstrateUnderrunError exception to see if it valid or not, and calling the load_der_... only one
  • Drop in the DER function the conversion to bytes (see later)
  • In the PEM function only try the load_pem_ ... call
  • Drop in the PEM function the conversion from bytes
  • Create a new x509_cert that order the calls to PEM / DER in the order that you suit, and do the conversion from / to bytes
  • Use only this function to load the certificates, and never call the PEM / DER versions outside this new function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEM here is the DER data base64 encoded, with the added header and footer (e.g. ----BEGIN CERT...), so once we call pem.readPemFromFile() we have the actual bytes, which we can parse with the method that parses the DER data. I changed it slightly and added a comment, to try to clarify it a bit, but I am not sure I addressed your concern fully, so if you could recheck I would appreciate.


logger.debug("EK cert matched cert: %s", cert_file)
return True
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can hide problems in the certification load under a misleading error message.

The try should be moved early, surrounding only tpm_ek_ca.cert_loader, because as it is now it is also catching any exception in the PEM / DER loads, that does not indicate any issue with the TPM.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to be in line with your comment. Let me know it if is better now, please.

keylime/cert_utils.py Outdated Show resolved Hide resolved
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 Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be an specific exception to indicate an error with the subproces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, please re-check.


def verify_ek_script(script, env, cwd):
if script is None:
logger.warning("External check script (%s) not specified", script)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the current direction is for keylime but consider the f-strings with {script} ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider the f-strings

That makes me wonder if we want this for logs or not. The logic behind log.level("str", params) (note that % is not used) is that interpolation only happens iff the call is made. With f-strings we move out from lazy interpolation.

Not sure that is relevant, tho.

scripts/ek-openssl-verify Outdated Show resolved Hide resolved
keylime/cert_utils.py Outdated Show resolved Hide resolved
@mpeters
Copy link
Member

mpeters commented Nov 9, 2022

This looks good to me but I'll wait until @aplanas @stefanberger and @THS-on can re-comment after you most recent changes. Would be good to get this fixed for 6.5.2

keylime/cert_utils.py Outdated Show resolved Hide resolved
keylime/cert_utils.py Outdated Show resolved Hide resolved
@stefanberger
Copy link
Contributor

LGTM

In both check_tpm_cert_store and cert_loader().
This will make it simpler for testing the certs in the store.

Additionaly, let's use a dict containing the file name as the key
for the cert data, as it may be helpful when debugging cert-related
issues.

Signed-off-by: Sergio Correia <scorreia@redhat.com>
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, so this commit also fixes an issue that had been
previously reported.

Hovewer, malformed certs that are not parsed by python-cryptography
still pose an issue as python-cryptography will be unable to verify
their signatures. One possible way to deal with those would be to use
different tools (e.g. openssl) to do the signature verification, with
the `ek_check_script' option.

Signed-off-by: Sergio Correia <scorreia@redhat.com>
Keylime allows to specify an optional script to check the EK and/or EK
certtificate against an allowlist or any other additional EK processing
you want to do, via the `ek_check_script' option in tenant.conf.

In this commit we move the code path that runs this external script to
cert_utils, so it becomes simpler to test, and we also provide a sample
`ek-openssl-verify' script that uses openssl to do the validation of
an EK certificate provided via the EK_CERT env var, against the
certificates in the TPM cert store.

Signed-off-by: Sergio Correia <scorreia@redhat.com>
@THS-on
Copy link
Member

THS-on commented Nov 10, 2022

/packit retest-failed

@THS-on
Copy link
Member

THS-on commented Nov 10, 2022

@kkaarreell @ansasaki the measured boot unit test fails on centos 9. Any idea why?

@sergio-correia
Copy link
Contributor Author

@kkaarreell @ansasaki the measured boot unit test fails on centos 9. Any idea why?

It fails in Centos 8 stream. From what I saw here quickly, the tpm2-tools version there (4.1.1) does not have tpm2_eventlog, so the test fails.

@THS-on
Copy link
Member

THS-on commented Nov 10, 2022

It fails in Centos 8 stream. From what I saw here quickly, the tpm2-tools version there (4.1.1) does not have tpm2_eventlog, so the test fails.

Yeah you are right, so the test can just add a skip if the the TPM version is below 4.1.1

@kkaarreell
Copy link
Contributor

Correct, in E2E tests we are disabling measured boot tests for C8S.

@ansasaki
Copy link
Contributor

ansasaki commented Nov 10, 2022

I'll create a PR to skip the measured boot log parsing test for old tpm2-tools versions

@THS-on
Copy link
Member

THS-on commented Nov 10, 2022

@ansasaki thank you. @sergio-correia I'll merge this PR now, because the failure has nothing to do with this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants