Source code for tpm2_pytss.types

# SPDX-License-Identifier: BSD-2
"""
The types module contains types for each of the corresponding TPM types from the following TCG specifications:

- https://trustedcomputinggroup.org/resource/tpm-library-specification/. See Part 2 "Structures".
- https://trustedcomputinggroup.org/resource/tss-overview-common-structures-specification

The classes contained within can be initialized based on named argument value pairs or dictionaries
of key-value objects where the keys are the names of the associated type.

"""
from ._libtpm2_pytss import ffi, lib

from tpm2_pytss.internal.utils import (
    _chkrc,
    _fixup_cdata_kwargs,
    _cpointer_to_ctype,
    _fixup_classname,
    _convert_to_python_native,
    _mock_bail,
    _ref_parent,
)
from tpm2_pytss.internal.crypto import (
    _calculate_sym_unique,
    _get_digest_size,
    _public_from_encoding,
    _private_from_encoding,
    _public_to_pem,
    _getname,
    _verify_signature,
    private_to_key,
)
import tpm2_pytss.constants as constants  # lgtm [py/import-and-import-from]
from tpm2_pytss.constants import (
    TPMA_OBJECT,
    TPM2_ALG,
    TPM2_ECC_CURVE,
)
from typing import Union, Tuple
import sys

try:
    from tpm2_pytss.internal.type_mapping import _type_map, _element_type_map
except ImportError as e:
    # this is needed so docs can be generated without building
    if "sphinx" not in sys.modules:
        raise e

import binascii
import secrets

from cryptography.hazmat.primitives import serialization


[docs]class ParserAttributeError(Exception): """ Exception ocurred when when parsing.""" pass
[docs]class TPM2_HANDLE(int): """"A handle to a TPM address""" pass
[docs]class TPM_OBJECT(object): """ Abstract Base class for all TPM Objects. Not suitable for direct instantiation.""" def __init__(self, _cdata=None, **kwargs): # Rather than trying to mock the FFI interface, just avoid it and return # the base object. This is really only needed for documentation, and it # makes it work. Why Yes, this is a terrible hack (cough cough). if _mock_bail(): return _cdata, kwargs = _fixup_cdata_kwargs(self, _cdata, kwargs) object.__setattr__(self, "_cdata", _cdata) tipe = _cpointer_to_ctype(self._cdata) expected_cname = _fixup_classname(tipe) # Because we alias TPM2B_AUTH as a TPM2B_DIGEST in the C code if ( expected_cname != "TPM2B_DIGEST" and expected_cname != self.__class__.__name__ and "TPM2B_" not in expected_cname ): raise TypeError( f"Unexpected _cdata type {expected_cname}, expected {self.__class__.__name__}" ) fields = {x[0]: x[1].type for x in tipe.fields} for k, v in kwargs.items(): if k not in fields: raise AttributeError( f"{self.__class__.__name__} has no field by the name of {k}" ) cname = fields[k] if cname.kind != "primitive" and cname.kind != "array": clsname = _fixup_classname(cname) clazz = globals()[clsname] # If subclass object is a TPM2B SIMPLE object, and we have a raw str, or bytes, convert if issubclass(clazz, TPM2B_SIMPLE_OBJECT) and isinstance( v, (str, bytes) ): _bytefield = clazz._get_bytefield() subobj = clazz(_cdata=None) setattr(subobj, _bytefield, v) v = subobj TPM_OBJECT.__setattr__(self, k, v) def __getattribute__(self, key): try: # go through object to avoid invoking THIS objects __getattribute__ call # and thus infinite recursion return object.__getattribute__(self, key) except AttributeError: # Ok the object has no idea what you're looking for... can we handle it? # Yes we could use self._cdata as it will only recurse once, but lets avoid it. _cdata = object.__getattribute__(self, "_cdata") # Get the attribute they're looking for out of _cdata x = getattr(_cdata, key) tm = _type_map.get((self.__class__.__name__, key)) if tm is not None and hasattr(constants, tm): c = getattr(constants, tm) obj = c(x) elif tm is not None: obj = globals()[tm](x) else: obj = _convert_to_python_native(globals(), x, parent=self._cdata) return obj def __setattr__(self, key, value): _cdata = object.__getattribute__(self, "_cdata") if isinstance(value, TPM_OBJECT): tipe = ffi.typeof(value._cdata) if tipe.kind in ["struct", "union"]: value = value._cdata else: value = value._cdata[0] try: # Get _cdata without invoking getattr setattr(_cdata, key, value) except AttributeError: return object.__setattr__(self, key, value) except TypeError as e: data = getattr(_cdata, key) tipe = ffi.typeof(data) clsname = _fixup_classname(tipe) clazz = None try: clazz = globals()[clsname] except KeyError: raise e _bytefield = clazz._get_bytefield() data = getattr(data, _bytefield) tipe = ffi.typeof(data) if tipe.kind != "array" or not issubclass(clazz, TPM2B_SIMPLE_OBJECT): raise e if isinstance(value, str): value = value.encode() subobj = clazz(_cdata=None) setattr(subobj, _bytefield, value) value = subobj # recurse so we can get handling of setattr with Python wrapped data setattr(self, key, value) def __dir__(self): return object.__dir__(self) + dir(self._cdata)
[docs] def marshal(self): """Marshal instance into bytes. Returns: Returns the marshaled type as bytes. """ mfunc = getattr(lib, f"Tss2_MU_{self.__class__.__name__}_Marshal", None) if mfunc is None: raise RuntimeError( f"No marshal function found for {self.__class__.__name__}" ) _cdata = self._cdata tipe = ffi.typeof(_cdata) if tipe.kind != "pointer": _cdata = ffi.new(f"{self.__class__.__name__} *", self._cdata) offset = ffi.new("size_t *") buf = ffi.new("uint8_t[4096]") _chkrc(mfunc(_cdata, buf, 4096, offset)) return bytes(buf[0 : offset[0]])
[docs] @classmethod def unmarshal(cls, buf): """Unmarshal bytes into type instance. Args: buf (bytes): The bytes to be unmarshaled. Returns: Returns an instance of the current type and the number of bytes consumed. """ umfunc = getattr(lib, f"Tss2_MU_{cls.__name__}_Unmarshal", None) if umfunc is None: raise RuntimeError(f"No unmarshal function found for {cls.__name__}") _cdata = ffi.new(f"{cls.__name__} *") offset = ffi.new("size_t *") _chkrc(umfunc(buf, len(buf), offset, _cdata)) return cls(_cdata=_cdata), offset[0]
[docs]class TPM2B_SIMPLE_OBJECT(TPM_OBJECT): """ Abstract Base class for all TPM2B Simple Objects. A Simple object contains only a size and byte buffer fields. This is not suitable for direct instantiation.""" def __init__(self, _cdata=None, **kwargs): _cdata, kwargs = _fixup_cdata_kwargs(self, _cdata, kwargs) _bytefield = type(self)._get_bytefield() for k, v in kwargs.items(): if k == "size": raise AttributeError(f"{k} is read only") if k != _bytefield: raise AttributeError(f"{self.__name__} has no field {k}") if isinstance(v, str): v = v.encode() setattr(_cdata, k, v) _cdata.size = len(v) super().__init__(_cdata=_cdata) @classmethod def _get_bytefield(cls): tipe = ffi.typeof(f"{cls.__name__}") for f in tipe.fields: if f[0] != "size": return f[0] def __setattr__(self, key, value): if key == "size": raise AttributeError(f"{key} is read only") _bytefield = type(self)._get_bytefield() if key == _bytefield: if isinstance(value, str): value = value.encode() setattr(self._cdata, key, value) self._cdata.size = len(value) else: super().__setattr__(key, value) def __getattribute__(self, key): _bytefield = type(self)._get_bytefield() if key == _bytefield: b = getattr(self._cdata, _bytefield) rb = _ref_parent(b, self._cdata) return memoryview(ffi.buffer(rb, self._cdata.size)) return super().__getattribute__(key) def __len__(self): return self._cdata.size def __getitem__(self, index): _bytefield = type(self)._get_bytefield() buf = getattr(self, _bytefield) if isinstance(index, int): if index >= self._cdata.size: raise IndexError("out of range") return buf[index] elif isinstance(index, slice): return buf[index] else: raise TypeError("index must an int or a slice") def __bytes__(self): _bytefield = type(self)._get_bytefield() buf = getattr(self, _bytefield) return bytes(buf)
[docs] def __str__(self) -> str: """Returns a hex string representation of the underlying buffer. This is the same as: .. code-block:: python bytes(tpm2b_type).hex() Returns (str): A hex encoded string of the buffer. """ b = self.__bytes__() return binascii.hexlify(b).decode()
def __eq__(self, value): b = self.__bytes__() return b == value
[docs]class TPML_Iterator(object): """ Iterator class for iterating over TPML data types. This class is used in enumerated for loops, such as: .. code-block:: python for alg in TPML_ALG: do_something(alg) """ def __init__(self, tpml): self._tpml = tpml self._index = 0 def __iter__(self): return self def __next__(self): if self._index > self._tpml.count - 1: raise StopIteration x = self._tpml[self._index] self._index = self._index + 1 return x
[docs]class TPML_OBJECT(TPM_OBJECT): """ Abstract Base class for all TPML Objects. A TPML object is an object that contains a list of objects. This is not suitable for direct instantiation.""" def __init__(self, _cdata=None, **kwargs): _cdata, kwargs = _fixup_cdata_kwargs(self, _cdata, kwargs) super().__init__(_cdata=_cdata) # Nothing todo if len(kwargs) == 0: return key = [*kwargs][0] cdata_array = self._cdata.__getattribute__(key) if isinstance(kwargs[key], TPM_OBJECT): kwargs[key] = [kwargs[key]] if not isinstance(kwargs[key], (list, tuple)): raise TypeError( "Expected initializer for TPML data types to be a list or tuple" ) expected_class = TPM_OBJECT try: tipe = ffi.typeof(cdata_array[0]) clsname = _fixup_classname(tipe) expected_class = globals()[clsname] except TypeError: pass for i, x in enumerate(kwargs[key]): if not isinstance(x, (expected_class, int)): try: x = expected_class(x) except TypeError: # Provide a better error message raise TypeError( f'Expected item at index {i} to be a TPM_OBJECT, got: "{type(x)}"' ) cdata_array[i] = x._cdata[0] if isinstance(x, TPM_OBJECT) else x self._cdata.count = len(kwargs[key]) def __getattribute__(self, key): try: # Can the parent handle it? x = TPM_OBJECT.__getattribute__(self, key) return x except TypeError: pass # Must be a TPML style array # Get cdata without implicitly invoking a derived classes __getattribute__ # This will prevent recursion and stack depth issues. _cdata = object.__getattribute__(self, "_cdata") # This will invoke the CFFI implementation, so getattr is safe here. x = getattr(_cdata, key) # If this isn't a CFFI type, something wen't crazy, and typeof() will raise TypeError. tipe = ffi.typeof(x) if tipe.kind != "array": raise TypeError( f'Unknown scalar conversion for kind "{tipe.kind}" for key "{key}"' ) # subclasses in the arrays within the CTypes are fixed, so # we only need to do this once clsname = _fixup_classname(tipe.item) subclass = globals()[clsname] l = [] # do not go through __len__ count = _cdata.count for i in range(0, count): obj = subclass(_cdata=x[i]) l.append(obj) return l def __getitem__(self, item): item_was_int = isinstance(item, int) try: return object.__getitem__(self, item) except AttributeError: pass if not isinstance(item, (int, slice)): raise TypeError( f"list indices must be integers or slices, not {type(item)}" ) # figure out what part named _cdata to go into tipe = ffi.typeof(self._cdata) if tipe.kind == "pointer": tipe = tipe.item field_name = next((v[0] for v in tipe.fields if v[0] != "count"), None) if isinstance(item, int): item = slice(item, item + 1) if item.stop is None: item = slice(item.start, len(self) - 1, item.step) # get the cdata field cdata_list = self._cdata.__getattribute__(field_name) cdatas = cdata_list[item] tm = _element_type_map.get(self.__class__.__name__) if tm is not None and hasattr(constants, tm): c = getattr(constants, tm) cdatas = [c(x) for x in cdatas] elif tm is not None: cdatas = [globals()[tm](x) for x in cdatas] if len(cdatas) > 0 and not isinstance(cdatas[0], ffi.CData): return cdatas[0] if item_was_int else cdatas # convert it to python native objects = [_convert_to_python_native(globals(), x, self._cdata) for x in cdatas] if isinstance(objects[0], TPM2B_SIMPLE_OBJECT): objects = [bytes(x) for x in objects] return objects[0] if item_was_int else objects def __len__(self): return self._cdata.count def __setitem__(self, key, value): if not isinstance(key, (int, slice)): raise TypeError(f"list indices must be integers or slices, not {type(key)}") if isinstance(key, int) and not isinstance(value, (TPM_OBJECT, int)): raise TypeError( f"expected value to be TPM_OBJECT or integer not {type(value)}" ) tipe = ffi.typeof(self._cdata) if tipe.kind == "pointer": tipe = tipe.item field_name = next((v[0] for v in tipe.fields if v[0] != "count"), None) cdata_list = self._cdata.__getattribute__(field_name) # make everything looks like slice if isinstance(key, int): key = slice(key, key + 1, 1) value = [value] elif key.step is None: key = slice(key.start, key.stop, 1) r = range(key.start, key.stop, key.step) if len(r) != len(value): raise ValueError("Expected {len(r)} items to unpack, got: {len(value)}") for value_offset, cdata_offset in enumerate(r): x = value[value_offset] x = x._cdata[0] if isinstance(x, TPM_OBJECT) else x cdata_list[cdata_offset] = x if key.stop > self._cdata.count: self._cdata.count = key.stop def __iter__(self): return TPML_Iterator(self)
[docs]class TPMU_PUBLIC_PARMS(TPM_OBJECT): pass
[docs]class TPMT_PUBLIC_PARMS(TPM_OBJECT): pass
[docs]class TPMT_SYM_DEF_OBJECT(TPM_OBJECT): pass
[docs]class TPMT_ASYM_SCHEME(TPM_OBJECT): pass
[docs]class TPM2B_NAME(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPMT_PUBLIC(TPM_OBJECT): @staticmethod def _handle_rsa(objstr, templ): templ.type = TPM2_ALG.RSA if objstr is None or objstr == "": objstr = "2048" expected = ["1024", "2048", "3072", "4096"] if objstr not in expected: raise ValueError( f'Expected keybits for RSA to be one of {expected}, got:"{objstr}"' ) keybits = int(objstr) templ.parameters.rsaDetail.keyBits = keybits return True @staticmethod def _handle_ecc(objstr, templ): templ.type = TPM2_ALG.ECC if objstr is None or objstr == "": curve = TPM2_ECC_CURVE.NIST_P256 else: curve = TPM2_ECC_CURVE.parse(objstr) templ.parameters.eccDetail.curveID = curve templ.parameters.eccDetail.kdf.scheme = TPM2_ALG.NULL return True @staticmethod def _handle_sym_common(objstr): if objstr is None or len(objstr) == 0: objstr = "128" bits = objstr[:3] expected = ["128", "192", "256"] if bits not in expected: raise ValueError(f'Expected bits to be one of {expected}, got: "{bits}"') bits = int(bits) # go past bits objstr = objstr[3:] if len(objstr) == 0: mode = "null" else: expected = ["cfb", "cbc", "ofb", "ctr", "ecb"] if objstr not in expected: raise ValueError( f'Expected mode to be one of {expected}, got: "{objstr}"' ) mode = objstr mode = TPM2_ALG.parse(mode) return (bits, mode) @staticmethod def _handle_aes(objstr, templ): templ.type = TPM2_ALG.SYMCIPHER templ.parameters.symDetail.sym.algorithm = TPM2_ALG.AES bits, mode = TPMT_PUBLIC._handle_sym_common(objstr) templ.parameters.symDetail.sym.keyBits.sym = bits templ.parameters.symDetail.sym.mode.sym = mode return False @staticmethod def _handle_camellia(objstr, templ): templ.type = TPM2_ALG.SYMCIPHER templ.parameters.symDetail.sym.algorithm = TPM2_ALG.CAMELLIA bits, mode = TPMT_PUBLIC._handle_sym_common(objstr) templ.parameters.symDetail.sym.keyBits.sym = bits templ.parameters.symDetail.sym.mode.sym = mode return False @staticmethod def _handle_xor(_, templ): templ.type = TPM2_ALG.KEYEDHASH templ.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG.XOR return True @staticmethod def _handle_hmac(_, templ): templ.type = TPM2_ALG.KEYEDHASH templ.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG.HMAC return True @staticmethod def _handle_keyedhash(_, templ): templ.type = TPM2_ALG.KEYEDHASH templ.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG.NULL return False @staticmethod def _error_on_conflicting_sign_attrs(templ): """ If the scheme is set, both the encrypt and decrypt attributes cannot be set, check to see if this is the case, and turn down: - DECRYPT - If its a signing scheme. - ENCRYPT - If its an asymmetric enc scheme. :param templ: The template to modify """ # Nothing to do if templ.parameters.asymDetail.scheme.scheme == TPM2_ALG.NULL: return is_both_set = bool(templ.objectAttributes & TPMA_OBJECT.SIGN_ENCRYPT) and bool( templ.objectAttributes & TPMA_OBJECT.DECRYPT ) # One could smarten this up to behave like tpm2-tools and turn down the attribute, but for now # error on bad attribute sets if is_both_set: raise ParserAttributeError( "Cannot set both SIGN_ENCRYPT and DECRYPT in objectAttributes" ) @staticmethod def _handle_scheme_rsa(scheme, templ): if scheme is None or len(scheme) == 0: scheme = "null" halg = "" # rsaes must match exactly takes no other params if scheme == "rsaes": templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.RSAES TPMT_PUBLIC._error_on_conflicting_sign_attrs(templ) return elif scheme == "null": templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.NULL elif scheme.startswith("rsassa"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.RSASSA halg = scheme[len("rsassa") + 1 :] elif scheme.startswith("rsapss"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.RSAPSS halg = scheme[len("rsapss") + 1 :] elif scheme.startswith("oaep"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.OAEP halg = scheme[len("oaep") + 1 :] else: templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.NULL raise ValueError( f'Expected RSA scheme null or rsapss or prefix of rsapss, rsassa, got "{scheme}"' ) if halg == "": halg = "sha256" templ.parameters.asymDetail.scheme.details.anySig.hashAlg = TPM2_ALG.parse(halg) TPMT_PUBLIC._error_on_conflicting_sign_attrs(templ) return True @staticmethod def _handle_scheme_ecc(scheme, templ): if scheme is None or len(scheme) == 0: scheme = "null" halg = "" if scheme.startswith("ecdsa"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.ECDSA halg = scheme[len("ecdsa") + 1 :] elif scheme.startswith("ecdh"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.ECDH halg = scheme[len("ecdh") + 1 :] elif scheme.startswith("ecschnorr"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.ECSCHNORR halg = scheme[len("ecschnorr") + 1 :] elif scheme.startswith("ecdaa"): templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.ECDAA counter = scheme[5:] if len(scheme) > 5 else "0" hunks = counter.split("-") counter = hunks[0] halg = hunks[1] if len(hunks) > 1 else "" templ.parameters.eccDetail.scheme.details.ecdaa.count = int(counter) elif scheme == "null": templ.parameters.eccDetail.scheme.scheme = TPM2_ALG.NULL else: templ.parameters.asymDetail.scheme.scheme = TPM2_ALG.NULL raise ValueError( f'Expected EC scheme null or prefix of oaep, ecdsa, ecdh, scshnorr, ecdaa, got "{scheme}"' ) if halg == "": halg = "sha256" templ.parameters.asymDetail.scheme.details.anySig.hashAlg = TPM2_ALG.parse(halg) TPMT_PUBLIC._error_on_conflicting_sign_attrs(templ) return True @staticmethod def _handle_scheme_keyedhash(scheme, templ): if scheme is None or scheme == "": scheme = "sha256" halg = TPM2_ALG.parse(scheme) if templ.parameters.keyedHashDetail.scheme.scheme == TPM2_ALG.HMAC: templ.parameters.keyedHashDetail.scheme.details.hmac.hashAlg = halg elif templ.parameters.keyedHashDetail.scheme.scheme == TPM2_ALG.XOR: templ.parameters.keyedHashDetail.scheme.details.exclusiveOr.hashAlg = halg templ.parameters.keyedHashDetail.scheme.details.exclusiveOr.kdf = ( TPM2_ALG.KDF1_SP800_108 ) else: raise ValueError( f'Expected one of HMAC or XOR, got: "{templ.parameters.keyedHashDetail.scheme.scheme}"' ) @staticmethod def _handle_scheme(scheme, templ): if templ.type == TPM2_ALG.RSA: TPMT_PUBLIC._handle_scheme_rsa(scheme, templ) elif templ.type == TPM2_ALG.ECC: TPMT_PUBLIC._handle_scheme_ecc(scheme, templ) elif templ.type == TPM2_ALG.KEYEDHASH: TPMT_PUBLIC._handle_scheme_keyedhash(scheme, templ) else: # TODO make __str__ routine for int types raise ValueError( f'Expected object to be of type RSA, ECC or KEYEDHASH, got "{templ.type}"' ) @staticmethod def _handle_asymdetail(detail, templ): if templ.type == TPM2_ALG.KEYEDHASH: if detail is not None: raise ValueError( f'Keyedhash objects cannot have asym detail, got: "{detail}"' ) return if templ.type != TPM2_ALG.RSA and templ.type != TPM2_ALG.ECC: raise ValueError( f'Expected only RSA and ECC objects to have asymdetail, got: "{templ.type}"' ) is_restricted = bool(templ.objectAttributes & TPMA_OBJECT.RESTRICTED) is_rsapss = templ.parameters.asymDetail.scheme.scheme == TPM2_ALG.RSAPSS if detail is None or detail == "": detail = "aes128cfb" if is_restricted or is_rsapss else "null" if detail == "null": templ.parameters.symDetail.sym.algorithm = TPM2_ALG.NULL return if detail.startswith("aes"): templ.parameters.symDetail.sym.algorithm = TPM2_ALG.AES detail = detail[3:] elif detail.startswith("camellia"): templ.parameters.symDetail.sym.algorithm = TPM2_ALG.CAMELLIA detail = detail[8:] else: raise ValueError( f'Expected symmetric detail to be null or start with one of aes, camellia, got: "{detail}"' ) bits, mode = TPMT_PUBLIC._handle_sym_common(detail) templ.parameters.symDetail.sym.keyBits.sym = bits templ.parameters.symDetail.sym.mode.sym = mode
[docs] @classmethod def parse( cls, alg: str = "rsa", objectAttributes: Union[ TPMA_OBJECT, int, str ] = TPMA_OBJECT.DEFAULT_TPM2_TOOLS_CREATE_ATTRS, nameAlg: Union[TPM2_ALG, int, str] = "sha256", authPolicy: bytes = None, ) -> "TPMT_PUBLIC": """Builds a TPMT_PUBLIC from a tpm2-tools like specifier strings. This builds the TPMT_PUBLIC structure which can be used in TPM2_Create and TPM2_CreatePrimary commands that map into the tpm2-tools project as tpm2 create and createprimary commandlets. Those commands take options: -G, -n, -L and -a option to specify the object to create. This method converts those options, but does not create the object like tpm2-tools. Args: alg (str): The string specifier for the objects algorithm type, bitsize, symmetric cipher and scheme. This is tpm2-tools option "-G" as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/alg.md#complex-specifiers. objectAttiributes (TPMA_OBJECT, int, str): The objects attributes whihch can either the object attributes themselves or a nice name string value. This is tpm2-tools option "-a as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/obj-attrs.md. nameAlg (TPM2_ALG, int, str): The hashing algorithm for the objects name, either the TPM2_ALG constant, integer value or a friendly name string. This is tpm2-tools option "-n" as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/alg.md#hashing-algorithms authPolicy (bytes): The policy digest of the object. This is tpm2-tools option "-L". Returns: A populated TPMT_PUBLIC for use. Raises: ValueError: If a string value is not of an expected format. Examples: .. code-block:: python TPMT_PUBLIC.parse( "ecc:ecdh-sha384", objectAttributes=TPMA_OBJECT.DEFAULT_TPM2_TOOLS_CREATEPRIMARY_ATTRS) TPMT_PUBLIC.parse( alg="xor:sha512", nameAlg="sha256", authPolicy=b'\xc5\x81sS\xf2\x9bc\x87r\xdf\x01\xd3\xbaowM\x96Q\xaf\x1a\xeeKEO\x82\xfeV\xf3\x13^[\x87') """ templ = TPMT_PUBLIC() if isinstance(nameAlg, str): nameAlg = TPM2_ALG.parse(nameAlg) templ.nameAlg = nameAlg if isinstance(objectAttributes, str): objectAttributes = TPMA_OBJECT.parse(objectAttributes) templ.objectAttributes = objectAttributes if authPolicy is not None: templ.authPolicy = authPolicy alg = alg.lower() hunks = alg.split(":") objstr = hunks[0].lower() scheme = hunks[1].lower() if len(hunks) > 1 else None symdetail = hunks[2].lower() if len(hunks) > 2 else None expected = ("rsa", "ecc", "aes", "camellia", "xor", "hmac", "keyedhash") keep_processing = False prefix = tuple(filter(lambda x: objstr.startswith(x), expected)) if len(prefix) == 1: prefix = prefix[0] keep_processing = getattr(TPMT_PUBLIC, f"_handle_{prefix}")( objstr[len(prefix) :], templ ) else: raise ValueError( f'Expected object prefix to be one of {expected}, got: "{objstr}"' ) if not keep_processing: if scheme: raise ValueError( f'{prefix} objects cannot have additional specifiers, got: "{scheme}"' ) return templ # at this point we either have scheme as a scheme or an asym detail try: TPMT_PUBLIC._handle_scheme(scheme, templ) except ValueError as e: # nope try it as asymdetail symdetail = scheme TPMT_PUBLIC._handle_asymdetail(symdetail, templ) return templ
[docs] @classmethod def from_pem( cls, data: bytes, nameAlg: Union[TPM2_ALG, int] = TPM2_ALG.SHA256, objectAttributes: Union[TPMA_OBJECT, int] = ( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), symmetric: TPMT_SYM_DEF_OBJECT = None, scheme: TPMT_ASYM_SCHEME = None, password: bytes = None, ) -> "TPMT_PUBLIC": """Decode the public part from standard key encodings. Currently supports PEM, DER and SSH encoded public keys. Args: data (bytes): The encoded public key. nameAlg (TPM2_ALG, int): The name algorithm for the public area, default is TPM2_ALG.SHA256. objectAttributes (TPMA_OBJECT, int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). symmetric (TPMT_SYM_DEF_OBJECT) optional: The symmetric definition to use for the public area, default is None. scheme (TPMT_ASYM_SCHEME) optional: The signing/key exchange scheme to use for the public area, default is None. password (bytes) optional: The password used to decrypt the key, default is None. Returns: Returns a TPMT_PUBLIC instance. Raises: ValueError: If key parameters are not supported. Example: .. code-block:: python ecc_key_pem = open('path/to/myecckey.pem').read().encode() TPMT_PUBLIC.from_pem(ecc_key_pem) """ p = cls() _public_from_encoding(data, p, password=password) p.nameAlg = nameAlg if isinstance(objectAttributes, str): objectAttributes = TPMA_OBJECT.parse(objectAttributes) p.objectAttributes = objectAttributes if symmetric is None: p.parameters.asymDetail.symmetric.algorithm = TPM2_ALG.NULL elif isinstance(symmetric, str): TPMT_PUBLIC._handle_asymdetail(symmetric, p) else: p.parameters.asymDetail.symmetric = symmetric if scheme is None: p.parameters.asymDetail.scheme.scheme = TPM2_ALG.NULL elif isinstance(scheme, str): TPMT_PUBLIC._handle_scheme(scheme, p) else: p.parameters.asymDetail.scheme = scheme if p.type == TPM2_ALG.ECC: p.parameters.eccDetail.kdf.scheme = TPM2_ALG.NULL return p
[docs] def to_pem(self) -> bytes: """Encode the public key as PEM encoded ASN.1. Returns: Returns the PEM encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.publicArea.to_pem() """ return _public_to_pem(self, "pem")
[docs] def to_der(self) -> bytes: """Encode the public key as DER encoded ASN.1. Returns: Returns the DER encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.publicArea.to_der() """ return _public_to_pem(self, "der")
[docs] def to_ssh(self) -> bytes: """Encode the public key in OpenSSH format Returns: Returns the OpenSSH encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.publicArea.to_ssh() """ return _public_to_pem(self, "ssh")
[docs] def get_name(self) -> TPM2B_NAME: """Get the TPM name of the public area. This function requires a populated TPMT_PUBLIC and will NOT go to the TPM to retrieve the name, and instead calculates it manually. Returns: Returns TPM2B_NAME. Raises: ValueError: Unsupported name digest algorithm. """ name = _getname(self) return TPM2B_NAME(name)
[docs]class TPM2B_ATTEST(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_CONTEXT_DATA(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_CONTEXT_SENSITIVE(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_CREATION_DATA(TPM_OBJECT): pass
[docs]class TPM2B_DATA(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_DIGEST(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_ECC_PARAMETER(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_ECC_POINT(TPM_OBJECT): pass
[docs]class TPM2B_ENCRYPTED_SECRET(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_EVENT(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_ID_OBJECT(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_IV(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_MAX_BUFFER(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_MAX_NV_BUFFER(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_NV_PUBLIC(TPM_OBJECT):
[docs] def get_name(self) -> TPM2B_NAME: """Get the TPM name of the NV public area. This function requires a populated TPM2B_NV_PUBLIC and will NOT go to the TPM to retrieve the name, and instead calculates it manually. Returns: Returns TPM2B_NAME. Raises: ValueError: Unsupported name digest algorithm. """ return self.nvPublic.get_name()
[docs]class TPM2B_PRIVATE(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_PRIVATE_KEY_RSA(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_PRIVATE_VENDOR_SPECIFIC(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_PUBLIC(TPM_OBJECT):
[docs] @classmethod def from_pem( cls, data: bytes, nameAlg: Union[TPM2_ALG, int] = TPM2_ALG.SHA256, objectAttributes: Union[TPMA_OBJECT, int] = ( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), symmetric: TPMT_SYM_DEF_OBJECT = None, scheme: TPMT_ASYM_SCHEME = None, password: bytes = None, ) -> "TPM2B_PUBLIC": """Decode the public part from standard key encodings. Currently supports PEM, DER and SSH encoded public keys. Args: data (bytes): The encoded public key. nameAlg (TPM2_ALG, int): The name algorithm for the public area, default is TPM2_ALG.SHA256. objectAttributes (TPMA_OBJECT, int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). symmetric (TPMT_SYM_DEF_OBJECT) optional: The symmetric definition to use for the public area, default is None. scheme (TPMT_ASYM_SCHEME) optional: The signing/key exchange scheme to use for the public area, default is None. password (bytes) optional: The password used to decrypt the key, default is None. Returns: Returns a TPMT_PUBLIC instance. Raises: ValueError: If key parameters are not supported. Example: .. code-block:: python ecc_key_pem = open('path/to/myecckey.pem').read().encode() TP2B_PUBLIC.from_pem(ecc_key_pem) """ pa = TPMT_PUBLIC.from_pem( data, nameAlg, objectAttributes, symmetric, scheme, password ) p = cls(publicArea=pa) return p
[docs] def to_pem(self) -> bytes: """Encode the public key as PEM encoded ASN.1. Returns: Returns the PEM encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.to_pem() """ return self.publicArea.to_pem()
[docs] def to_der(self) -> bytes: """Encode the public key as DER encoded ASN.1. Returns: Returns the DER encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.to_der() """ return self.publicArea.to_der()
[docs] def to_ssh(self) -> bytes: """Encode the public key in OpenSSH format Returns: Returns the OpenSSH encoded key as bytes. Raises: ValueError: If key type is not supported. Example: .. code-block:: python with ESAPI() as e: # public parameter is index 1 in the return tuple pub = e.create_primary(None)[1] pub.to_ssh() """ return self.publicArea.to_ssh()
[docs] def get_name(self) -> TPM2B_NAME: """Get the TPM name of the public area. This function requires a populated TPM2B_PUBLIC and will NOT go to the TPM to retrieve the name, and instead calculates it manually. Returns: Returns TPM2B_NAME. Raises: ValueError: Unsupported name digest algorithm. """ return self.publicArea.get_name()
[docs] @classmethod def parse( cls, alg="rsa", objectAttributes=TPMA_OBJECT.DEFAULT_TPM2_TOOLS_CREATE_ATTRS, nameAlg="sha256", authPolicy=None, ) -> "TPM2B_PUBLIC": """Builds a TPM2B_PUBLIC from a tpm2-tools like specifier strings. This builds the TPM2B_PUBLIC structure which can be used in TPM2_Create and TPM2_CreatePrimary commands that map into the tpm2-tools project as tpm2 create and createprimary commandlets. Those commands take options: -G, -n, -L and -a option to specify the object to create. This method converts those options, but does not create the object like tpm2-tools. Args: alg (str): The string specifier for the objects algorithm type, bitsize, symmetric cipher and scheme. This is tpm2-tools option "-G" as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/alg.md#complex-specifiers. objectAttiributes (TPMA_OBJECT, int, str): The objects attributes whihch can either the object attributes themselves or a nice name string value. This is tpm2-tools option "-a as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/obj-attrs.md. nameAlg (TPM2_ALG, int, str): The hashing algorithm for the objects name, either the TPM2_ALG constant, integer value or a friendly name string. This is tpm2-tools option "-n" as described in: https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/alg.md#hashing-algorithms authPolicy (bytes): The policy digest of the object. This is tpm2-tools option "-L". Returns: A populated TPMT_PUBLIC for use. Raises: ValueError: If a string value is not of an expected format. Examples: .. code-block:: python TPM2B_PUBLIC.parse( "ecc:ecdh-sha384", objectAttributes=TPMA_OBJECT.DEFAULT_TPM2_TOOLS_CREATEPRIMARY_ATTRS) TPM2B_PUBLIC.parse( alg="xor:sha512", nameAlg="sha256", authPolicy=b'\xc5\x81sS\xf2\x9bc\x87r\xdf\x01\xd3\xbaowM\x96Q\xaf\x1a\xeeKEO\x82\xfeV\xf3\x13^[\x87') """ return cls(TPMT_PUBLIC.parse(alg, objectAttributes, nameAlg, authPolicy))
[docs]class TPM2B_PUBLIC_KEY_RSA(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPMT_KEYEDHASH_SCHEME(TPM_OBJECT): pass
[docs]class TPM2B_SENSITIVE(TPM_OBJECT):
[docs] @classmethod def from_pem(cls, data: bytes, password: bytes = None) -> "TPM2B_SENSITIVE": """Decode the private part from standard key encodings. Currently supports PEM, DER and SSH encoded private keys. Args: data (bytes): The encoded key as bytes. password (bytes, optional): The password used to decrypt the key, default is None. Returns: Returns an instance of TPM2B_SENSITIVE. Raises: ValueError: If key parameters are not supported. Example: .. code-block:: python rsa_private_key = open('path/to/my/rsaprivatekey.pem').read().encode() TPM2B_SENSITIVE.from_pem(rsa_private_key) """ p = TPMT_SENSITIVE.from_pem(data, password) return cls(sensitiveArea=p)
[docs] @classmethod def keyedhash_from_secret( cls, secret: bytes, nameAlg: Union[TPM2_ALG, int] = TPM2_ALG.SHA256, objectAttributes: Union[TPMA_OBJECT, int] = ( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), scheme: TPMT_KEYEDHASH_SCHEME = None, seed: bytes = None, ) -> Tuple["TPM2B_SENSITIVE", TPM2B_PUBLIC]: """Generate the private and public part for a keyed hash object from a secret. Args: secret (bytes): The HMAC key / data to be sealed. nameAlg (TPM2_ALG, int): The name algorithm for the public part, default is TPM2_ALG.SHA256. objectAttributes (TPMA_OBJECT, int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). scheme (TPMT_KEYEDHASH_SCHEME) optional: The signing/key exchange scheme to use for the public area, default is None. seed (bytes) optional: The obfuscate value, default is a randomized value. Returns: A tuple of TPM2B_SENSITIVE and TPM2B_PUBLIC Raises: ValueError: If key parameters are not supported. Example: .. code-block:: python secret = b"secret key" scheme = TPMT_KEYEDHASH_SCHEME(scheme=TPM2_ALG.HMAC) scheme.details.hmac.hashAlg = TPM2_ALG.SHA256 (sens, pub) = TPM2B_SENSITIVE.keyedhash_from_secret(secret, scheme=scheme) """ sa, pa = TPMT_SENSITIVE.keyedhash_from_secret( secret, nameAlg, objectAttributes, scheme, seed ) priv = TPM2B_SENSITIVE(sensitiveArea=sa) pub = TPM2B_PUBLIC(publicArea=pa) return (priv, pub)
[docs] @classmethod def symcipher_from_secret( cls, secret: bytes, algorithm: Union[TPM2_ALG, int] = TPM2_ALG.AES, mode: Union[TPM2_ALG, int] = TPM2_ALG.CFB, nameAlg: Union[TPM2_ALG, int] = TPM2_ALG.SHA256, objectAttributes: Union[TPMA_OBJECT, int] = ( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), seed: bytes = None, ) -> Tuple["TPM2B_SENSITIVE", TPM2B_PUBLIC]: """Generate the private and public part for a symcipher object from a secret. Args: secret (bytes): the symmetric key. algorithm (TPM2_ALG, int): The symmetric cipher algorithm to use, default is TPM2_ALG.AES. mode (TPM2_ALG. int): The symmetric mode to use, default is TPM2_ALG.CFB. nameAlg (TPM2_ALG, int): The name algorithm for the public part, default is TPM2_ALG.SHA256. objectAttributes (TPMA_OBJECT, int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). seed (bytes) optional: The obfuscate value, default is a randomized value. Returns: A tuple of TPM2B_SENSITIVE and TPM2B_PUBLIC Example: .. code-block:: python secret = b"\xF1" * 32 sens, pub = TPM2B_SENSITIVE.symcipher_from_secret(secret) """ sa, pa = TPMT_SENSITIVE.symcipher_from_secret( secret, algorithm, mode, nameAlg, objectAttributes, seed ) priv = TPM2B_SENSITIVE(sensitiveArea=sa) pub = TPM2B_PUBLIC(publicArea=pa) return (priv, pub)
[docs] def to_pem(self, public: TPMT_PUBLIC, password=None) -> bytes: """Encode the key as PEM encoded ASN.1. Args: public(TPMT_PUBLIC): The corresponding public key. password(bytes): An optional password for encrypting the PEM with. Returns: Returns the PEM encoding as bytes. Raises: ValueError: Unsupported key type. Example: .. code-block:: python rsa_private_key = open('path/to/my/rsaprivatekey.pem').read().encode() priv = TPM2B_SENSITIVE.from_pem(rsa_private_key) pub = TPM2B_PUBLIC.from_pem(rsa_private_key) priv.to_pem(pub.publicArea) """ return self.sensitiveArea.to_pem(public, password)
[docs] def to_der(self, public: TPMT_PUBLIC) -> bytes: """Encode the key as DER encoded ASN.1. public(TPMT_PUBLIC): The corresponding public key. Returns: Returns the DER encoding as bytes. Raises: ValueError: Unsupported key type. Example: .. code-block:: python rsa_private_key = open('path/to/my/rsaprivatekey.pem').read().encode() priv = TPM2B_SENSITIVE.from_pem(rsa_private_key) pub = TPM2B_PUBLIC.from_pem(rsa_private_key) priv.to_der(pub.publicArea) """ return self.sensitiveArea.to_der(public)
[docs] def to_ssh(self, public: TPMT_PUBLIC, password: bytes = None) -> bytes: """Encode the key as OPENSSH PEM format. Args: public(TPMT_PUBLIC): The corresponding public key. password(bytes): An optional password for encrypting the PEM with. Returns: Returns the PEM OPENSSH encoding as bytes. Raises: ValueError: Unsupported key type. Example: .. code-block:: python rsa_private_key = open('path/to/my/rsaprivatekey.pem').read().encode() priv = TPM2B_SENSITIVE.from_pem(rsa_private_key) pub = TPM2B_PUBLIC.from_pem(rsa_private_key) priv.to_ssh(pub.publicArea) """ return self.sensitiveArea.to_ssh(public, password=password)
[docs]class TPM2B_SENSITIVE_CREATE(TPM_OBJECT): pass
[docs]class TPM2B_SENSITIVE_DATA(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_SYM_KEY(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_TEMPLATE(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPML_AC_CAPABILITIES(TPML_OBJECT): pass
[docs]class TPML_ALG(TPML_OBJECT):
[docs] @classmethod def parse(cls, algorithms: str) -> "TPML_ALG": """Convert an comma separated list of algorithm friendly string names to a list of numeric constants. Friendly algorithm names are the constants representing algorithms found in the TPM2_ALG class. The string identifiers are those understood by TPM2_ALG.parse. Args: algorithms(str): A comma separated list of algorithm friendly names. May be a list of one item with no comma. Returns: A populated TPML_ALG Raises: ValueError: Invalid algorithms list. Example: .. code-block:: python TPML_ALG("aes") TPML_ALG("aes,sha256") """ if algorithms is None or len(algorithms) == 0: raise ValueError( f"Expected algorithms to be not None or len > 0, got: {algorithms}" ) alglist = [] for a in algorithms.split(","): a = a.strip() if len(a) > 0: alglist.append(TPM2_ALG.parse(a)) if len(alglist) == 0: raise ValueError(f'No algorithms found in algorithms, got "{algorithms}"') return TPML_ALG(alglist)
[docs]class TPML_ALG_PROPERTY(TPML_OBJECT): pass
[docs]class TPML_CC(TPML_OBJECT): pass
[docs]class TPML_CCA(TPML_OBJECT): pass
[docs]class TPML_DIGEST(TPML_OBJECT): pass
[docs]class TPML_DIGEST_VALUES(TPML_OBJECT): pass
[docs]class TPML_ECC_CURVE(TPML_OBJECT): pass
[docs]class TPML_HANDLE(TPML_OBJECT): pass
[docs]class TPML_INTEL_PTT_PROPERTY(TPML_OBJECT): pass
[docs]class TPML_PCR_SELECTION(TPML_OBJECT):
[docs] @staticmethod def parse(selections: str) -> "TPML_PCR_SELECTION": """Convert a PCR selection string into the TPML_PCR_SELECTION data structure. PCR Bank Selection lists follow the below specification: :: <BANK>:<PCR>[,<PCR>] or <BANK>:all multiple banks may be separated by '+'. For Example "sha1:3,4+sha256:all", will select PCRs 3 and 4 from the SHA1 bank and PCRs 0 to 23 from the SHA256 bank. Args: algorithms(str): A comma separated list of algorithm friendly names. May be a list of one item with no comma. Returns: A populated TPML_PCR_SELECTION Raises: ValueError: Invalid algorithms list. Example: .. code-block:: python TPML_PCR_SELECTION.parse("sha256:1,3,5,7") TPML_PCR_SELECTION.parse("sha1:3,4+sha256:all") """ if selections is None or len(selections) == 0: return TPML_PCR_SELECTION() selectors = selections.split("+") if "+" in selections else [selections] if len(selectors) - 1 != selections.count("+"): raise ValueError( f"Malformed PCR bank selection list (unbalanced +), got: {selections}" ) for x in selectors: if len(x) == 0: raise ValueError( f"Malformed PCR bank selection list (unbalanced +), got: {selections}" ) count = len(selectors) if count > lib.TPM2_NUM_PCR_BANKS: raise ValueError( f"PCR Selection list greater than {lib.TPM2_NUM_PCR_BANKS}, " f"got {len(selectors)}" ) selections = [TPMS_PCR_SELECTION.parse(x) for x in selectors] return TPML_PCR_SELECTION(selections)
[docs]class TPML_TAGGED_PCR_PROPERTY(TPML_OBJECT): pass
[docs]class TPML_TAGGED_TPM_PROPERTY(TPML_OBJECT): pass
[docs]class TPMS_AC_OUTPUT(TPM_OBJECT): pass
[docs]class TPMS_ALGORITHM_DESCRIPTION(TPM_OBJECT): pass
[docs]class TPMS_ALGORITHM_DETAIL_ECC(TPM_OBJECT): pass
[docs]class TPMS_ALG_PROPERTY(TPM_OBJECT): pass
[docs]class TPMS_ASYM_PARMS(TPM_OBJECT): pass
[docs]class TPMU_ATTEST(TPM_OBJECT): pass
[docs]class TPMS_ATTEST(TPM_OBJECT): pass
[docs]class TPMS_AUTH_COMMAND(TPM_OBJECT): pass
[docs]class TPMS_AUTH_RESPONSE(TPM_OBJECT): pass
[docs]class TPMU_CAPABILITIES(TPM_OBJECT): pass
[docs]class TPMS_CAPABILITY_DATA(TPM_OBJECT): pass
[docs]class TPMS_CERTIFY_INFO(TPM_OBJECT): pass
[docs]class TPMS_CLOCK_INFO(TPM_OBJECT): pass
[docs]class TPMS_COMMAND_AUDIT_INFO(TPM_OBJECT): pass
[docs]class TPMS_CONTEXT(TPM_OBJECT):
[docs] @classmethod def from_tools(cls, data: bytes) -> "TPMS_CONTEXT": """Unmarshal a tpm2-tools context blob. Note: Currently only support key object contexts from tpm2-tools. Args: data (bytes): The bytes from a tpm2-tools context file. Returns: Returns a TPMS_CONTEXT instance. """ magic = int.from_bytes(data[0:4], byteorder="big") if magic != 0xBADCC0DE: raise ValueError(f"bad magic, expected 0xBADCC0DE, got 0x{magic:X}") version = int.from_bytes(data[4:8], byteorder="big") if version != 1: raise ValueError(f"bad version, expected 1, got {version}") ctx = cls() ctx.hierarchy = int.from_bytes(data[8:12], byteorder="big") ctx.savedHandle = int.from_bytes(data[12:16], byteorder="big") ctx.sequence = int.from_bytes(data[16:24], byteorder="big") ctx.contextBlob, _ = TPM2B_CONTEXT_DATA.unmarshal(data[24:]) return ctx
[docs]class TPMS_CONTEXT_DATA(TPM_OBJECT): pass
[docs]class TPMS_CREATION_DATA(TPM_OBJECT): pass
[docs]class TPMS_CREATION_INFO(TPM_OBJECT): pass
[docs]class TPMS_ECC_PARMS(TPM_OBJECT): pass
[docs]class TPMS_ECC_POINT(TPM_OBJECT): pass
[docs]class TPMS_EMPTY(TPM_OBJECT): pass
[docs]class TPMS_ID_OBJECT(TPM_OBJECT): pass
[docs]class TPMS_KEYEDHASH_PARMS(TPM_OBJECT): pass
[docs]class TPMS_NV_CERTIFY_INFO(TPM_OBJECT): pass
[docs]class TPMS_NV_PIN_COUNTER_PARAMETERS(TPM_OBJECT): pass
[docs]class TPMS_NV_PUBLIC(TPM_OBJECT):
[docs] def get_name(self) -> TPM2B_NAME: """Get the TPM name of the NV public area. Returns: Returns TPM2B_NAME. """ name = _getname(self) return TPM2B_NAME(name)
[docs]class TPMS_PCR_SELECT(TPM_OBJECT): pass
[docs]class TPMS_PCR_SELECTION(TPM_OBJECT): def __init__(self, pcrs=None, **kwargs): super().__init__(**kwargs) if not pcrs: return if bool(self.hash) != bool(pcrs): raise ValueError("hash and pcrs MUST be specified") self._cdata.sizeofSelect = 3 if pcrs == "all" or (len(pcrs) == 1 and pcrs[0] == "all"): self._cdata.pcrSelect[0] = 0xFF self._cdata.pcrSelect[1] = 0xFF self._cdata.pcrSelect[2] = 0xFF return for pcr in pcrs: if pcr < 0 or pcr > lib.TPM2_PCR_LAST: raise ValueError(f"PCR Index out of range, got {pcr}") self._cdata.pcrSelect[pcr // 8] |= 1 << (pcr % 8)
[docs] @staticmethod def parse(selection: str) -> "TPMS_PCR_SELECTION": """Given a PCR selection string populate a TPMS_PCR_SELECTION structure. A PCR Bank selection lists: :: <BANK>:<PCR>[,<PCR>] or <BANK>:all For Example "sha1:3,4", will select PCRs 3 and 4 from the SHA1 bank. Args: selection(str): A PCR selection string. Returns: A populated TPMS_PCR_SELECTION Raises: ValueError: Invalid PCR specification. Example: .. code-block:: python TPMS_PCR_SELECTION.parse("sha256:1,3,5,7") TPMS_PCR_SELECTION.parse("sha1:all") """ if selection is None or len(selection) == 0: raise ValueError( f'Expected selection to be not None and len > 0, got: "{selection}"' ) hunks = [x.strip() for x in selection.split(":")] if len(hunks) != 2: raise ValueError(f"PCR Selection malformed, got {selection}") try: halg = int(hunks[0], 0) except ValueError: halg = TPM2_ALG.parse(hunks[0]) if hunks[1] != "all": try: pcrs = [int(x.strip(), 0) for x in hunks[1].split(",")] except ValueError: raise ValueError(f"Expected PCR number, got {hunks[1]}") else: pcrs = hunks[1] return TPMS_PCR_SELECTION(hash=halg, pcrs=pcrs)
[docs]class TPMS_QUOTE_INFO(TPM_OBJECT): pass
[docs]class TPMS_RSA_PARMS(TPM_OBJECT): pass
[docs]class TPMS_SCHEME_ECDAA(TPM_OBJECT): pass
[docs]class TPMS_SCHEME_HASH(TPM_OBJECT): pass
[docs]class TPMS_SCHEME_XOR(TPM_OBJECT): pass
[docs]class TPMS_SENSITIVE_CREATE(TPM_OBJECT): pass
[docs]class TPMS_SESSION_AUDIT_INFO(TPM_OBJECT): pass
[docs]class TPMS_SIGNATURE_ECC(TPM_OBJECT): pass
[docs]class TPMS_SIGNATURE_RSA(TPM_OBJECT): pass
[docs]class TPMS_SYMCIPHER_PARMS(TPM_OBJECT): pass
[docs]class TPMS_TAGGED_PCR_SELECT(TPM_OBJECT): pass
[docs]class TPMS_TAGGED_PROPERTY(TPM_OBJECT): pass
[docs]class TPMS_TIME_ATTEST_INFO(TPM_OBJECT): pass
[docs]class TPMS_TIME_INFO(TPM_OBJECT): pass
[docs]class TPMT_ECC_SCHEME(TPM_OBJECT): pass
[docs]class TPMU_ASYM_SCHEME(TPM_OBJECT): pass
[docs]class TPMT_KDF_SCHEME(TPM_OBJECT): pass
[docs]class TPMT_TK_CREATION(TPM_OBJECT): pass
[docs]class TPMT_RSA_SCHEME(TPM_OBJECT): pass
[docs]class TPMU_SYM_KEY_BITS(TPM_OBJECT): pass
[docs]class TPMU_SYM_MODE(TPM_OBJECT): pass
[docs]class TPMT_SYM_DEF(TPM_OBJECT): pass
[docs]class TPM2B_AUTH(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPM2B_NONCE(TPM2B_SIMPLE_OBJECT): pass
[docs]class TPMU_PUBLIC_ID(TPM_OBJECT): pass
[docs]class TPMT_SENSITIVE(TPM_OBJECT):
[docs] @classmethod def from_pem(cls, data, password=None): """Decode the private part from standard key encodings. Currently supports PEM, DER and SSH encoded private keys. Args: data (bytes): The encoded key as bytes. password (bytes, optional): The password used to decrypt the key, default is None. Returns: Returns an instance of TPMT_SENSITIVE. """ p = cls() _private_from_encoding(data, p, password) return p
[docs] @classmethod def keyedhash_from_secret( cls, secret, nameAlg=TPM2_ALG.SHA256, objectAttributes=( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), scheme=None, seed=None, ): """Generate the private and public part for a keyed hash object from a secret. Args: secret (bytes): The HMAC key / data to be sealed. nameAlg (int): The name algorithm for the public part, default is TPM2_ALG.SHA256. objectAttributes (int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). scheme (TPMT_KEYEDHASH_SCHEME, optional): The signing/key exchange scheme to use for the public area, default is None. seed (bytes, optional): The obfuscate value, default is a randomized value. Returns: A tuple of of TPMT_SENSITIVE and TPMT_PUBLIC """ pub = TPMT_PUBLIC( type=TPM2_ALG.KEYEDHASH, nameAlg=nameAlg, objectAttributes=objectAttributes ) if scheme is None: pub.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG.NULL else: pub.parameters.keyedHashDetail.scheme = scheme digsize = _get_digest_size(nameAlg) if seed and len(seed) != digsize: raise ValueError( f"invalid seed size, expected {digsize} but got {len(seed)}" ) elif not seed: seed = secrets.token_bytes(digsize) pub.unique.keyedHash = _calculate_sym_unique(nameAlg, secret, seed) priv = cls(sensitiveType=TPM2_ALG.KEYEDHASH) priv.sensitive.bits = secret priv.seedValue = seed return (priv, pub)
[docs] @classmethod def symcipher_from_secret( cls, secret, algorithm=TPM2_ALG.AES, mode=TPM2_ALG.CFB, nameAlg=TPM2_ALG.SHA256, objectAttributes=( TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH ), seed=None, ): """ Generate the private and public part for a symcipher object from a secret. Args: secret (bytes): the symmetric key. algorithm (int): The symmetric cipher algorithm to use, default is TPM2_ALG.AES. mode (int): The symmetric mode to use, default is TPM2_ALG.CFB. nameAlg (int): The name algorithm for the public part, default is TPM2_ALG.SHA256. objectAttributes (int): The object attributes for the public area, default is (TPMA_OBJECT.DECRYPT | TPMA_OBJECT.SIGN_ENCRYPT | TPMA_OBJECT.USERWITHAUTH). seed (bytes, optional): The obfuscate value, default is a randomized value. Returns: A tuple of TPMT_SENSITIVE and TPMT_PUBLIC """ nbits = len(secret) * 8 if algorithm == TPM2_ALG.SM4 and nbits != 128: raise ValueError(f"invalid key size, expected 128, got {nbits}") elif nbits not in (128, 192, 256): raise ValueError( f"invalid key size, expected 128, 192 or 256 bits, got {nbits}" ) pub = TPMT_PUBLIC( type=TPM2_ALG.SYMCIPHER, nameAlg=nameAlg, objectAttributes=objectAttributes ) pub.parameters.symDetail.sym.keyBits.sym = nbits pub.parameters.symDetail.sym.algorithm = algorithm pub.parameters.symDetail.sym.mode.sym = mode digsize = _get_digest_size(nameAlg) if seed and len(seed) != digsize: raise ValueError( f"invalid seed size, expected {digsize} but got {len(seed)}" ) elif not seed: seed = secrets.token_bytes(digsize) pub.unique.sym = _calculate_sym_unique(nameAlg, secret, seed) priv = cls(sensitiveType=TPM2_ALG.SYMCIPHER) priv.sensitive.bits = secret priv.seedValue = seed return (priv, pub)
def _serialize( self, encoding: str, public: TPMT_PUBLIC, format: str = serialization.PrivateFormat.TraditionalOpenSSL, password: bytes = None, ): k = private_to_key(self, public) enc_alg = ( serialization.NoEncryption() if password is None else serialization.BestAvailableEncryption(password) ) data = k.private_bytes( encoding=encoding, format=format, encryption_algorithm=enc_alg, ) return data
[docs] def to_pem(self, public: TPMT_PUBLIC, password: bytes = None): """Encode the key as PEM encoded ASN.1. public(TPMT_PUBLIC): The corresponding public key. password(bytes): An optional password for encrypting the PEM with. Returns: Returns the PEM encoding as bytes. """ return self._serialize(serialization.Encoding.PEM, public, password=password)
[docs] def to_der(self, public: TPMT_PUBLIC): """Encode the key as DER encoded ASN.1. public(TPMT_PUBLIC): The corresponding public key. Returns: Returns the DER encoding as bytes. """ return self._serialize(serialization.Encoding.DER, public)
[docs] def to_ssh(self, public: TPMT_PUBLIC, password: bytes = None): """Encode the key as SSH format. public(TPMT_PUBLIC): The corresponding public key. password(bytes): An optional password for encrypting the PEM with. Returns: Returns the DER encoding as bytes. """ return self._serialize( serialization.Encoding.PEM, public, format=serialization.PrivateFormat.OpenSSH, password=password, )
[docs]class TPMU_SENSITIVE_COMPOSITE(TPM_OBJECT): pass
[docs]class TPMU_SCHEME_KEYEDHASH(TPM_OBJECT): pass
[docs]class TPMT_RSA_DECRYPT(TPM_OBJECT): pass
[docs]class TPMT_TK_HASHCHECK(TPM_OBJECT): pass
[docs]class TPMT_HA(TPM_OBJECT): def __bytes__(self) -> bytes: """Returns the digest field as bytes. If the hashAlg field is TPM2_ALG.NULL, it returns bytes object of len 0. Return: The digest field as bytes. """ if self.hashAlg == TPM2_ALG.NULL: return b"" ds = _get_digest_size(self.hashAlg) return bytes(self.digest.sha512[0:ds])
[docs]class TPMU_HA(TPM_OBJECT): pass
[docs]class TPMT_SIG_SCHEME(TPM_OBJECT): pass
[docs]class TPMU_SIGNATURE(TPM_OBJECT): pass
[docs]class TPMT_SIGNATURE(TPM_OBJECT):
[docs] def verify_signature(self, key, data): """ Verify a TPM generated signature against a key. Args: key (TPMT_PUBLIC, TPM2B_PUBLIC or bytes): The key to verify against, bytes for HMAC, the public part for asymmetric key. data (bytes): The signed data to verify. Raises: InvalidSignature: when the signature doesn't match the data. """ _verify_signature(self, key, data)
[docs]class TPMU_SIG_SCHEME(TPM_OBJECT): pass
[docs]class TPMT_TK_VERIFIED(TPM_OBJECT): pass
[docs]class TPM2B_TIMEOUT(TPM_OBJECT): pass
[docs]class TPMT_TK_AUTH(TPM_OBJECT): pass
[docs]class TPM2B_OPERAND(TPM2B_SIMPLE_OBJECT): pass