# SPDX-License-Identifier: BSD-2
import warnings
import base64
from ._libtpm2_pytss import lib
from .types import *
from .constants import TPM2_ECC, TPM2_CAP, ESYS_TR
from cryptography.hazmat import asn1
from cryptography.x509 import ObjectIdentifier
from typing import Annotated
_parent_rsa_template = TPMT_PUBLIC(
type=TPM2_ALG.RSA,
nameAlg=TPM2_ALG.SHA256,
objectAttributes=TPMA_OBJECT.USERWITHAUTH
| TPMA_OBJECT.RESTRICTED
| TPMA_OBJECT.DECRYPT
| TPMA_OBJECT.NODA
| TPMA_OBJECT.FIXEDTPM
| TPMA_OBJECT.FIXEDPARENT
| TPMA_OBJECT.SENSITIVEDATAORIGIN,
authPolicy=b"",
parameters=TPMU_PUBLIC_PARMS(
rsaDetail=TPMS_RSA_PARMS(
symmetric=TPMT_SYM_DEF_OBJECT(
algorithm=TPM2_ALG.AES,
keyBits=TPMU_SYM_KEY_BITS(aes=128),
mode=TPMU_SYM_MODE(aes=TPM2_ALG.CFB),
),
scheme=TPMT_RSA_SCHEME(scheme=TPM2_ALG.NULL),
keyBits=2048,
exponent=0,
),
),
)
_parent_ecc_template = TPMT_PUBLIC(
type=TPM2_ALG.ECC,
nameAlg=TPM2_ALG.SHA256,
objectAttributes=TPMA_OBJECT.USERWITHAUTH
| TPMA_OBJECT.RESTRICTED
| TPMA_OBJECT.DECRYPT
| TPMA_OBJECT.NODA
| TPMA_OBJECT.FIXEDTPM
| TPMA_OBJECT.FIXEDPARENT
| TPMA_OBJECT.SENSITIVEDATAORIGIN,
authPolicy=b"",
parameters=TPMU_PUBLIC_PARMS(
eccDetail=TPMS_ECC_PARMS(
symmetric=TPMT_SYM_DEF_OBJECT(
algorithm=TPM2_ALG.AES,
keyBits=TPMU_SYM_KEY_BITS(aes=128),
mode=TPMU_SYM_MODE(aes=TPM2_ALG.CFB),
),
scheme=TPMT_ECC_SCHEME(scheme=TPM2_ALG.NULL),
curveID=TPM2_ECC.NIST_P256,
kdf=TPMT_KDF_SCHEME(scheme=TPM2_ALG.NULL),
),
),
)
_rsa_template = TPMT_PUBLIC(
type=TPM2_ALG.RSA,
nameAlg=TPM2_ALG.SHA256,
objectAttributes=TPMA_OBJECT.USERWITHAUTH
| TPMA_OBJECT.SIGN_ENCRYPT
| TPMA_OBJECT.DECRYPT
| TPMA_OBJECT.NODA
| TPMA_OBJECT.FIXEDTPM
| TPMA_OBJECT.FIXEDPARENT
| TPMA_OBJECT.SENSITIVEDATAORIGIN,
authPolicy=b"",
parameters=TPMU_PUBLIC_PARMS(
rsaDetail=TPMS_RSA_PARMS(
symmetric=TPMT_SYM_DEF_OBJECT(algorithm=TPM2_ALG.NULL),
scheme=TPMT_RSA_SCHEME(scheme=TPM2_ALG.NULL),
),
),
)
_ecc_template = TPMT_PUBLIC(
type=TPM2_ALG.ECC,
nameAlg=TPM2_ALG.SHA256,
objectAttributes=TPMA_OBJECT.USERWITHAUTH
| TPMA_OBJECT.SIGN_ENCRYPT
| TPMA_OBJECT.NODA
| TPMA_OBJECT.FIXEDTPM
| TPMA_OBJECT.FIXEDPARENT
| TPMA_OBJECT.SENSITIVEDATAORIGIN,
authPolicy=b"",
parameters=TPMU_PUBLIC_PARMS(
eccDetail=TPMS_ECC_PARMS(
symmetric=TPMT_SYM_DEF_OBJECT(algorithm=TPM2_ALG.NULL),
scheme=TPMT_ECC_SCHEME(scheme=TPM2_ALG.NULL),
kdf=TPMT_KDF_SCHEME(scheme=TPM2_ALG.NULL),
),
),
)
_loadablekey_oid = ObjectIdentifier("2.23.133.10.1.3")
_pem_begin = b"-----BEGIN TSS2 PRIVATE KEY-----\n"
_pem_end = b"-----END TSS2 PRIVATE KEY-----\n"
def _tssprivkey_pem_unarmor(data: bytes) -> bytes:
begin = data.find(_pem_begin)
if begin == -1:
raise ValueError("beginning of PEM not found")
skip = begin + len(_pem_begin)
end = data.find(_pem_end, skip)
if end == -1:
raise ValueError("end of PEM not found")
pem_data = data[skip:end]
der_data = base64.b64decode(pem_data)
return der_data
def _tssprivkey_pem_armor(data: bytes) -> bytes:
# base64 encode
pem_data = base64.b64encode(data)
# split with max length
split_data = _pem_begin
index = 0
while len(pem_data[index:]):
split_data += pem_data[index : index + 64] + b"\n"
index += 64
split_data += _pem_end
# append beginning, split data, end
return split_data
[docs]
class TSSPrivKey(object):
"""TSSPrivKey is class to create/load keys for/from tpm2-tss-engine / tpm2-openssl.
Note:
Most users should use create_rsa/create_ecc together with to_pem and from_pem together with load.
"""
@asn1.sequence
class _tssprivkey_load:
object_type: ObjectIdentifier = _loadablekey_oid
empty_auth: asn1.TLV
parent: int
public: bytes
private: bytes
@asn1.sequence
class _tssprivkey_save:
object_type: ObjectIdentifier = _loadablekey_oid
empty_auth: Annotated[bool, asn1.Explicit(0)]
parent: int
public: bytes
private: bytes
def __init__(self, private, public, empty_auth=True, parent=lib.TPM2_RH_OWNER):
"""Initialize TSSPrivKey using raw values.
Args:
private (TPM2B_PRIVATE): The private part of the TPM key.
public (TPM2B_PUBLIC): The public part of the TPM key.
empty_auth (bool): Defines if the authorization is a empty password, default is True.
parent (int): The parent of the key, either a persistent key handle or TPM2_RH_OWNER, default is TPM2_RH_OWNER.
"""
self._private = private
self._public = public
self._empty_auth = bool(empty_auth)
self._parent = parent
@property
def private(self):
"""TPM2B_PRIVATE: The private part of the TPM key."""
return self._private
@property
def public(self):
"""TPM2B_PUBLIC: The public part of the TPM key."""
return self._public
@property
def empty_auth(self):
"""bool: Defines if the authorization is a empty password."""
return self._empty_auth
@property
def parent(self):
"""int: Handle of the parent key."""
return self._parent
[docs]
def to_der(self):
"""Encode the TSSPrivKey as DER encoded ASN.1.
Returns:
Returns the DER encoding as bytes.
"""
pub = self.public.marshal()
priv = self.private.marshal()
seq = self._tssprivkey_save(
empty_auth=self.empty_auth,
parent=self.parent,
public=pub,
private=priv,
)
return asn1.encode_der(seq)
[docs]
def to_pem(self):
"""Encode the TSSPrivKey as PEM encoded ASN.1.
Returns:
Returns the PEM encoding as bytes.
"""
der = self.to_der()
return _tssprivkey_pem_armor(der)
@staticmethod
def _getparenttemplate(ectx):
more = True
al = list()
while more:
more, data = ectx.get_capability(TPM2_CAP.ALGS, 0, lib.TPM2_MAX_CAP_ALGS)
algs = data.data.algorithms
for i in range(0, algs.count):
al.append(algs.algProperties[i].alg)
if TPM2_ALG.ECC in al:
return _parent_ecc_template
elif TPM2_ALG.RSA in al:
return _parent_rsa_template
return None
@staticmethod
def _getparent(ectx, keytype, parent):
if parent == lib.TPM2_RH_OWNER:
template = TSSPrivKey._getparenttemplate(ectx)
else:
return ectx.tr_from_tpmpublic(parent)
if template is None:
raise RuntimeError("Unable to find supported parent key type")
inpub = TPM2B_PUBLIC(publicArea=template)
phandle, _, _, _, _ = ectx.create_primary(
primary_handle=ESYS_TR.RH_OWNER,
in_sensitive=TPM2B_SENSITIVE_CREATE(),
in_public=inpub,
outside_info=TPM2B_DATA(),
creation_pcr=TPML_PCR_SELECTION(),
session1=ESYS_TR.PASSWORD,
)
return phandle
[docs]
def load(self, ectx, password=None):
"""Load the TSSPrivKey.
Args:
ectx (ESAPI): The ESAPI instance to use for loading the key.
password (bytes): The password of the TPM key, default is None.
Returns:
An ESYS_TR handle.
"""
if not password and not self.empty_auth:
raise RuntimeError("no password specified but it is required")
elif password and self.empty_auth:
warnings.warn("password specified but empty_auth is true")
phandle = self._getparent(ectx, self.public.publicArea.type, self.parent)
with phandle as phandle:
handle = ectx.load(phandle, self.private, self.public)
ectx.tr_set_auth(handle, password)
return handle
[docs]
@classmethod
def create(cls, ectx, template, parent=lib.TPM2_RH_OWNER, password=None):
"""Create a TssPrivKey using a template.
Note:
Most users should use the create_rsa or create_ecc methods.
Args:
ectx (ESAPI): The ESAPI instance to use for creating the key.
template (TPM2B_PUBLIC): The key template.
parent (int): The parent of the key, default is TPM2_RH_OWNER.
password (bytes): The password to set for the key, default is None.
Returns:
Returns a TSSPrivKey instance with the created key.
"""
insens = TPM2B_SENSITIVE_CREATE()
emptyauth = True
if password:
insens.sensitive.userAuth = password
emptyauth = False
phandle = cls._getparent(ectx, template.type, parent)
with phandle as phandle:
private, public, _, _, _ = ectx.create(
parent_handle=phandle,
in_sensitive=insens,
in_public=TPM2B_PUBLIC(publicArea=template),
outside_info=TPM2B_DATA(),
creation_pcr=TPML_PCR_SELECTION(),
)
return cls(private, public, emptyauth, parent)
[docs]
@classmethod
def create_rsa(
cls, ectx, keyBits=2048, exponent=0, parent=lib.TPM2_RH_OWNER, password=None
):
"""Create a RSA TssPrivKey using a standard RSA key template.
Args:
ectx (ESAPI): The ESAPI instance to use for creating the key.
keyBits (int): Size of the RSA key, default is 2048.
exponent (int): The exponent to use for the RSA key, default is 0 (TPM default).
parent (int): The parent of the key, default is TPM2_RH_OWNER.
password (bytes): The password to set for the key, default is None.
Returns:
Returns a TSSPrivKey instance with the created RSA key.
"""
template = _rsa_template
template.parameters.rsaDetail.keyBits = keyBits
template.parameters.rsaDetail.exponent = exponent
return cls.create(ectx, template, parent, password)
[docs]
@classmethod
def create_ecc(
cls, ectx, curveID=TPM2_ECC.NIST_P256, parent=lib.TPM2_RH_OWNER, password=None
):
"""Create an ECC TssPrivKey using a standard ECC key template.
Args:
ectx (ESAPI): The ESAPI instance to use for creating the key.
curveID (int): The ECC curve to be used, default is TPM2_ECC.NIST_P256.
parent (int): The parent of the key, default is TPM2_RH_OWNER.
password (bytes): The password to set for the key, default is None.
Returns:
Returns a TSSPrivKey instance with the created ECC key.
"""
template = _ecc_template
template.parameters.eccDetail.curveID = curveID
return cls.create(ectx, template, parent, password)
@staticmethod
def _decode_bad_bool(tlv: asn1.TLV) -> bool:
# Some versions of tpm2-tss-engine and tpm2-openssl encode TRUE as 1.
# That is an invalid DER encoding, so handle that here.
data = bytes(tlv.data)
if len(data) != 3:
raise ValueError(
f"unexpected emptyAuth ASN.1 TLV length, expected 3, got {len(data)}"
)
tag, length, value = data
if tag != 1:
raise ValueError(f"unexpected emptyAuth ASN.1 tag, expected 1, got {tag}")
elif length != 1:
raise ValueError(
f"unexpected emptyAuth ASN.1 value length, expected 1, got {length}"
)
return bool(value)
[docs]
@classmethod
def from_der(cls, data):
"""Load a TSSPrivKey from DER ASN.1.
Args:
data (bytes): The DER encoded ASN.1.
Returns:
Returns a TSSPrivKey instance.
"""
seq = asn1.decode_der(cls._tssprivkey_load, data)
if seq.object_type != _loadablekey_oid:
raise TypeError("unsupported key type")
empty_auth = cls._decode_bad_bool(seq.empty_auth)
parent = seq.parent
public, _ = TPM2B_PUBLIC.unmarshal(seq.public)
private, _ = TPM2B_PRIVATE.unmarshal(seq.private)
return cls(private, public, empty_auth, parent)
[docs]
@classmethod
def from_pem(cls, data):
"""Load a TSSPrivKey from PEM ASN.1.
Args:
data (bytes): The PEM encoded ASN.1.
Returns:
Returns a TSSPrivKey instance.
"""
der = _tssprivkey_pem_unarmor(data)
return cls.from_der(der)
pem_type, _, der = pem.unarmor(data)
if pem_type != "TSS2 PRIVATE KEY":
raise TypeError("unsupported PEM type")
return cls.from_der(der)