Skip to content

Instantly share code, notes, and snippets.

@ignatk
Last active February 1, 2025 21:38
Show Gist options
  • Save ignatk/9038d139e983ca355136aec7ec2d9bfc to your computer and use it in GitHub Desktop.
Save ignatk/9038d139e983ca355136aec7ec2d9bfc to your computer and use it in GitHub Desktop.
TPM derived keys
#!/usr/bin/python3
# requires gokey, keyutils, openssl, tpm2-tools
# configure with
# echo 'create * tpm2:derived:* * |<path to this script> %t %d %c %u %g' > /etc/request-key.d/derived.conf
import hashlib
import os
import subprocess
import sys
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def find_auth_serial():
result = subprocess.run(['keyctl', 'list', '@s'], capture_output=True)
for line in result.stdout.splitlines():
if b".request_key_auth" in line:
return int(line.split(b":", 1)[0])
eprint("could not find auth key serial")
exit(1)
def find_in_proc_keys(serial):
keys = open("/proc/keys", "r")
lines = keys.readlines()
keys.close()
for line in lines:
if line.startswith(f"{serial:08x}"):
return line
eprint("could not find auth key in /proc.keys")
exit(1)
# 3a132464 I------ 10 perm 1b010000 1000 1000 .request_ key:1cf507cd pid:6201 ci:0
def find_request_pid(serial):
line = find_in_proc_keys(serial)
for part in line.split(" "):
if part.startswith("pid:"):
return int(part.split(":", 1)[1])
eprint("could not find request process pid")
exit(1)
def parse_callout(callout):
parts = callout.split(" ")
if len(parts) < 2:
eprint("invalid key parameters")
exit(1)
if parts[1] not in ["csum", "path"]:
eprint("invalid mixin parameter")
exit(1)
return int(parts[0]), parts[1]
key_type = sys.argv[1]
if key_type not in ["user", "logon", "asymmetric"]:
eprint("requested key type not supported")
exit(1)
key_desc = sys.argv[2]
callout = sys.argv[3]
request_uid = int(sys.argv[4])
key_len, mixin = parse_callout(callout)
# generate enough entropy for RSA key generation
if key_type == "asymmetric":
key_len = 256
auth_serial = find_auth_serial()
request_pid = find_request_pid(auth_serial)
request_path = os.path.realpath(f"/proc/{request_pid}/exe")
unique = hashlib.sha256()
unique.update(f"{key_len} {key_type} {key_desc} {request_uid} {mixin} ".encode())
if mixin == 'path':
unique.update(request_path.encode())
if mixin == 'csum':
result = subprocess.run(['openssl', 'sha256', '--binary', request_path], capture_output=True)
unique.update(result.stdout)
key_ctx = os.memfd_create("keyctx", os.MFD_CLOEXEC)
result = subprocess.run(['tpm2_createprimary', '-G', 'aes256ctr', '-a', 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign', '-u', '-', '-c', '/proc/{}/fd/{}'.format(os.getpid(), key_ctx)], input=unique.digest(), capture_output=True)
if result.returncode != 0:
eprint("could not create primary key")
eprint(result.stderr)
exit(1)
result = subprocess.run(['tpm2_encryptdecrypt', '-c', '/proc/{}/fd/{}'.format(os.getpid(), key_ctx)], input=bytes(key_len), capture_output=True)
if result.returncode != 0:
eprint("could not extract cipherstream")
eprint(result.stderr)
exit(1)
os.close(key_ctx)
# for asymmetric key request generate an RSA 2048 key from the entropy
if key_type == "asymmetric":
# should be fine for "unsafe" flag here as we're passing 256 bit "random" string as a password
result = subprocess.run(['gokey', '-p', result.stdout.hex(), '-r', unique.digest().hex(), '-t', 'rsa2048', '-u'], capture_output=True)
if result.returncode != 0:
eprint("could not generate RSA key")
eprint(result.stderr)
exit(1)
# now convert to PKCS8 format
result = subprocess.run(['openssl', 'pkcs8', '-topk8', '-nocrypt', '-outform', 'DER'], input=result.stdout, capture_output=True)
if result.returncode != 0:
eprint("could not convert RSA key to PKCS8 format")
eprint(result.stderr)
exit(1)
eprint(f"derived {key_len} byte {key_type} key from the TPM and the {mixin} of {request_path}")
sys.stdout.buffer.write(result.stdout)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment