Last active
February 1, 2025 21:38
-
-
Save ignatk/9038d139e983ca355136aec7ec2d9bfc to your computer and use it in GitHub Desktop.
TPM derived keys
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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