# 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}"
)
[docs]
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)"
)
[docs]
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)
[docs]
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
[docs]
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
[docs]
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
[docs]
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
[docs]
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)
[docs]
def private_numbers(self) -> None:
"""Always raises a NotImplementedError."""
raise NotImplementedError()
[docs]
def private_bytes(
self,
encoding: Encoding,
format: PrivateFormat,
encryption_algorithm: KeySerializationEncryption,
) -> None:
"""Always raises a NotImplementedError."""
raise NotImplementedError()
[docs]
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
[docs]
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)
[docs]
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)
[docs]
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
[docs]
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
[docs]
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
[docs]
def private_numbers(self) -> None:
"""Always raises a NotImplementedError."""
raise NotImplementedError()
[docs]
def private_bytes(
self,
encoding: Encoding,
format: PrivateFormat,
encryption_algorithm: KeySerializationEncryption,
) -> None:
"""Always raises a NotImplementedError."""
raise NotImplementedError()