From 853e735241d2a280778ae7f163a4596504b46829 Mon Sep 17 00:00:00 2001 From: sal rashid Date: Mon, 23 Sep 2024 08:55:34 -0400 Subject: [PATCH] add policyauthvalue; session encryption Signed-off-by: sal rashid --- .github/workflows/release.yml | 117 ++++++++++++ README.md | 229 +++++++++++++++-------- cloud_auth_tpm/aws/awscredentials.py | 5 +- cloud_auth_tpm/aws/awshmaccredentials.py | 23 ++- cloud_auth_tpm/azure/azurecredentials.py | 3 +- cloud_auth_tpm/base.py | 52 ++++- cloud_auth_tpm/gcp/gcpcredentials.py | 3 +- cloud_auth_tpm/policy/__init__.py | 1 + cloud_auth_tpm/policy/pcr.py | 20 +- cloud_auth_tpm/policy/pcr_authvalue.py | 69 +++++++ cloud_auth_tpm/policy/policy.py | 4 +- example/main_aws.py | 75 ++++---- example/main_aws_hmac.py | 83 ++++---- example/main_azure.py | 65 +++---- example/main_gcp.py | 62 +++--- util/load.py | 15 +- 16 files changed, 583 insertions(+), 243 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 cloud_auth_tpm/policy/pcr_authvalue.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c0634d1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,117 @@ +name: Publish Python distribution to PyPI and TestPyPI + +on: push + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/cloud-auth-tpm # Replace with your PyPI project name + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python distribution to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/cloud-auth-tpm + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/README.md b/README.md index 14c47ed..37c4365 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ for blob in blob_list: | **`ownerpassword`** | Password for the OWNER hierarchy used by H2 template: (optional; default: ``) | | **`password`** | Password for the Key (userAuth): (optional; default: ``) | | **`policy_impl`** | Concrete implementation class for Policy: (optional; default: ``) | +| **`enc_key_name`** | Hex "name" for the TPM key to use for session encryption: (optional; default: ``) | ##### **GCPCredentials** @@ -225,73 +226,8 @@ For this implementation, each key's parent is defined using the standard [H2 TPM The example in this repo converts the TPM public/private blobs to PEM format using python. As mentioned, you can also directly use [tpm2genkey](https://github.com/salrashid123/tpm2genkey?tab=readme-ov-file#convert-tpm2b_public-tpm2b_private-with-tpmhtpermanent-h2-template----pem) or for simple keys [tpm2tss-genkey](https://github.com/tpm2-software/tpm2-tss-engine/blob/master/man/tpm2tss-genkey.1.md) -##### Password Auth - -If you want to provide a passphrase to use the TPM based key, you can setup userAuth based access. To skip password based auth, omit the `-p $KEY_PASSWORD` option during tpm key generation and `--keyPassword=` option while converting to set no auth on the keyfile - -```bash -export TPM2TOOLS_TCTI="swtpm:port=2321" # for real tpm use device:/dev/tpmrm0 -export KEY_PASSWORD=passwd - -cd /tmp/ -### create H2 template -printf '\x00\x00' > unique.dat -tpm2_createprimary -C o -G ecc -g sha256 \ - -c primary.ctx \ - -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat - - -# import the key, note we're setting the password. -## Each provider has a different way to get this rsa private key; see examples below -tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ - -g sha256 -i rsakey.pem -u rsa.pub -r rsa.prv -p $KEY_PASSWORD - -tpm2_load -C primary.ctx -u rsa.pub -r rsa.prv -c rsa.ctx - -tpm2_readpublic -c rsa.ctx -o rsapub.pem -f PEM -Q -cat rsapub.pem - -## convert pub/priv blobs to PEM (remember to specify the full path --public --private and --out) -cd util/ -python3 load.py --public=rsa.pub \ - --private=rsa.prv --out=rsatpm.pem \ - --keyPassword=$KEY_PASSWORD # --tcti=$TPM2TOOLS_TCTI -``` - -##### PCR Policy - -To create a key with a PCR policy (i.,e a policy which enforces the key can only be used if certain PCR values exist) -```bash -export TPM2TOOLS_TCTI="swtpm:port=2321" # for real tpm use device:/dev/tpmrm0 -export PCR=23 -export HASH=sha256 -export PCRBANK=$HASH:$PCR - -cd /tmp/ -printf '\x00\x00' > unique.dat -tpm2_createprimary -C o -G ecc -g sha256 \ - -c primary.ctx \ - -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat - -## set a pcr policy trial session and import the key -tpm2_startauthsession -S session.dat -tpm2_policypcr -S session.dat -l "$PCRBANK" -L policy.dat -tpm2_flushcontext session.dat - -tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ - -g sha256 -i rsakey.pem -u rsa.pub -r rsa.prv -L policy.dat - -tpm2_load -C primary.ctx -u rsa.pub -r rsa.prv -c .ctx - -tpm2_readpublic -c rsa.ctx -o rsapub.pem -f PEM -Q -cat rsapub.pem - -## convert pub/priv to PEM -cd util/ -python3 load.py --public=rsa.pub \ - --private=rsa.prv --out=rsatpm.pem # --tcti=$TPM2TOOLS_TCTI -``` +For details on how to import an RSA or HMAC key into the TPM see [KeyImport](#keyimport) #### Setup - GCP @@ -326,15 +262,21 @@ cd example/ pip3 install -r requirements-gcp.txt ### Password -python3 main_gcp.py --keyfile=rsatpm.pem \ +python3 main_gcp.py --keyfile=rsa_auth.pem \ --email=$SERVICE_ACCOUNT_EMAIL --project_id=$PROJECT_ID \ --password=$KEY_PASSWORD --tcti=$TPM2TOOLS_TCTI ### PCR export PCR=23 -python3 main_gcp.py --keyfile=rsatpm.pem \ +python3 main_gcp.py --keyfile=rsa_pcr.pem \ --email=$SERVICE_ACCOUNT_EMAIL --project_id=$PROJECT_ID \ --pcr=$PCR --tcti=$TPM2TOOLS_TCTI + +### PCR and password +export PCR=23 +python3 main_gcp.py --keyfile=rsa_pcr_auth.pem \ + --email=$SERVICE_ACCOUNT_EMAIL --project_id=$PROJECT_ID \ + --pcr=$PCR --password=$KEY_PASSWORD --tcti=$TPM2TOOLS_TCTI ``` How it works: @@ -392,7 +334,7 @@ python3 main_aws.py --public_certificate_file=$CERTIFICATE \ --region=$REGION --trust_anchor_arn=$TRUST_ANCHOR_ARN \ --role_arn=$ROLE_ARN \ --profile_arn=$PROFILE_ARN \ - --keyfile=rsatpm.pem \ + --keyfile=rsa_auth.pem \ --password=$KEY_PASSWORD \ --tcti=$TPM2TOOLS_TCTI @@ -402,7 +344,7 @@ python3 main_aws.py --public_certificate_file=$CERTIFICATE \ --region=$REGION --trust_anchor_arn=$TRUST_ANCHOR_ARN \ --role_arn=$ROLE_ARN \ --profile_arn=$PROFILE_ARN \ - --keyfile=rsatpm.pem \ + --keyfile=rsa_pcr.pem \ --pcr=$PCR \ --tcti=$TPM2TOOLS_TCTI ``` @@ -423,6 +365,7 @@ This repo includes an example setup and to use this, you need your `AWS_ACCESS_K export AWS_ACCESS_KEY_ID=recacted export AWS_SECRET_ACCESS_KEY=redacted export TPM2TOOLS_TCTI="swtpm:port=2321" +export KEY_PASSWORD=passwd ## add the AWS4 prefix to the key per the signing protocol export secret="AWS4$AWS_SECRET_ACCESS_KEY" @@ -434,17 +377,17 @@ tpm2_createprimary -C o -G ecc -g sha256 -c primary.ctx -a "fixedtpm|fixedpare # embed the hmac key tpm2_import -C primary.ctx -G hmac -i hmac.key -u hmac.pub -r hmac.prv -p $KEY_PASSWORD - tpm2_load -C primary.ctx -u hmac.pub -r hmac.prv -c hmac.ctx ### now convert the pub/priv to a PEM cd util/ -python3 load.py --public=hmac.pub --private=hmac.prv --out=hmac.pem --keyPassword=$KEY_PASSWORD # --tcti=$TPM2TOOLS_TCTI +python3 load.py --public=hmac.pub --private=hmac.prv --out=hmac.pem --keyPassword=$KEY_PASSWORD --tcti=$TPM2TOOLS_TCTI ``` To use this, you need to specify the key file and the `AWS_ACCESS_KEY_ID` ```bash export REGION="us-east-1" +pip3 install -r requirements-aws.txt ## for getsessiontoken python3 main_aws_hmac.py --get_session_token=False \ @@ -498,14 +441,14 @@ curl -s --oauth2-bearer "$AZURE_TOKEN" -H 'x-ms-version: 2017-11-09' \ pip3 install -r requirements-azure.txt ### Password -python3 main_azure.py --keyfile=rsatpm.pem \ +python3 main_azure.py --keyfile=rsa_auth.pem \ --certificate_path=$CERTIFICATE_PATH \ --client_id=$CLIENT_ID --tenant_id=$TENANT_ID \ --password=$KEY_PASSWORD --tcti=$TPM2TOOLS_TCTI ### PCR export PCR=23 -python3 main_azure.py --keyfile=rsatpm.pem \ +python3 main_azure.py --keyfile=rsa_pcr.pem \ --certificate_path=$CERTIFICATE_PATH \ --client_id=$CLIENT_ID --tenant_id=$TENANT_ID \ --pcr=$PCR --tcti=$TPM2TOOLS_TCTI @@ -513,6 +456,126 @@ python3 main_azure.py --keyfile=rsatpm.pem \ Currently ONLY RSASSA keys are supported (its easy enough to support others, TODO) + +#### KeyImport + +The following details how you can import an RSA PEM key file into a given TPM. + +The first stage loads the PEM and saves it as `(TPM2B_PUBLIC,TPM2B_PRIVATE)` binary structures + +You would then convert it those to TPM PEM formatted files (`-----BEGIN TSS2 PRIVATE KEY-----`) + +First step is to create an "H2 Template" primary key: + +```bash +export TPM2TOOLS_TCTI="swtpm:port=2321" # for real tpm use device:/dev/tpmrm0 +cd /tmp/ +### create H2 template +printf '\x00\x00' > unique.dat +tpm2_createprimary -C o -G ecc -g sha256 \ + -c primary.ctx \ + -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat +``` + +Then depending on what constraints you have on the key: + +##### No Auth + +For a plain key + +```bash +# import the key, note we're setting the password. +## Each provider has a different way to get this rsa private key; see examples below +tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ + -g sha256 -i rsakey.pem -u rsa.pub -r rsa.prv + +tpm2_load -C primary.ctx -u rsa.pub -r rsa.prv -c rsa.ctx + +## convert pub/priv blobs to PEM (remember to specify the full path --public --private and --out) +### alternatives to this is: https://github.com/tpm2-software/tpm2-tss-engine/blob/master/man/tpm2tss-genkey.1.md +cd util/ +python3 load.py --public=rsa.pub \ + --private=rsa.prv --out=rsa_auth.pem --tcti=$TPM2TOOLS_TCTI +``` + +##### Password Auth + +If you want to provide a passphrase to use the TPM based key, you can setup userAuth based access. To skip password based auth, omit the `-p $KEY_PASSWORD` option during tpm key generation and `--keyPassword=` option while converting to set no auth on the keyfile + +```bash +# import the key, note we're setting the password. +## Each provider has a different way to get this rsa private key; see examples below +tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ + -g sha256 -i rsakey.pem -u rsa.pub -r rsa.prv -p $KEY_PASSWORD + +tpm2_load -C primary.ctx -u rsa.pub -r rsa.prv -c rsa.ctx + +## convert pub/priv blobs to PEM (remember to specify the full path --public --private and --out) +### alternatives to this is: https://github.com/tpm2-software/tpm2-tss-engine/blob/master/man/tpm2tss-genkey.1.md +cd util/ +python3 load.py --public=rsa.pub \ + --private=rsa.prv --out=rsa_auth.pem \ + --keyPassword=$KEY_PASSWORD --tcti=$TPM2TOOLS_TCTI +``` + +##### PCR Policy + +To create a key with a PCR policy (i.,e a policy which enforces the key can only be used if certain PCR values exist) + +```bash +export PCR=23 +export HASH=sha256 +export PCRBANK=$HASH:$PCR + +## set a pcr policy trial session and import the key +tpm2_startauthsession -S session.dat +tpm2_pcrread "$PCRBANK" -o pcr23_val.bin +tpm2_policypcr -S session.dat -l "$PCRBANK" -L policy.dat -f pcr23_val.bin +tpm2_flushcontext session.dat + +tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ + -g sha256 -i rsakey.pem -u rsa.pub -r rsa.prv -L policy.dat + +tpm2_load -C primary.ctx -u rsa.pub -r rsa.prv -c .ctx + +tpm2_readpublic -c rsa.ctx -o rsapub.pem -f PEM -Q +cat rsapub.pem + +## convert pub/priv to PEM +cd util/ +python3 load.py --public=rsa.pub \ + --private=rsa.prv --out=rsa_pcr.pem --tcti=$TPM2TOOLS_TCTI +``` + +##### PCR and PolicyAuthValue + +To create a key with a PCR policy and a password, use `tpm2_policyauthvalue` + +```bash +export PCR=23 +export HASH=sha256 +export PCRBANK=$HASH:$PCR +export KEY_PASSWORD=passwd + +tpm2_startauthsession -S session.dat +tpm2_pcrread sha256:23 -o pcr23_val.bin +tpm2_policypcr -S session.dat -l sha256:23 -L policy.dat -f pcr23_val.bin +tpm2_policyauthvalue -S session.dat -L policy.dat +tpm2_flushcontext session.dat +tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l + +tpm2_import -C primary.ctx -G rsa2048:rsassa:null \ + -g sha256 -i rsakey.pem \ + -u rsa.pub -r rsa.prv -L policy.dat -p $KEY_PASSWORD + +tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l + +## convert pub/priv to PEM +cd util/ +python3 load.py --public=/tmp/rsa.pub --private=/tmp/rsa.prv \ + --out=rsa_pcr_auth.pem --tcti=$TPM2TOOLS_TCTI --keyPassword=$KEY_PASSWORD +``` + #### Custom Policy Implementation The built repo contains a helper function which fulfills a `PCR` policy (i.,e certain PCR values must be present to use the key). @@ -639,10 +702,24 @@ As a side note, although this is a private key in PEM format, this is NOT usable #### Session Encryption -TODO: Enable [TPM Bus encryption](https://trustedcomputinggroup.org/wp-content/uploads/TCG_CPU_TPM_Bus_Protection_Guidance_Passive_Attack_Mitigation_8May23-3.pdf). +To Enable [TPM Bus encryption](https://trustedcomputinggroup.org/wp-content/uploads/TCG_CPU_TPM_Bus_Protection_Guidance_Passive_Attack_Mitigation_8May23-3.pdf), you need to pass in the hex formatted 'name' of a trusted key you know thats on the TPM shown [here](https://github.com/salrashid123/tpm2/blob/master/pytss/README.md). + +For example, the following prints the EKRSA Public "name" on the tpm. + +```bash +tpm2_createek -c primary.ctx -G rsa -u ek.pub -Q +tpm2_readpublic -c primary.ctx -o ek.pem -n name.bin -f pem -Q +xxd -p -c 100 name.bin + 000bc947113c66100e860949eaa17bd5aa2a66dac54b55816e459669ef3975bbc91e +``` + +so pass that in as -for this, we'd use a well known key handle like the endorsement key shown [here](https://github.com/salrashid123/tpm2/blob/master/pytss/esapi_session_encryption.py). The EKRSA itself could be verified by comparing the derived 'name' at runtime and comparing it with the hex expected name provided by the user (similar to [this](https://github.com/salrashid123/gcp-adc-tpm?tab=readme-ov-file#encrypted-tpm-sessions) +```bash +--enc_key_name=000bc947113c66100e860949eaa17bd5aa2a66dac54b55816e459669ef3975bbc91e +``` +If you don't provide the name, the EKPub will get read in live and used as-is #### Local Build diff --git a/cloud_auth_tpm/aws/awscredentials.py b/cloud_auth_tpm/aws/awscredentials.py index 9e080d1..fba6588 100644 --- a/cloud_auth_tpm/aws/awscredentials.py +++ b/cloud_auth_tpm/aws/awscredentials.py @@ -31,7 +31,8 @@ def __init__( ownerpassword=None, password=None, policy_impl=None, - + enc_key_name=None, + public_certificate_file=None, region=None, duration_seconds=3600, @@ -45,7 +46,7 @@ def __init__( **kwargs: Any ): BaseCredential.__init__(self, tcti=tcti, keyfile=keyfile, - ownerpassword=ownerpassword, password=password, policy_impl=policy_impl) + ownerpassword=ownerpassword, password=password, policy_impl=policy_impl,enc_key_name=enc_key_name) CredentialProvider.__init__(self) self._public_certificate_file = public_certificate_file diff --git a/cloud_auth_tpm/aws/awshmaccredentials.py b/cloud_auth_tpm/aws/awshmaccredentials.py index 2260985..020aad8 100644 --- a/cloud_auth_tpm/aws/awshmaccredentials.py +++ b/cloud_auth_tpm/aws/awshmaccredentials.py @@ -2,6 +2,7 @@ from cloud_auth_tpm.base import BaseCredential from tpm2_pytss.tsskey import TSSPrivKey +from tpm2_pytss.internal.templates import _ek import json from datetime import datetime @@ -27,6 +28,7 @@ def __init__( ownerpassword=None, password=None, policy_impl=None, + enc_key_name=None, region=None, duration_seconds=3600, @@ -47,6 +49,7 @@ def __init__( self._ownerpassword = ownerpassword self._password = password self._policy_impl = policy_impl + self._enc_key_name = enc_key_name self._region = region self._duration_seconds = duration_seconds @@ -108,14 +111,28 @@ def _getSignatureKey(self, key, dateStamp, regionName, serviceName): if self._password != None: ectx.tr_set_auth(hkeyLoaded, self._password) + nv, tmpl = _ek.EK_RSA2048 + + inSensitive = TPM2B_SENSITIVE_CREATE() + handle, outpub, _, _, _ = ectx.create_primary( + inSensitive, tmpl, ESYS_TR.ENDORSEMENT) + + # n = ectx.tr_get_name(handle) + n = outpub.get_name() + if self._enc_key_name != "": + if bytes(n).hex() != self._enc_key_name: + raise Exception("session encryption key name mismatch: expected {}, got {}".format( + self._enc_key_name, bytes(n).hex())) + if self._policy_impl == None: - thmac = ectx.hmac(hkeyLoaded, dateStamp, TPM2_ALG.SHA256) + thmac = ectx.hmac(hkeyLoaded, dateStamp, TPM2_ALG.SHA256, + session1=ESYS_TR.PASSWORD) else: - sess = self._policy_impl.policy_callback(ectx=ectx) + sess = self._policy_impl.policy_callback(ectx=ectx, handle=handle) thmac = ectx.hmac(hkeyLoaded, dateStamp, TPM2_ALG.SHA256, session1=sess) - ectx.flush_context(sess) + ectx.flush_context(handle) ectx.flush_context(hkeyLoaded) kDate = thmac.__bytes__() ectx.close() diff --git a/cloud_auth_tpm/azure/azurecredentials.py b/cloud_auth_tpm/azure/azurecredentials.py index 2c088e5..74057d1 100644 --- a/cloud_auth_tpm/azure/azurecredentials.py +++ b/cloud_auth_tpm/azure/azurecredentials.py @@ -36,6 +36,7 @@ def __init__( ownerpassword=None, password=None, policy_impl=None, + enc_key_name=None, tenant_id=None, client_id=None, @@ -60,7 +61,7 @@ def __init__( raise Exception("Error : currently only RSA_WITH_SHA256 keys are supported, got {}".format( self._cert.signature_algorithm_oid)) - BaseCredential.__init__(self,tcti=tcti,keyfile=keyfile,ownerpassword=ownerpassword,password=password,policy_impl=policy_impl) + BaseCredential.__init__(self,tcti=tcti,keyfile=keyfile,ownerpassword=ownerpassword,password=password,policy_impl=policy_impl,enc_key_name=enc_key_name) TokenCredential.__init__(self) self._tenant_id = tenant_id diff --git a/cloud_auth_tpm/base.py b/cloud_auth_tpm/base.py index 5cd64c7..874d2b2 100644 --- a/cloud_auth_tpm/base.py +++ b/cloud_auth_tpm/base.py @@ -1,5 +1,6 @@ from tpm2_pytss import * from tpm2_pytss.tsskey import TSSPrivKey +from tpm2_pytss.internal.templates import _ek from cloud_auth_tpm.policy.policy import PolicyEval @@ -50,11 +51,13 @@ def __init__( ownerpassword, password, policy_impl: PolicyEval, + enc_key_name, ): self._tcti = tcti or self.DEFAULT_TCTI self._password = password self._ownerpassword = ownerpassword + self._enc_key_name = enc_key_name f = open(keyfile, "r") self._kb = f.read() @@ -80,25 +83,60 @@ def sign(self, data): rkeyLoaded = ectx.load(primary1, k.private, k.public) ectx.flush_context(primary1) + + if k.empty_auth == False and self._password == '': + raise Exception("key has auth but password not set") - digest = sha256(data) + if self._password != '': + ectx.tr_set_auth(rkeyLoaded, self._password) + + + nv, tmpl = _ek.EK_RSA2048 + ## todo, EK may not have the same ownerpassword... + # inSensitive = TPM2B_SENSITIVE_CREATE( + # TPMS_SENSITIVE_CREATE(userAuth=TPM2B_AUTH(self._ownerpassword))) + inSensitive = TPM2B_SENSITIVE_CREATE() + handle, outpub, _, _, _ = ectx.create_primary( + inSensitive, tmpl, ESYS_TR.ENDORSEMENT) + + # n = ectx.tr_get_name(handle) + n = outpub.get_name() + if self._enc_key_name != "": + if bytes(n).hex() != self._enc_key_name: + raise Exception("session encryption key name mismatch: expected {}, got {}".format( + self._enc_key_name, bytes(n).hex())) + + scheme = TPMT_SIG_SCHEME(scheme=TPM2_ALG.RSASSA) scheme.details.any.hashAlg = TPM2_ALG.SHA256 validation = TPMT_TK_HASHCHECK( tag=TPM2_ST.HASHCHECK, hierarchy=TPM2_RH.OWNER) digest, ticket = ectx.hash(data, TPM2_ALG.SHA256, ESYS_TR.OWNER) - if self._password != None: - ectx.tr_set_auth(rkeyLoaded, self._password) - if self._policy_impl == None: + hsess = ectx.start_auth_session( + tpm_key=handle, + bind=ESYS_TR.NONE, + session_type=TPM2_SE.HMAC, + symmetric=TPMT_SYM_DEF( + algorithm=TPM2_ALG.AES, + keyBits=TPMU_SYM_KEY_BITS(sym=128), + mode=TPMU_SYM_MODE(sym=TPM2_ALG.CFB), + ), + auth_hash=TPM2_ALG.SHA256, + ) + ectx.trsess_set_attributes( + hsess, (TPMA_SESSION.DECRYPT) + ) s = ectx.sign(rkeyLoaded, TPM2B_DIGEST( - digest), scheme, validation) + digest), scheme, validation, session1=hsess) else: - sess = self._policy_impl.policy_callback(ectx=ectx) + sess = self._policy_impl.policy_callback( + ectx=ectx, handle=handle) s = ectx.sign(rkeyLoaded, TPM2B_DIGEST(digest), scheme, validation, session1=sess) - ectx.flush_context(sess) + + ectx.flush_context(handle) ectx.flush_context(rkeyLoaded) ectx.close() diff --git a/cloud_auth_tpm/gcp/gcpcredentials.py b/cloud_auth_tpm/gcp/gcpcredentials.py index 28835ed..b31ed2d 100644 --- a/cloud_auth_tpm/gcp/gcpcredentials.py +++ b/cloud_auth_tpm/gcp/gcpcredentials.py @@ -23,6 +23,7 @@ def __init__( ownerpassword=None, password=None, policy_impl=None, + enc_key_name=None, email=None, scopes="https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email", @@ -31,7 +32,7 @@ def __init__( ): - BaseCredential.__init__(self,tcti=tcti,keyfile=keyfile,ownerpassword=ownerpassword,password=password,policy_impl=policy_impl) + BaseCredential.__init__(self,tcti=tcti,keyfile=keyfile,ownerpassword=ownerpassword,password=password,policy_impl=policy_impl,enc_key_name=enc_key_name) credentials.CredentialsWithQuotaProject.__init__(self) self._email = email diff --git a/cloud_auth_tpm/policy/__init__.py b/cloud_auth_tpm/policy/__init__.py index 45786d7..cb5e36b 100644 --- a/cloud_auth_tpm/policy/__init__.py +++ b/cloud_auth_tpm/policy/__init__.py @@ -2,3 +2,4 @@ from cloud_auth_tpm.policy.policy import PolicyEval from cloud_auth_tpm.policy.pcr import PCRPolicy +from cloud_auth_tpm.policy.pcr_authvalue import PCRAuthValuePolicy diff --git a/cloud_auth_tpm/policy/pcr.py b/cloud_auth_tpm/policy/pcr.py index dfd0350..dd1c54c 100644 --- a/cloud_auth_tpm/policy/pcr.py +++ b/cloud_auth_tpm/policy/pcr.py @@ -1,4 +1,5 @@ from tpm2_pytss import * + from cloud_auth_tpm.policy.policy import PolicyEval @@ -17,7 +18,7 @@ class PCRPolicy(PolicyEval): def __init__( self, policy=None, - debug=False + debug=False, ): super().__init__(policy=policy, debug=debug) @@ -41,14 +42,18 @@ def _pcr_cb(self, selection): out_dig = TPML_DIGEST(digests) return (out_sel, out_dig) - def policy_callback(self, ectx): + def policy_callback(self, ectx,handle): sess = ectx.start_auth_session( - tpm_key=ESYS_TR.NONE, + tpm_key=handle, bind=ESYS_TR.NONE, session_type=TPM2_SE.POLICY, - symmetric=TPMT_SYM_DEF(algorithm=TPM2_ALG.NULL), + symmetric=TPMT_SYM_DEF( + algorithm=TPM2_ALG.AES, + keyBits=TPMU_SYM_KEY_BITS(sym=128), + mode=TPMU_SYM_MODE(sym=TPM2_ALG.CFB), + ), auth_hash=TPM2_ALG.SHA256, - ) + ) polstr = json.dumps(self._policy).encode() with policy(polstr, TPM2_ALG.SHA256) as p: p.set_callback(policy_cb_types.CALC_PCR, self._pcr_cb) @@ -57,5 +62,8 @@ def policy_callback(self, ectx): cjb = p.get_calculated_json() json_object = json.loads(cjb) print(json.dumps(json_object, indent=4)) - p.execute(ectx, sess) + p.execute(ectx, sess) + ectx.trsess_set_attributes( + sess, (TPMA_SESSION.DECRYPT | TPMA_SESSION.ENCRYPT ) + ) return sess diff --git a/cloud_auth_tpm/policy/pcr_authvalue.py b/cloud_auth_tpm/policy/pcr_authvalue.py new file mode 100644 index 0000000..6ec1367 --- /dev/null +++ b/cloud_auth_tpm/policy/pcr_authvalue.py @@ -0,0 +1,69 @@ +from tpm2_pytss import * + +from cloud_auth_tpm.policy.policy import PolicyEval + +class PCRAuthValuePolicy(PolicyEval): + + DEFAULT_POLICY = { + "description": "Default Policy", + "policy": [ + { + "type": "POLICYPCR", + "pcrs": [] + } + ] + } + + def __init__( + self, + policy=None, + debug=False, + ): + + super().__init__(policy=policy, debug=debug) + # self._debug = debug + # self._policy = policy or self.DEFAULT_POLICY + + def _pcr_cb(self, selection): + sel = TPMS_PCR_SELECTION( + hash=TPM2_ALG.SHA256, + sizeofSelect=selection.selections.pcr_select.sizeofSelect, + pcrSelect=selection.selections.pcr_select.pcrSelect, + ) + out_sel = TPML_PCR_SELECTION((sel,)) + digests = list() + selb = bytes(sel.pcrSelect[0: sel.sizeofSelect]) + seli = int.from_bytes(reversed(selb), "big") + for i in range(0, sel.sizeofSelect * 8): + if (1 << i) & seli: + dig = TPM2B_DIGEST(bytes([i]) * 32) + digests.append(dig) + out_dig = TPML_DIGEST(digests) + return (out_sel, out_dig) + + def policy_callback(self, ectx: ESAPI, handle: ESYS_TR): + sess = ectx.start_auth_session( + tpm_key=handle, #ESYS_TR.NONE, + bind=ESYS_TR.NONE, + session_type=TPM2_SE.POLICY, + symmetric=TPMT_SYM_DEF( + algorithm=TPM2_ALG.AES, + keyBits=TPMU_SYM_KEY_BITS(sym=128), + mode=TPMU_SYM_MODE(sym=TPM2_ALG.CFB), + ), + auth_hash=TPM2_ALG.SHA256, + ) + polstr = json.dumps(self._policy).encode() + with policy(polstr, TPM2_ALG.SHA256) as p: + p.set_callback(policy_cb_types.CALC_PCR, self._pcr_cb) + p.calculate() + if self._debug: + cjb = p.get_calculated_json() + json_object = json.loads(cjb) + print(json.dumps(json_object, indent=4)) + p.execute(ectx, sess) + ectx.policy_auth_value(sess) + ectx.trsess_set_attributes( + sess, ( TPMA_SESSION.ENCRYPT | TPMA_SESSION.DECRYPT) + ) + return sess diff --git a/cloud_auth_tpm/policy/policy.py b/cloud_auth_tpm/policy/policy.py index 284c81c..a9b8211 100644 --- a/cloud_auth_tpm/policy/policy.py +++ b/cloud_auth_tpm/policy/policy.py @@ -1,4 +1,4 @@ -from tpm2_pytss import ESAPI +from tpm2_pytss import ESAPI, ESYS_TR from abc import ABCMeta, abstractmethod @@ -10,5 +10,5 @@ def __init__(self, policy: dict[str, any], debug: bool): self._debug = debug @abstractmethod - def policy_callback(self, ectx: ESAPI): + def policy_callback(self, ectx: ESAPI, handle: ESYS_TR): pass diff --git a/example/main_aws.py b/example/main_aws.py index 8ba7410..182d236 100644 --- a/example/main_aws.py +++ b/example/main_aws.py @@ -1,6 +1,6 @@ import boto3 from cloud_auth_tpm.aws.awscredentials import AWSCredentials -from cloud_auth_tpm.policy import PCRPolicy +from cloud_auth_tpm.policy import PCRPolicy, PCRAuthValuePolicy import argparse @@ -10,7 +10,7 @@ parser.add_argument("--ownerpassword", default='') parser.add_argument("--password", default='') parser.add_argument("--pcr", default='') - +parser.add_argument("--enc_key_name", default='') parser.add_argument("--public_certificate_file", default="certs/alice-cert.crt", required=True) @@ -24,51 +24,48 @@ args = parser.parse_args() -if args.pcr == '': - - pc = AWSCredentials(tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=None, +policy_impl = None - public_certificate_file=args.public_certificate_file, - region=args.region, - duration_seconds=1000, - trust_anchor_arn=args.trust_anchor_arn, - session_name="foo", - role_arn=args.role_arn, - profile_arn=args.profile_arn) +# if your pcr value bound to is: +# $ tpm2_pcrread sha256:23 +# sha256: +# 23: 0x0000000000000000000000000000000000000000000000000000000000000000 -else: - pol = { - "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), - "policy": [ - { - "type": "POLICYPCR", - "pcrs": [ +pol = { + "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), + "policy": [ + { + "type": "POLICYPCR", + "pcrs": [ { "pcr": args.pcr, "hashAlg": "TPM2_ALG_SHA256", "digest": "0000000000000000000000000000000000000000000000000000000000000000" } - ] - } - ] - } - pc = AWSCredentials(tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=PCRPolicy(policy=pol), + ] + } + ] +} + +if args.pcr != '' and args.password != '': + policy_impl = PCRAuthValuePolicy(policy=pol) +elif args.pcr != '': + policy_impl = PCRPolicy(policy=pol) + +pc = AWSCredentials(tcti=args.tcti, + keyfile=args.keyfile, + ownerpassword=args.ownerpassword, + password=args.password, + policy_impl=None, + enc_key_name=args.enc_key_name, - public_certificate_file=args.public_certificate_file, - region=args.region, - duration_seconds=1000, - trust_anchor_arn=args.trust_anchor_arn, - session_name="foo", - role_arn=args.role_arn, - profile_arn=args.profile_arn) + public_certificate_file=args.public_certificate_file, + region=args.region, + duration_seconds=1000, + trust_anchor_arn=args.trust_anchor_arn, + session_name="foo", + role_arn=args.role_arn, + profile_arn=args.profile_arn) session = pc.get_session() diff --git a/example/main_aws_hmac.py b/example/main_aws_hmac.py index a8bf787..1ee7ee1 100644 --- a/example/main_aws_hmac.py +++ b/example/main_aws_hmac.py @@ -1,6 +1,6 @@ import boto3 from cloud_auth_tpm.aws.awshmaccredentials import AWSHMACCredentials -from cloud_auth_tpm.policy import PCRPolicy +from cloud_auth_tpm.policy import PCRPolicy, PCRAuthValuePolicy import argparse @@ -10,6 +10,7 @@ parser.add_argument("--ownerpassword", default='') parser.add_argument("--password", default='') parser.add_argument("--pcr", default='') +parser.add_argument("--enc_key_name", default='') parser.add_argument("--aws_access_key_id", default='', required=True) parser.add_argument("--region", default="us-east-1", required=True) @@ -23,55 +24,51 @@ args = parser.parse_args() -if args.pcr == '': +policy_impl = None - pc = AWSHMACCredentials( - tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=None, +# if your pcr value bound to is: +# $ tpm2_pcrread sha256:23 +# sha256: +# 23: 0x0000000000000000000000000000000000000000000000000000000000000000 - access_key=args.aws_access_key_id, - region=args.region, - duration_seconds=3600, - role_session_name=args.role_session_name, - assume_role_arn=args.assume_role_arn, - - get_session_token=args.get_session_token -) - -else: - pol = { - "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), - "policy": [ - { - "type": "POLICYPCR", - "pcrs": [ +pol = { + "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), + "policy": [ + { + "type": "POLICYPCR", + "pcrs": [ { "pcr": args.pcr, "hashAlg": "TPM2_ALG_SHA256", "digest": "0000000000000000000000000000000000000000000000000000000000000000" } - ] - } - ] - } - pc = AWSHMACCredentials( - tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=PCRPolicy(policy=pol), - - access_key=args.aws_access_key_id, - region=args.region, - duration_seconds=3600, - role_session_name=args.role_session_name, - assume_role_arn=args.assume_role_arn, - - get_session_token=args.get_session_token) - + ] + } + ] +} + +if args.pcr != '' and args.password != '': + policy_impl = PCRAuthValuePolicy(policy=pol) +elif args.pcr != '': + policy_impl = PCRPolicy(policy=pol) + + +pc = AWSHMACCredentials( + tcti=args.tcti, + keyfile=args.keyfile, + ownerpassword=args.ownerpassword, + password=args.password, + policy_impl=policy_impl, + enc_key_name=args.enc_key_name, + + access_key=args.aws_access_key_id, + region=args.region, + duration_seconds=3600, + role_session_name=args.role_session_name, + assume_role_arn=args.assume_role_arn, + + get_session_token=args.get_session_token +) session = pc.get_session() diff --git a/example/main_azure.py b/example/main_azure.py index 57f6026..2672029 100644 --- a/example/main_azure.py +++ b/example/main_azure.py @@ -1,6 +1,6 @@ from azure.storage.blob import BlobServiceClient from cloud_auth_tpm.azure.azurecredentials import AzureCredentials -from cloud_auth_tpm.policy import PCRPolicy +from cloud_auth_tpm.policy import PCRPolicy, PCRAuthValuePolicy import argparse @@ -10,6 +10,7 @@ parser.add_argument("--ownerpassword", default='') parser.add_argument("--password", default='') parser.add_argument("--pcr", default='') +parser.add_argument("--enc_key_name", default='') parser.add_argument("--certificate_path", default="certs/azclient.crt", required=True) @@ -23,45 +24,45 @@ args = parser.parse_args() +policy_impl = None -if args.pcr == '': - pc = AzureCredentials( - tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=None, +# if your pcr value bound to is: +# $ tpm2_pcrread sha256:23 +# sha256: +# 23: 0x0000000000000000000000000000000000000000000000000000000000000000 - tenant_id=args.tenant_id, - client_id=args.client_id, - certificate_path=args.certificate_path) -else: - pol = { - "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), - "policy": [ - { - "type": "POLICYPCR", - "pcrs": [ +pol = { + "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), + "policy": [ + { + "type": "POLICYPCR", + "pcrs": [ { "pcr": args.pcr, "hashAlg": "TPM2_ALG_SHA256", "digest": "0000000000000000000000000000000000000000000000000000000000000000" } - ] - } - ] - } - pc = AzureCredentials( - tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=PCRPolicy(policy=pol), + ] + } + ] +} - tenant_id=args.tenant_id, - client_id=args.client_id, - certificate_path=args.certificate_path) - +if args.pcr != '' and args.password != '': + policy_impl = PCRAuthValuePolicy(policy=pol) +elif args.pcr != '': + policy_impl = PCRPolicy(policy=pol) + +pc = AzureCredentials( + tcti=args.tcti, + keyfile=args.keyfile, + ownerpassword=args.ownerpassword, + password=args.password, + policy_impl=policy_impl, + enc_key_name=args.enc_key_name, + + tenant_id=args.tenant_id, + client_id=args.client_id, + certificate_path=args.certificate_path) blob_service_client = BlobServiceClient( account_url="https://{}.blob.core.windows.net".format(args.storageaccount), diff --git a/example/main_gcp.py b/example/main_gcp.py index d774d2e..100b710 100644 --- a/example/main_gcp.py +++ b/example/main_gcp.py @@ -1,7 +1,7 @@ from tpm2_pytss import * from google.cloud import storage from cloud_auth_tpm.gcp.gcpcredentials import GCPCredentials -from cloud_auth_tpm.policy import PCRPolicy +from cloud_auth_tpm.policy import PCRPolicy, PCRAuthValuePolicy import argparse @@ -11,6 +11,7 @@ parser.add_argument("--ownerpassword", default='') parser.add_argument("--password", default='') parser.add_argument("--pcr", default='') +parser.add_argument("--enc_key_name", default='') parser.add_argument( "--email", default='tpm-sa@core-eso.iam.gserviceaccount.com') @@ -18,40 +19,41 @@ args = parser.parse_args() -if args.pcr == '': - pc = GCPCredentials(tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=None, - email=args.email) -else: - ## if your pcr value bound to is: - # $ tpm2_pcrread sha256:23 - # sha256: - # 23: 0x0000000000000000000000000000000000000000000000000000000000000000 - - pol = { - "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), - "policy": [ - { - "type": "POLICYPCR", - "pcrs": [ +policy_impl = None + +# if your pcr value bound to is: +# $ tpm2_pcrread sha256:23 +# sha256: +# 23: 0x0000000000000000000000000000000000000000000000000000000000000000 + +pol = { + "description": "Policy PCR {} TPM2_ALG_SHA256".format(args.pcr), + "policy": [ + { + "type": "POLICYPCR", + "pcrs": [ { "pcr": args.pcr, "hashAlg": "TPM2_ALG_SHA256", "digest": "0000000000000000000000000000000000000000000000000000000000000000" } - ] - } - ] - } - pc = GCPCredentials(tcti=args.tcti, - keyfile=args.keyfile, - ownerpassword=args.ownerpassword, - password=args.password, - policy_impl=PCRPolicy(policy=pol), - email=args.email) + ] + } + ] +} + +if args.pcr != '' and args.password != '': + policy_impl = PCRAuthValuePolicy(policy=pol) +elif args.pcr != '': + policy_impl = PCRPolicy(policy=pol) + +pc = GCPCredentials(tcti=args.tcti, + keyfile=args.keyfile, + ownerpassword=args.ownerpassword, + password=args.password, + policy_impl=policy_impl, + enc_key_name=args.enc_key_name, + email=args.email) storage_client = storage.Client(project=args.project_id, credentials=pc) diff --git a/util/load.py b/util/load.py index 4623757..8dd94d4 100644 --- a/util/load.py +++ b/util/load.py @@ -7,7 +7,20 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend -# pip install git+https://github.com/tpm2-software/tpm2-pytss.git +''' +Utility script which converts tpm2 created keys (TPM2B_PUBLIC,TPM2B_PRIVATE) to PEM format + +https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html + +If the origin key has a password/userAuth, enter in anything as -keyPassword + +alternatives is to use +https://github.com/tpm2-software/tpm2-tss-engine/blob/master/man/tpm2tss-genkey.1.md + +or +https://github.com/salrashid123/tpm2genkey?tab=readme-ov-file#convert-pem----tpm2b_public-tpm2b_private + +''' parser = argparse.ArgumentParser( description='convert public/private key to h2 keyfile')