Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-tpm2-pytss for openSUSE:Factory checked in at 2024-10-09 22:12:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tpm2-pytss (Old) and /work/SRC/openSUSE:Factory/.python-tpm2-pytss.new.19354 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tpm2-pytss" Wed Oct 9 22:12:46 2024 rev:6 rq:1206411 version:2.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tpm2-pytss/python-tpm2-pytss.changes 2024-02-28 19:48:10.108098780 +0100 +++ /work/SRC/openSUSE:Factory/.python-tpm2-pytss.new.19354/python-tpm2-pytss.changes 2024-10-09 22:13:12.558910172 +0200 @@ -1,0 +2,18 @@ +Wed Oct 9 04:00:45 UTC 2024 - Steve Kowalik <steven.kowa...@suse.com> + +- Update to version 2.3.0: + * FAPI: support exists_ok for create_nv + * Compatibility with python-cryptography 42 + * scripts: update regex for #defines + * cryptography: fixes for newer version of cryptography + * docs/maintainers: add gpg key details + * docs: fix whitespace error + * docs: fix error on SECURITY.md not being used + * cryptography: add module for using TPM keys with the cryptography module + * encoding: add deprecation warning to tools_encdec + * internal/crypto: fix _MyRSAPrivateNumbers with cryptograpy >= 42.0.1 + * test: disable pcr_set_auth_value and pcr_set_auth_policy tests for swtpm +- Drop patch python-tpm2-pytss-RSAPrivateNumbers.patch, included upstream. +- Switch to pyproject macros. + +------------------------------------------------------------------- Old: ---- python-tpm2-pytss-RSAPrivateNumbers.patch tpm2-pytss-2.2.0.tar.gz New: ---- tpm2-pytss-2.3.0.tar.gz BETA DEBUG BEGIN: Old: * test: disable pcr_set_auth_value and pcr_set_auth_policy tests for swtpm - Drop patch python-tpm2-pytss-RSAPrivateNumbers.patch, included upstream. - Switch to pyproject macros. BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tpm2-pytss.spec ++++++ --- /var/tmp/diff_new_pack.cCZnQ1/_old 2024-10-09 22:13:13.410945696 +0200 +++ /var/tmp/diff_new_pack.cCZnQ1/_new 2024-10-09 22:13:13.410945696 +0200 @@ -17,28 +17,27 @@ %define pythons python3 -%{?!python_module:%define python_module() python-%{**} python3-%{**}} %define srcname tpm2-pytss %bcond_with test Name: python-%{srcname} -Version: 2.2.0 +Version: 2.3.0 Release: 0 Summary: Python bindings for TSS License: BSD-2-Clause URL: https://github.com/tpm2-software/tpm2-pytss Source: %{srcname}-%{version}.tar.gz -# PATCH-FIX-UPSTREAM Fix tpm2-pkcs11 build: github.com/tpm2-software/tpm2-pytss/pull/562 -Patch0: python-tpm2-pytss-RSAPrivateNumbers.patch BuildRequires: %{python_module PyYAML} BuildRequires: %{python_module asn1crypto} BuildRequires: %{python_module cffi} BuildRequires: %{python_module cryptography} BuildRequires: %{python_module devel} BuildRequires: %{python_module packaging} +BuildRequires: %{python_module pip} BuildRequires: %{python_module pkgconfig} BuildRequires: %{python_module pycparser} BuildRequires: %{python_module setuptools_scm} BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: pkgconfig BuildRequires: python-rpm-macros @@ -49,7 +48,6 @@ Requires: python3-cffi Requires: python3-cryptography Requires: python3-packaging -Requires: python3-setuptools Requires: pkgconfig(tss2-esys) Requires: pkgconfig(tss2-fapi) %if %{with test} @@ -81,10 +79,10 @@ Version: %{version} EOF export CFLAGS="%{optflags}" -%python_build +%pyproject_wheel %install -%python_install +%pyproject_install %python_expand %fdupes %{buildroot}%{$python_sitearch} %if %{with test} @@ -95,5 +93,5 @@ %files %{python_files} %license LICENSE %{python_sitearch}/tpm2_pytss -%{python_sitearch}/tpm2_pytss-%{version}*-info +%{python_sitearch}/tpm2_pytss-%{version}.dist-info ++++++ _service ++++++ --- /var/tmp/diff_new_pack.cCZnQ1/_old 2024-10-09 22:13:13.438946864 +0200 +++ /var/tmp/diff_new_pack.cCZnQ1/_new 2024-10-09 22:13:13.442947030 +0200 @@ -1,7 +1,7 @@ <services> <service name="tar_scm" mode="disabled"> <param name="filename">tpm2-pytss</param> - <param name="revision">2.2.0</param> + <param name="revision">2.3.0</param> <param name="scm">git</param> <param name="submodules">disable</param> <param name="url">https://github.com/tpm2-software/tpm2-pytss.git</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.cCZnQ1/_old 2024-10-09 22:13:13.462947865 +0200 +++ /var/tmp/diff_new_pack.cCZnQ1/_new 2024-10-09 22:13:13.466948031 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/tpm2-software/tpm2-pytss.git</param> - <param name="changesrevision">b9dae8373baad0f9f09a8d745a063f4960dd9369</param></service></servicedata> + <param name="changesrevision">930cee2c45b84ac533c320b883b226edaeda9a38</param></service></servicedata> (No newline at EOF) ++++++ tpm2-pytss-2.2.0.tar.gz -> tpm2-pytss-2.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/CHANGELOG.md new/tpm2-pytss-2.3.0/CHANGELOG.md --- old/tpm2-pytss-2.2.0/CHANGELOG.md 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/CHANGELOG.md 2024-06-27 10:36:21.000000000 +0200 @@ -4,6 +4,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.3.0 - 2024-06-26 +### Fixed +- Fix builds for tpm2-tss > 4.0.1. +- Support newer releases of the cryptography package. + +### Added +- Add module to use TPM keys with the cryptography package. +- Support for exists_ok for FAPI.create_nv, behaves the same as for FAPI.create_key. + +### Changed +- Support for the tpm2-tools encoder/decoder will be removed in the future and a warning has been added. + +## 2.2.1 - 2024-01-07 +### Fixed +- Fix tests `pcr_set_auth_value` and `pcr_set_auth_policy` tests when running against libtpms-based simulators. +- Fix integration with cryptography >= 42.0.1 + ## 2.2.0 - 2023-11-30 ### Fixed - Fix pycparse error for __float128. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/docs/api.rst new/tpm2-pytss-2.3.0/docs/api.rst --- old/tpm2-pytss-2.2.0/docs/api.rst 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/docs/api.rst 2024-06-27 10:36:21.000000000 +0200 @@ -14,3 +14,4 @@ Utility Routines <utils> TSS PEM Key (OpenSSL) <tsskey> TSS2_Exception <exception> + cryptography <cryptography> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/docs/cryptography.rst new/tpm2-pytss-2.3.0/docs/cryptography.rst --- old/tpm2-pytss-2.2.0/docs/cryptography.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/tpm2-pytss-2.3.0/docs/cryptography.rst 2024-06-27 10:36:21.000000000 +0200 @@ -0,0 +1,6 @@ +cryptography +============ + +.. automodule:: tpm2_pytss.cryptography + :members: + :undoc-members: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/docs/maintainers.rst new/tpm2-pytss-2.3.0/docs/maintainers.rst --- old/tpm2-pytss-2.2.0/docs/maintainers.rst 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/docs/maintainers.rst 2024-06-27 10:36:21.000000000 +0200 @@ -3,5 +3,15 @@ Project Maintainers ------------------- -- William Roberts <william.c.robe...@intel.com> -- Erik Larsson <who+git...@cnackers.org> +.. list-table:: Maintainers + :header-rows: 1 + + * - Name + - Email + - GPG Key Id + * - William Roberts + - bill.c.robe...@gmail.com + - D91A82DB310E8E07519C298A9877C26A3CD36409 + * - Erik Larsson + - who+git...@cnackers.org + - 2CAF442B36D6D5D0EB82FBEA28630DB7CEE35E6F diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/docs/project.rst new/tpm2-pytss-2.3.0/docs/project.rst --- old/tpm2-pytss-2.2.0/docs/project.rst 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/docs/project.rst 2024-06-27 10:36:21.000000000 +0200 @@ -7,7 +7,8 @@ :maxdepth: 1 Release Process <release> - Contributing <contributing> + Contributing <contributing> Maintainers <maintainers> Code of Conduct <CODE_OF_CONDUCT> + Security Practices <SECURITY> LICENSE <https://github.com/tpm2-software/tpm2-pytss/blob/master/LICENSE> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/scripts/prepare_headers.py new/tpm2-pytss-2.3.0/scripts/prepare_headers.py --- old/tpm2-pytss-2.2.0/scripts/prepare_headers.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/scripts/prepare_headers.py 2024-06-27 10:36:21.000000000 +0200 @@ -32,6 +32,7 @@ # Restructure #defines with ... s = re.sub("(#define [A-Za-z0-9_]+) +\(\(.*?\) \(.*?\)\)", "\g<1>...", s) + s = re.sub("(#define [A-Za-z0-9_]+) +\(\(\(.*?\) .*\)", "\g<1>...", s) s = re.sub("(#define [A-Za-z0-9_]+) +\(\(.*?\).*?\) ", "\g<1>...", s) s = re.sub( "(#define [A-Za-z0-9_]+) .*\n.*?.*\)\)", "\g<1>...", s, flags=re.MULTILINE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/src/tpm2_pytss/FAPI.py new/tpm2-pytss-2.3.0/src/tpm2_pytss/FAPI.py --- old/tpm2-pytss-2.2.0/src/tpm2_pytss/FAPI.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/src/tpm2_pytss/FAPI.py 2024-06-27 10:36:21.000000000 +0200 @@ -1110,7 +1110,8 @@ type_: Optional[Union[bytes, str]] = None, policy_path: Optional[Union[bytes, str]] = None, auth_value: Optional[Union[bytes, str]] = None, - ) -> None: + exists_ok: bool = False, + ) -> bool: """Create non-volatile (NV) storage on the TPM. Args: @@ -1119,16 +1120,23 @@ type_ (bytes or str): Type of the storage area. A combination of `bitfield`, `counter`, `pcr`, `system`, `noda`. Defaults to None. policy_path (bytes or str): The path to the policy which will be associated with the storage area. Defaults to None. auth_value (bytes or str): Password to protect the new storage area. Defaults to None. + exists_ok (bool): Do not throw a TSS2_Exception if a storage area with the given path already exists. Defaults to False. Raises: TSS2_Exception: If Fapi returned an error code. + + Returns: + bool: True if the storage area was created. False otherwise. """ path = _to_bytes_or_null(path) type_ = _to_bytes_or_null(type_) policy_path = _to_bytes_or_null(policy_path) auth_value = _to_bytes_or_null(auth_value) ret = lib.Fapi_CreateNv(self._ctx, path, type_, size, policy_path, auth_value) - _chkrc(ret) + _chkrc( + ret, acceptable=lib.TSS2_FAPI_RC_PATH_ALREADY_EXISTS if exists_ok else None + ) + return ret == lib.TPM2_RC_SUCCESS def nv_read(self, path: Union[bytes, str]) -> Tuple[bytes, str]: """Read from non-volatile (NV) TPM storage. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/src/tpm2_pytss/cryptography.py new/tpm2-pytss-2.3.0/src/tpm2_pytss/cryptography.py --- old/tpm2-pytss-2.2.0/src/tpm2_pytss/cryptography.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tpm2-pytss-2.3.0/src/tpm2_pytss/cryptography.py 2024-06-27 10:36:21.000000000 +0200 @@ -0,0 +1,430 @@ +# SPDX-License-Identifier: BSD-2 + +from .ESAPI import ESAPI +from .constants import ESYS_TR, TPM2_ALG, TPMA_OBJECT, TPM2_ST, TPM2_RH +from .types import ( + TPMT_RSA_DECRYPT, + TPM2B_DATA, + TPMT_SIG_SCHEME, + TPMT_TK_HASHCHECK, + TPM2B_ECC_POINT, + TPMT_ASYM_SCHEME, + TPMT_ECC_SCHEME, + TPMU_SIG_SCHEME, +) +from .internal.crypto import ( + public_to_key, + _get_curve, + _rsa_decrypt_padding_to_scheme, + _rsa_sign_padding_to_scheme, + _int_to_buffer, + _ecc_sign_algorithm_to_scheme, + _get_digest, +) +from typing import Union +from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + KeySerializationEncryption, +) + + +def _compare_schemes( + in_scheme: Union[TPMT_RSA_DECRYPT, TPMT_SIG_SCHEME], key_scheme: TPMT_SIG_SCHEME +) -> None: + """Compare a keys scheme and any scheme passed to sign/decrypt functions. + + Raises: + ValueError: On any scheme mismatch. + """ + if key_scheme.scheme == TPM2_ALG.NULL: + return + if in_scheme.scheme != key_scheme.scheme: + raise ValueError( + f"invalid scheme, scheme has {in_scheme.scheme} but key requires {key_scheme.scheme}" + ) + if in_scheme.scheme == TPM2_ALG.RSAES: + return + if isinstance(in_scheme.details, TPMU_SIG_SCHEME): + halg = in_scheme.details.any.hashAlg + else: + halg = in_scheme.details.anySig.hashAlg + if halg != key_scheme.details.anySig.hashAlg: + raise ValueError( + f"digest algorithm mismatch, scheme has {halg} but key requires {key_scheme.details.anySig.hashAlg}" + ) + + +class tpm_rsa_private_key(rsa.RSAPrivateKey): + """Interface to a TPM RSA key for use with the cryptography module. + + Args: + ectx (ESAPI): The ESAPI instance to use. + handle (ESYS_TR): The key handle. + session (ESYS_TR): The session to authorize usage of the key, default is ESYS_TR.PASSWORD + + Notes: + It is recommended to use the :func:`get_digest_algorithm`, :func:`get_decryption_padding` and :func:`get_signature_padding` methods for highest compatibility. + + Raises: + ValueError: If the key has the restricted bit set or if the handle doesn't reference an RSA key. + """ + + def __init__( + self, ectx: ESAPI, handle: ESYS_TR, session: ESYS_TR = ESYS_TR.PASSWORD + ): + self._handle = handle + self._session = session + self._ectx = ectx + public, _, _ = ectx.read_public(handle) + self._public = public.publicArea + if self._public.type != TPM2_ALG.RSA: + raise ValueError( + f"invalid key type, expected {TPM2_ALG.RSA}, got {self._public.type}" + ) + if self._public.objectAttributes & TPMA_OBJECT.RESTRICTED: + raise ValueError( + "TPM key does not allow generic signing and/or decryption (object attribute restricted is set)" + ) + + def decrypt(self, ciphertext: bytes, padding: padding) -> bytes: + """Implements the decrypt interface. + + See :py:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.decrypt` for documentation. + + Notes: + If a non-empty label is used with OAEP padding, this will fail. + + Raises: + ValueError: if the requested padding isn't supported by the key. + """ + if not self._public.objectAttributes & TPMA_OBJECT.DECRYPT: + raise ValueError( + "TPM key does not allow decryption (object attribute decrypt is not set)" + ) + scheme = TPMT_RSA_DECRYPT() + _rsa_decrypt_padding_to_scheme(padding, scheme) + _compare_schemes(scheme, self._public.parameters.rsaDetail.scheme) + data2b = self._ectx.rsa_decrypt( + self._handle, ciphertext, scheme, TPM2B_DATA(), session1=self._session + ) + return bytes(data2b) + + def public_key(self) -> rsa.RSAPublicKey: + """Get the public key. + + Returns: the public part of the RSA key as a :py:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. + """ + return public_to_key(self._public) + + @property + def key_size(self) -> int: + """The RSA key size""" + return self._public.parameters.rsaDetail.keyBits + + def get_digest_algorithm(self) -> hashes.HashAlgorithm: + """Get an usable digest algorithm for use with the key. + + If any scheme with a specified digest algorithm is specified return that algorithm. + Otherwise the name digest algorithm is returned. + + The returned digest algorithm can be used with different cryptography functions. + + Returns: + The digest algorithm as a :py:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` subclass. + + Raises: + ValueError: If the digest algorithm is not supported. + """ + if self._public.parameters.rsaDetail.scheme.scheme in ( + TPM2_ALG.RSASSA, + TPM2_ALG.RSAPSS, + TPM2_ALG.OAEP, + ): + tpm_alg = self._public.parameters.rsaDetail.scheme.details.anySig.hashAlg + else: + tpm_alg = self._public.nameAlg + halg = _get_digest(tpm_alg) + if halg is None: + raise ValueError(f"unsupported digest algorithm {tpm_alg}") + return halg + + def get_decryption_padding(self) -> padding.AsymmetricPadding: + """Get a padding configuration for use with the decrypt method. + + If the key has a scheme specified, use that scheme. + Otherwise, use OAEP as the default. + + Returns: + An instance of :py:class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + Raises: + ValueError: If the either the scheme or digest algorithm is unsupported. + """ + if self._public.parameters.asymDetail.scheme.scheme == TPM2_ALG.NULL: + scheme = TPMT_ASYM_SCHEME(scheme=TPM2_ALG.OAEP) + scheme.details.anySig.hashAlg = self._public.nameAlg + else: + scheme = self._public.parameters.asymDetail.scheme + if scheme.scheme == TPM2_ALG.OAEP: + algorithm = self.get_digest_algorithm() + decrypt_padding = padding.OAEP( + mgf=padding.MGF1(algorithm=algorithm()), + algorithm=algorithm(), + label=b"", + ) + elif scheme.scheme == TPM2_ALG.RSAES: + decrypt_padding = padding.PKCS1v15() + else: + raise ValueError(f"unsupported decryption scheme {scheme.scheme}") + return decrypt_padding + + def get_signature_padding(self) -> padding.AsymmetricPadding: + """Get a padding configuration for use with the sign method. + + If the key has a scheme specified, use that scheme. + Otherwise, use PSS as the default. + + Returns: + An instance of :py:class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + Raises: ValueError if the either the scheme or digest algorithm is unsupported. + """ + if self._public.parameters.asymDetail.scheme.scheme == TPM2_ALG.NULL: + scheme = TPMT_ASYM_SCHEME(scheme=TPM2_ALG.RSAPSS) + scheme.details.anySig.hashAlg = self._public.nameAlg + else: + scheme = self._public.parameters.asymDetail.scheme + if scheme.scheme == TPM2_ALG.RSAPSS: + algorithm = self.get_digest_algorithm() + sign_padding = padding.PSS( + mgf=padding.MGF1(algorithm=algorithm()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) + elif scheme.scheme == TPM2_ALG.RSASSA: + sign_padding = padding.PKCS1v15() + else: + raise ValueError(f"unsupported signature scheme {scheme.scheme}") + return sign_padding + + def sign( + self, + data: bytes, + padding: padding, + algorithm: Union[hashes.HashAlgorithm, Prehashed], + ) -> bytes: + """Implements the sign interface. + + See :py:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` for documentationen. + + Notes: + For PSS padding, the salt length should be set to the length of the digest as that is the only setup the TPM uses. + + Raises: + ValueError: If the requested padding isn't supported by the key or the sign_encrypt bit isn't set. + """ + if not self._public.objectAttributes & TPMA_OBJECT.SIGN_ENCRYPT: + raise ValueError( + "TPM key does not allow signing (object attribute sign_encrypt is not set)" + ) + if isinstance(algorithm, Prehashed): + raise ValueError("Prehashed data is not supported") + scheme = TPMT_SIG_SCHEME() + _rsa_sign_padding_to_scheme(padding, type(algorithm), scheme) + _compare_schemes(scheme, self._public.parameters.rsaDetail.scheme) + h = hashes.Hash(algorithm) + h.update(data) + digest = h.finalize() + validation = TPMT_TK_HASHCHECK(tag=TPM2_ST.HASHCHECK, hierarchy=TPM2_RH.NULL) + tpm_sig = self._ectx.sign( + self._handle, digest, scheme, validation, session1=self._session + ) + return bytes(tpm_sig) + + def private_numbers(self) -> None: + """Always raises a NotImplementedError.""" + raise NotImplementedError() + + def private_bytes( + self, + encoding: Encoding, + format: PrivateFormat, + encryption_algorithm: KeySerializationEncryption, + ) -> None: + """Always raises a NotImplementedError.""" + raise NotImplementedError() + + +class tpm_ecc_private_key(ec.EllipticCurvePrivateKey): + """Interface to a TPM ECC key for use with the cryptography module. + + Args: + ectx (ESAPI): The ESAPI instance to use. + handle (ESYS_TR): The key handle. + session (ESYS_TR): The session to authorize usage of the key, default is ESYS_TR.PASSWORD + + Notes: + It is recommended to use the :func:`get_digest_algorithm` and :func:`get_signature_algorithm` methods for highest compatibility. + + Raises: + ValueError: If the key has the restricted bit set, the curve isn't supported or if the handle doesn't reference an ECC key. + """ + + def __init__( + self, ectx: ESAPI, handle: ESYS_TR, session: ESYS_TR = ESYS_TR.PASSWORD + ): + self._handle = handle + self._session = session + self._ectx = ectx + public, _, _ = ectx.read_public(handle) + self._public = public.publicArea + if self._public.type != TPM2_ALG.ECC: + raise ValueError( + f"invalid key type, expected {TPM2_ALG.ECC}, got {self._public.type}" + ) + if self._public.objectAttributes & TPMA_OBJECT.RESTRICTED: + raise ValueError( + "TPM key does not allow generic signing and/or decryption (object attribute restricted is set)" + ) + cid = _get_curve(self._public.parameters.eccDetail.curveID) + if cid is None: + raise ValueError( + f"unsupported curve {self._public.parameters.eccDetail.curveID}" + ) + self._curve = cid + + def exchange( + self, algorithm: ec.ECDH, peer_public_key: ec.EllipticCurvePublicKey + ) -> bytes: + """Implements the exchange interface. + + See :py:meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.exchange` for documentationen. + + Raises: + ValueError: If the curves does not match or the decrypt bit isn't set. + """ + if not self._public.objectAttributes & TPMA_OBJECT.DECRYPT: + raise ValueError( + "TPM key does not allow ECDH key exchange (object attribute decrypt is not set)" + ) + if type(peer_public_key.curve) != type(self.curve): + raise ValueError( + f"curve mismatch for peer key, got {peer_public_key.curve.name}, expected {self.curve.name}" + ) + scheme = TPMT_SIG_SCHEME(scheme=TPM2_ALG.ECDH) + _compare_schemes(scheme, self._public.parameters.eccDetail.scheme) + in_point = TPM2B_ECC_POINT() + nums = peer_public_key.public_numbers() + _int_to_buffer(nums.x, in_point.point.x) + _int_to_buffer(nums.y, in_point.point.y) + + out_point = self._ectx.ecdh_zgen(self._handle, in_point, session1=self._session) + return bytes(out_point.point.x) + + def public_key(self) -> ec.EllipticCurvePublicKey: + """Get the public key. + + Returns: the public part of the ECC key as a :py:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + """ + return public_to_key(self._public) + + def get_digest_algorithm(self) -> hashes.HashAlgorithm: + """Get an usable digest algorithm for use with the key. + + If any scheme with a specified digest algorithm is specified return that algorithm. + Otherwise the name digest algorithm is returned. + + The returned digest algorithm can be used with different cryptography functions. + + Returns: + The digest algorithm as a :py:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` subclass. + + Raises: + ValueError: If the digest algorithm is not supported. + """ + if self._public.parameters.eccDetail.scheme.scheme == TPM2_ALG.ECDSA: + tpm_alg = self._public.parameters.eccDetail.scheme.details.anySig.hashAlg + else: + tpm_alg = self._public.nameAlg + halg = _get_digest(tpm_alg) + if halg is None: + raise ValueError(f"unsupported digest algorithm {tpm_alg}") + return halg + + def get_signature_algorithm(self) -> ec.EllipticCurveSignatureAlgorithm: + """Get a padding configuration for use with the sign method. + + If the key has a scheme specified, use that scheme. + Otherwise, use ECDSA as the default + + Returns: an instance of :py:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurveSignatureAlgorithm` + + Raises: + ValueError: If the either the scheme or digest algorithm is unsupported. + """ + if self._public.parameters.eccDetail.scheme.scheme == TPM2_ALG.NULL: + scheme = TPMT_ECC_SCHEME(scheme=TPM2_ALG.ECDSA) + scheme.details.anySig.hashAlg = self._public.nameAlg + else: + scheme = self._public.parameters.eccDetail.scheme + if scheme.scheme == TPM2_ALG.ECDSA: + algorithm = self.get_digest_algorithm() + sig_alg = ec.ECDSA(algorithm()) + else: + raise ValueError(f"unsupported signature scheme {scheme.scheme}") + return sig_alg + + def sign( + self, data: bytes, signature_algorithm: ec.EllipticCurveSignatureAlgorithm + ) -> bytes: + """Implements the sign interface. + + See :py:meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign`: for documentation. + + Raises: + ValueError: if the requested signature algorithm isn't supported by the key or the sign_encrypt bit isn't set. + """ + if not self._public.objectAttributes & TPMA_OBJECT.SIGN_ENCRYPT: + raise ValueError( + "TPM key does not allow signing (object attribute sign_encrypt is not set)" + ) + algorithm = signature_algorithm.algorithm + if isinstance(algorithm, Prehashed): + raise ValueError("Prehashed data is not supported") + scheme = TPMT_SIG_SCHEME() + _ecc_sign_algorithm_to_scheme(signature_algorithm, scheme) + _compare_schemes(scheme, self._public.parameters.eccDetail.scheme) + h = hashes.Hash(algorithm) + h.update(data) + digest = h.finalize() + validation = TPMT_TK_HASHCHECK(tag=TPM2_ST.HASHCHECK, hierarchy=TPM2_RH.NULL) + tpm_sig = self._ectx.sign( + self._handle, digest, scheme, validation, session1=self._session + ) + return bytes(tpm_sig) + + @property + def curve(self) -> ec.EllipticCurve: + """The ECC curve.""" + return self._curve() + + @property + def key_size(self) -> int: + """The ECC key size.""" + return self.public_key().key_size + + def private_numbers(self) -> None: + """Always raises a NotImplementedError.""" + raise NotImplementedError() + + def private_bytes( + self, + encoding: Encoding, + format: PrivateFormat, + encryption_algorithm: KeySerializationEncryption, + ) -> None: + """Always raises a NotImplementedError.""" + raise NotImplementedError() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/src/tpm2_pytss/encoding.py new/tpm2-pytss-2.3.0/src/tpm2_pytss/encoding.py --- old/tpm2-pytss-2.2.0/src/tpm2_pytss/encoding.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/src/tpm2_pytss/encoding.py 2024-06-27 10:36:21.000000000 +0200 @@ -80,6 +80,7 @@ ) import yaml import collections.abc +import warnings class base_encdec(object): @@ -825,6 +826,14 @@ """Encode TPM types in the same format as tpm2-tools """ + def __init__(self): + warnings.warn( + "The tools_encdec class will be deprecated in the future, " + "see https://github.com/tpm2-software/tpm2-pytss/issues/557", + category=PendingDeprecationWarning, + ) + super().__init__() + def encode_friendly_int_nv(self, val: TPM_FRIENDLY_INT) -> Dict[str, str]: d = { "friendly": str(val), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/src/tpm2_pytss/internal/crypto.py new/tpm2-pytss-2.3.0/src/tpm2_pytss/internal/crypto.py --- old/tpm2-pytss-2.2.0/src/tpm2_pytss/internal/crypto.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/src/tpm2_pytss/internal/crypto.py 2024-06-27 10:36:21.000000000 +0200 @@ -23,8 +23,9 @@ from cryptography.hazmat.primitives.ciphers import modes, Cipher, CipherAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -from typing import Tuple, Type +from typing import Tuple, Type, Any import secrets +import inspect import sys _curvetable = ( @@ -84,6 +85,15 @@ return None +def _get_pyca_digest(digest_type): + for (algid, d) in _digesttable: + if inspect.isclass(digest_type) and issubclass(digest_type, d): + return algid + elif isinstance(digest_type, d): + return algid + return None + + def _get_alg(alg): for (algid, a) in _algtable: if algid == alg: @@ -220,7 +230,7 @@ return key -class _MyRSAPrivateNumbers(rsa.RSAPrivateNumbers): +class _MyRSAPrivateNumbers: def __init__(self, p: int, n: int, e: int, pubnums: rsa.RSAPublicNumbers): q = n // p @@ -231,7 +241,12 @@ dmq1 = rsa.rsa_crt_dmq1(d, q) iqmp = rsa.rsa_crt_iqmp(p, q) - super().__init__(p, q, d, dmp1, dmq1, iqmp, pubnums) + self._private_numbers = rsa.RSAPrivateNumbers( + p, q, d, dmp1, dmq1, iqmp, pubnums + ) + + def private_key(self, *args: Any, **kwargs: Any) -> rsa.RSAPrivateKey: + return self._private_numbers.private_key(*args, **kwargs) @staticmethod def _xgcd(a: int, b: int) -> Tuple[int, int, int]: @@ -251,15 +266,7 @@ # @staticmethod def _modinv(a, m): - - if sys.version_info < (3, 8): - g, x, y = _MyRSAPrivateNumbers._xgcd(a, m) - if g != 1: - raise Exception("modular inverse does not exist") - else: - return x % m - else: - return pow(a, -1, m) + return pow(a, -1, m) @staticmethod def _generate_d(p, q, e, n): @@ -644,3 +651,60 @@ decr = ciph.decryptor() plaintextdata = decr.update(data) + decr.finalize() return plaintextdata + + +def _rsa_decrypt_padding_to_scheme( + decrypt_padding: padding.AsymmetricPadding, scheme: "TPMT_RSA_DECRYPT" +): + if isinstance(decrypt_padding, padding.OAEP): + if hasattr(decrypt_padding, "algorithm"): + alg = decrypt_padding.algorithm + elif hasattr(decrypt_padding, "_algorithm"): + # This is an ugly hack, but until cryptography 42 is released it's needed. + alg = type(decrypt_padding._algorithm) + else: + raise ValueError("unable to get hash algorithm from OAEP padding") + scheme.scheme = TPM2_ALG.OAEP + halg = _get_pyca_digest(alg) + if halg is None: + raise ValueError(f"unsupported digest algorithm {alg}") + scheme.details.oaep.hashAlg = halg + elif isinstance(decrypt_padding, padding.PKCS1v15): + scheme.scheme = TPM2_ALG.RSAES + else: + raise ValueError(f"unsupported RSA decryption scheme: {decrypt_padding}") + return + + +def _rsa_sign_padding_to_scheme( + sign_padding: padding.AsymmetricPadding, + algorithm: hashes.HashAlgorithm, + scheme: "TPMT_SIG_SCHEME", +): + if isinstance(sign_padding, padding.PSS): + scheme.scheme = TPM2_ALG.RSAPSS + + elif isinstance(sign_padding, padding.PKCS1v15): + scheme.scheme = TPM2_ALG.RSASSA + else: + raise ValueError(f"unsupported RSA signature scheme: {sign_padding}") + halg = _get_pyca_digest(algorithm) + if halg is None: + raise ValueError(f"unsupported digest algorithm {algorithm}") + scheme.details.any.hashAlg = halg + return + + +def _ecc_sign_algorithm_to_scheme( + sign_alg: ec.EllipticCurveSignatureAlgorithm, scheme: "TPMT_SIG_SCHEME" +): + if isinstance(sign_alg, ec.ECDSA): + scheme.scheme = TPM2_ALG.ECDSA + algorithm = sign_alg.algorithm + else: + raise ValueError(f"unsupported ECC signature scheme: {sign_alg}") + halg = _get_pyca_digest(type(algorithm)) + if halg is None: + raise ValueError(f"unsupported digest algorithm {algorithm}") + scheme.details.any.hashAlg = halg + return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_crypto.py new/tpm2-pytss-2.3.0/test/test_crypto.py --- old/tpm2-pytss-2.2.0/test/test_crypto.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_crypto.py 2024-06-27 10:36:21.000000000 +0200 @@ -8,6 +8,7 @@ from base64 import b64decode from hashlib import sha256, sha384 from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.exceptions import UnsupportedAlgorithm rsa_private_key = b""" -----BEGIN RSA PRIVATE KEY----- @@ -206,7 +207,7 @@ self._has_sect163r2 = True try: load_pem_public_key(ecc_bad_curve) - except ValueError: + except (ValueError, UnsupportedAlgorithm): self._has_sect163r2 = False def test_public_from_pem_rsa(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_cryptography.py new/tpm2-pytss-2.3.0/test/test_cryptography.py --- old/tpm2-pytss-2.2.0/test/test_cryptography.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_cryptography.py 2024-06-27 10:36:21.000000000 +0200 @@ -0,0 +1,504 @@ +#!/usr/bin/python3 -u +# SPDX-License-Identifier: BSD-2 + +from .TSS2_BaseTest import TSS2_EsapiTest +from tpm2_pytss.constants import TPMA_OBJECT, TPM2_ECC, TPM2_ALG +from tpm2_pytss.types import TPM2B_PUBLIC +from tpm2_pytss.cryptography import tpm_rsa_private_key, tpm_ecc_private_key +from cryptography.hazmat.primitives.asymmetric.padding import OAEP, MGF1, PKCS1v15, PSS +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +from cryptography import x509 +import datetime + + +rsa_template = TPM2B_PUBLIC.parse( + "rsa2048", + objectAttributes=TPMA_OBJECT.DECRYPT + | TPMA_OBJECT.SIGN_ENCRYPT + | TPMA_OBJECT.FIXEDTPM + | TPMA_OBJECT.FIXEDPARENT + | TPMA_OBJECT.SENSITIVEDATAORIGIN + | TPMA_OBJECT.USERWITHAUTH, +) + +ecc_template = TPM2B_PUBLIC.parse( + "ecc256", + objectAttributes=TPMA_OBJECT.DECRYPT + | TPMA_OBJECT.SIGN_ENCRYPT + | TPMA_OBJECT.FIXEDTPM + | TPMA_OBJECT.FIXEDPARENT + | TPMA_OBJECT.SENSITIVEDATAORIGIN + | TPMA_OBJECT.USERWITHAUTH, +) + + +class TestCryptography(TSS2_EsapiTest): + def test_rsa_key(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + self.assertEqual(privkey.key_size, 2048) + + with self.assertRaises(NotImplementedError) as e: + privkey.private_numbers() + + with self.assertRaises(NotImplementedError) as e: + privkey.private_bytes(encoding=None, format=None, encryption_algorithm=None) + + def test_rsa_decrypt_oaep(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + padding = privkey.get_decryption_padding() + encrypted_data = pubkey.encrypt(b"falafel", padding) + + decrypted_data = privkey.decrypt(encrypted_data, padding) + self.assertEqual(decrypted_data, b"falafel") + + def test_rsa_decrypt_pkcs1v15(self): + rsaes = TPM2B_PUBLIC(rsa_template) + rsaes.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSAES + rsaes.publicArea.objectAttributes ^= TPMA_OBJECT.SIGN_ENCRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsaes + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + padding = privkey.get_decryption_padding() + encrypted_data = pubkey.encrypt(b"falafel", padding) + + decrypted_data = privkey.decrypt(encrypted_data, padding) + self.assertEqual(decrypted_data, b"falafel") + + def test_rsa_key_bad_type(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + with self.assertRaises(ValueError) as e: + tpm_rsa_private_key(self.ectx, handle) + self.assertEqual(str(e.exception), "invalid key type, expected rsa, got ecc") + + def test_rsa_key_restricted(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public="rsa2048" + ) + with self.assertRaises(ValueError) as e: + tpm_rsa_private_key(self.ectx, handle) + self.assertEqual( + str(e.exception), + "TPM key does not allow generic signing and/or decryption (object attribute restricted is set)", + ) + + def test_rsa_sign_pss(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + padding = privkey.get_signature_padding() + halg = privkey.get_digest_algorithm() + + sig = privkey.sign(b"falafel", padding, halg()) + pubkey.verify(sig, b"falafel", padding, halg()) + + def test_rsa_sign_pkcs1v15(self): + rsassa = TPM2B_PUBLIC(rsa_template) + rsassa.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSASSA + rsassa.publicArea.parameters.rsaDetail.scheme.details.anySig.hashAlg = ( + TPM2_ALG.SHA384 + ) + rsassa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsassa + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + padding = privkey.get_signature_padding() + halg = privkey.get_digest_algorithm() + + sig = privkey.sign(b"falafel", padding, halg()) + pubkey.verify(sig, b"falafel", padding, halg()) + + def test_rsa_no_decrypt(self): + rsa_no_decrypt = TPM2B_PUBLIC(rsa_template) + rsa_no_decrypt.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_no_decrypt + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = PKCS1v15() + with self.assertRaises(ValueError) as e: + privkey.decrypt(b"falafel", padding) + self.assertEqual( + str(e.exception), + "TPM key does not allow decryption (object attribute decrypt is not set)", + ) + + def test_rsa_no_sign(self): + rsa_no_sign = TPM2B_PUBLIC(rsa_template) + rsa_no_sign.publicArea.objectAttributes ^= TPMA_OBJECT.SIGN_ENCRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_no_sign + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = PKCS1v15() + halg = privkey.get_digest_algorithm() + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", padding, halg()) + self.assertEqual( + str(e.exception), + "TPM key does not allow signing (object attribute sign_encrypt is not set)", + ) + + def test_rsa_prehashed(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = PKCS1v15() + halg = privkey.get_digest_algorithm() + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", padding, Prehashed(halg())) + self.assertEqual(str(e.exception), "Prehashed data is not supported") + + def test_rsa_unsupported_sig_scheme(self): + rsaes = TPM2B_PUBLIC(rsa_template) + rsaes.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSAES + rsaes.publicArea.objectAttributes ^= TPMA_OBJECT.SIGN_ENCRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsaes + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + with self.assertRaises(ValueError) as e: + privkey.get_signature_padding() + self.assertEqual(str(e.exception), "unsupported signature scheme rsaes") + + def test_rsa_unsupported_decrypt_scheme(self): + rsassa = TPM2B_PUBLIC(rsa_template) + rsassa.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSASSA + rsassa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsassa + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + with self.assertRaises(ValueError) as e: + privkey.get_decryption_padding() + self.assertEqual(str(e.exception), "unsupported decryption scheme rsassa") + + def test_ecc_key(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + self.assertEqual(privkey.key_size, 256) + self.assertIsInstance(privkey.curve, ec.SECP256R1) + + with self.assertRaises(NotImplementedError) as e: + privkey.private_numbers() + + with self.assertRaises(NotImplementedError) as e: + privkey.private_bytes(encoding=None, format=None, encryption_algorithm=None) + + def test_ecc_key_bad_type(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + with self.assertRaises(ValueError) as e: + tpm_ecc_private_key(self.ectx, handle) + self.assertEqual(str(e.exception), "invalid key type, expected ecc, got rsa") + + def test_ecc_key_restricted(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public="ecc256" + ) + with self.assertRaises(ValueError) as e: + tpm_ecc_private_key(self.ectx, handle) + self.assertEqual( + str(e.exception), + "TPM key does not allow generic signing and/or decryption (object attribute restricted is set)", + ) + + def test_ecc_exchange(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + peer_key = ec.generate_private_key(privkey.curve) + peer_public_key = peer_key.public_key() + + tpm_shared_key = privkey.exchange(ec.ECDH(), peer_public_key) + pyca_shared_key = peer_key.exchange(ec.ECDH(), privkey.public_key()) + self.assertEqual(tpm_shared_key, pyca_shared_key) + + def test_ecc_sign(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + sigalg = privkey.get_signature_algorithm() + sig = privkey.sign(b"falafel", sigalg) + + pubkey.verify(sig, b"falafel", sigalg) + + def test_ecc_sign_with_scheme(self): + ecc_ecdsa = TPM2B_PUBLIC(ecc_template) + ecc_ecdsa.publicArea.parameters.eccDetail.scheme.scheme = TPM2_ALG.ECDSA + ecc_ecdsa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_ecdsa + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + sigalg = privkey.get_signature_algorithm() + sig = privkey.sign(b"falafel", sigalg) + + pubkey.verify(sig, b"falafel", sigalg) + + def test_ecc_no_decrypt(self): + ecc_no_decrypt = TPM2B_PUBLIC(ecc_template) + ecc_no_decrypt.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_no_decrypt + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + peer_key = ec.generate_private_key(privkey.curve) + peer_public_key = peer_key.public_key() + + with self.assertRaises(ValueError) as e: + privkey.exchange(ec.ECDH(), peer_public_key) + self.assertEqual( + str(e.exception), + "TPM key does not allow ECDH key exchange (object attribute decrypt is not set)", + ) + + def test_ecc_different_curves(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + peer_key = ec.generate_private_key(ec.SECP192R1()) + peer_public_key = peer_key.public_key() + + with self.assertRaises(ValueError) as e: + privkey.exchange(ec.ECDH(), peer_public_key) + self.assertEqual( + str(e.exception), + "curve mismatch for peer key, got secp192r1, expected secp256r1", + ) + + def test_ecc_no_sign(self): + ecc_no_sign = TPM2B_PUBLIC(ecc_template) + ecc_no_sign.publicArea.objectAttributes ^= TPMA_OBJECT.SIGN_ENCRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_no_sign + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + halg = privkey.get_digest_algorithm() + sigalg = ec.ECDSA(halg()) + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", sigalg) + self.assertEqual( + str(e.exception), + "TPM key does not allow signing (object attribute sign_encrypt is not set)", + ) + + def test_ecc_prehashed(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + halg = privkey.get_digest_algorithm() + sigalg = ec.ECDSA(Prehashed(halg())) + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", sigalg) + self.assertEqual(str(e.exception), "Prehashed data is not supported") + + def test_ecc_unsupported_curve(self): + ecc_brainpool = TPM2B_PUBLIC(ecc_template) + ecc_brainpool.publicArea.parameters.eccDetail.curveID = TPM2_ECC.BN_P256 + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_brainpool + ) + + with self.assertRaises(ValueError) as e: + tpm_ecc_private_key(self.ectx, handle) + self.assertEqual(str(e.exception), "unsupported curve bn_p256") + + def test_ecc_unsupported_scheme(self): + ecc_ecdaa = TPM2B_PUBLIC(ecc_template) + ecc_ecdaa.publicArea.parameters.eccDetail.scheme.scheme = TPM2_ALG.ECDAA + ecc_ecdaa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_ecdaa + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + with self.assertRaises(ValueError) as e: + privkey.get_signature_algorithm() + self.assertEqual(str(e.exception), "unsupported signature scheme ecdaa") + + def test_scheme_mismatch(self): + rsassa = TPM2B_PUBLIC(rsa_template) + rsassa.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSASSA + rsassa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsassa + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = PSS( + mgf=MGF1(algorithm=hashes.SHA256()), salt_length=PSS.DIGEST_LENGTH + ) + + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", padding, hashes.SHA256()) + self.assertEqual( + str(e.exception), + "invalid scheme, scheme has rsapss but key requires rsassa", + ) + + def test_scheme_digest_mismatch(self): + rsassa = TPM2B_PUBLIC(rsa_template) + rsassa.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.RSASSA + rsassa.publicArea.parameters.rsaDetail.scheme.details.anySig.hashAlg = ( + TPM2_ALG.SHA1 + ) + rsassa.publicArea.objectAttributes ^= TPMA_OBJECT.DECRYPT + + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsassa + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = PKCS1v15() + + with self.assertRaises(ValueError) as e: + privkey.sign(b"falafel", padding, hashes.SHA256()) + self.assertEqual( + str(e.exception), + "digest algorithm mismatch, scheme has sha256 but key requires sha", + ) + + def test_scheme_digest_mismatch_oaep(self): + rsa_oaep = TPM2B_PUBLIC(rsa_template) + rsa_oaep.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG.OAEP + rsa_oaep.publicArea.parameters.rsaDetail.scheme.details.anySig.hashAlg = ( + TPM2_ALG.SHA256 + ) + rsa_oaep.publicArea.objectAttributes ^= TPMA_OBJECT.SIGN_ENCRYPT + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_oaep + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + padding = OAEP( + mgf=MGF1(algorithm=hashes.SHA512()), algorithm=hashes.SHA384(), label=b"" + ) + + with self.assertRaises(ValueError) as e: + privkey.decrypt(b"falafel", padding) + self.assertEqual( + str(e.exception), + "digest algorithm mismatch, scheme has sha384 but key requires sha256", + ) + + def test_cert_builder_rsa(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + builder = builder.serial_number(x509.random_serial_number()) + one_day = datetime.timedelta(1, 0, 0) + builder = builder.not_valid_before(datetime.datetime.today() - one_day) + builder = builder.not_valid_after(datetime.datetime.today() + one_day) + builder = builder.public_key(pubkey) + + halg = privkey.get_digest_algorithm() + cert = builder.sign(privkey, algorithm=halg()) + cert.verify_directly_issued_by(cert) + + def test_csr_builder_rsa(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=rsa_template + ) + privkey = tpm_rsa_private_key(self.ectx, handle) + + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + halg = privkey.get_digest_algorithm() + csr = builder.sign(privkey, algorithm=halg()) + self.assertEqual(csr.is_signature_valid, True) + + def test_cert_builder_ecc(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + pubkey = privkey.public_key() + + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + builder = builder.serial_number(x509.random_serial_number()) + one_day = datetime.timedelta(1, 0, 0) + builder = builder.not_valid_before(datetime.datetime.today() - one_day) + builder = builder.not_valid_after(datetime.datetime.today() + one_day) + builder = builder.public_key(pubkey) + + halg = privkey.get_digest_algorithm() + cert = builder.sign(privkey, algorithm=halg()) + cert.verify_directly_issued_by(cert) + + def test_csr_builder_ecc(self): + handle, _, _, _, _ = self.ectx.create_primary( + in_sensitive=None, in_public=ecc_template + ) + privkey = tpm_ecc_private_key(self.ectx, handle) + + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "falafel"),]) + ) + halg = privkey.get_digest_algorithm() + csr = builder.sign(privkey, algorithm=halg()) + self.assertEqual(csr.is_signature_valid, True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_encoding.py new/tpm2-pytss-2.3.0/test/test_encoding.py --- old/tpm2-pytss-2.2.0/test/test_encoding.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_encoding.py 2024-06-27 10:36:21.000000000 +0200 @@ -1406,7 +1406,7 @@ def test_tools_decode_tpm2b_name(self): if not self.has_tools: self.skipTest("tools not in path") - key = ec.generate_private_key(ec.SECP256R1).public_key() + key = ec.generate_private_key(ec.SECP256R1()).public_key() kb = key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_esapi.py new/tpm2-pytss-2.3.0/test/test_esapi.py --- old/tpm2-pytss-2.2.0/test/test_esapi.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_esapi.py 2024-06-27 10:36:21.000000000 +0200 @@ -3585,6 +3585,8 @@ self.ectx.pcr_allocate(pcrsels, session3=object()) def test_pcr_set_auth_policy(self): + if getattr(self.tcti, "name", "") == "swtpm": + self.skipTest("pcr_set_auth_policy not supported by swtpm") policy = b"0123456789ABCDEF0123456789ABCDEF" self.ectx.pcr_set_auth_policy(policy, TPM2_ALG.SHA256, ESYS_TR.PCR20) @@ -3630,6 +3632,8 @@ ) def test_pcr_set_auth_value(self): + if getattr(self.tcti, "name", "") == "swtpm": + self.skipTest("pcr_set_auth_value not supported by swtpm") self.ectx.pcr_set_auth_value(ESYS_TR.PCR20, b"password") self.ectx.tr_set_auth(ESYS_TR.PCR20, b"password") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_fapi.py new/tpm2-pytss-2.3.0/test/test_fapi.py --- old/tpm2-pytss-2.2.0/test/test_fapi.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_fapi.py 2024-06-27 10:36:21.000000000 +0200 @@ -961,6 +961,22 @@ with pytest.raises(TSS2_Exception): self.fapi.sign(path=key_path, digest=b"\x11" * 32) + def test_nv_create_double_ok(self): + nv_path = f"/nv/Owner/nv_{random_uid()}" + created = self.fapi.create_nv(path=nv_path, size=10) + assert created == True + + created = self.fapi.create_nv(path=nv_path, size=10, exists_ok=True) + assert created == False + + def test_nv_create_double_fail(self): + nv_path = f"/nv/Owner/nv_{random_uid()}" + created = self.fapi.create_nv(path=nv_path, size=10) + assert created == True + + with pytest.raises(TSS2_Exception): + self.fapi.create_nv(path=nv_path, size=10) + @pytest.mark.usefixtures("init_fapi_ecc") class TestFapiECC(Common): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tpm2-pytss-2.2.0/test/test_policy.py new/tpm2-pytss-2.3.0/test/test_policy.py --- old/tpm2-pytss-2.2.0/test/test_policy.py 2024-01-06 00:50:04.000000000 +0100 +++ new/tpm2-pytss-2.3.0/test/test_policy.py 2024-06-27 10:36:21.000000000 +0200 @@ -47,7 +47,7 @@ super().setUp() self._has_secp192r1 = True try: - ec.generate_private_key(ec.SECP192R1) + ec.generate_private_key(ec.SECP192R1()) except Exception: self._has_secp192r1 = False