<![CDATA[THSOn Blog]]>https://thson.de/https://thson.de/favicon.pngTHSOn Bloghttps://thson.de/Ghost 6.21Tue, 17 Mar 2026 05:44:49 GMT60<![CDATA[Taking Things Apart and Fixing Them]]>At the start of the week, I noticed that the shower head on my coffee machine wasn't working properly. This was unfortunate for the wasted coffee from the already prepared coffee puck, but I then switched to a V60 filter for the week. Over the weekend, I diagnosed

]]>
https://thson.de/2026/03/15/taking-things-apart-and-fixing-them/69b656cf81501f0001685d0bSun, 15 Mar 2026 19:08:58 GMTAt the start of the week, I noticed that the shower head on my coffee machine wasn't working properly. This was unfortunate for the wasted coffee from the already prepared coffee puck, but I then switched to a V60 filter for the week. Over the weekend, I diagnosed the issue and found that the solenoid valve was clogged. I'm still not sure why this happened, as there were no visible chalk deposits. After removing the boiler and taking the valve assembly apart, I unclogged the valve with a needle and changed the gaskets. The machine was up and running again for the next morning.

Sometimes, talking to non-engineering friends about taking things apart can evoke a sense of fear about breaking things. If, like me, you have been taking PCs apart since childhood, this is not really the case. However, it helps to follow some rules:

  • Don't take things apart if you can't afford to break them (further). This is particularly the case when taking things apart for the first time, find something to practice on first. On the other hand, I definitely took expensive things apart when I knew what I was doing, as this was the only option.
  • RTFM! Or, at the very least, check to see if anyone else has already done that. There will be a screw hidden somewhere.
  • Take pictures of everything and label as much as possible. This includes separating screws, as some vendors like to use just slightly different screws. It saved me countless times.
  • It's fine to work on dangerous things, but don't be stupid! Read the safety instructions, wear protective gear and ensure you have the right workspace. This means for example shutting off the electricity and double-checking with a multi-meter.
  • Make sure you have the right tools, or know how to build them. For me, at least, the last time I broke something was because I didn't have the right tools and tried to do something quick and dirty.
  • Parts can often take more force than you think. However, with plastic parts in particular, make sure you are putting them in the correct way.
  • Lastly, ask people who might know more for help when you get stuck. Sometimes, a second pair of eyes is all it takes to solve the problem.

Yes, you will occasionally break things, but it's a good learning experience for next time you encounter something similar. Speaking of the coffee machine, some of its parts are definitely not original anymore because of that. Don't be afraid to repair and tinker your own things. It's fun!

]]>
<![CDATA[Two Policies to Unlock the Treasury — Having Fun with TPM Policies]]>Imagine that you have access to the castle's treasury, but only once the king and the treasurer have approved your request and confirmed that the castle is secure. This might involve having two locks on the door, with the keys only being given out by trusted personnel once

]]>
https://thson.de/2026/01/03/two-policies-to-unlock-the-treasury-having-fun-with-tpm-policies/6931e5316236320001ceb304Sat, 03 Jan 2026 13:15:19 GMTImagine that you have access to the castle's treasury, but only once the king and the treasurer have approved your request and confirmed that the castle is secure. This might involve having two locks on the door, with the keys only being given out by trusted personnel once the security protocol has been enforced. Now, let's move on from castles to the modern world. Can we solve this problem using a Trusted Platform Module (TPM), which is commonly found in modern hardware?

The short answer is yes, by using Extend Authorization (EA) policies. However, it is not as straightforward as it seems. Before we begin, there is already some excellent material available on TPMs and how policies work. If you are not familiar with TPMs, I recommend look these first:

Now, let's take a deep dive into building this. All the examples are provided in the form of scripts using tpm2-tools and uses the Trusted Platform Module 2.0 Library, Version 184 as the specification. We assume a resource manager is used. To simplify some of the steps, we use the TPM to generate the values required for the policies. This is not necessary, however, and in one step we demonstrate how this can be done manually.

Extended Authorization Curiosities

When looking at the available policy commands, you will find TPM2_PolicyAuthorize, which is almost what we want to do: delegate authorization to an arbitrary policy digest signed by a specific key. Executing policies one after the other generally results in a logical AND, as the policy digest of the earlier policy is included in the next one. Therefore, it seems like we can just model our setup by using it once with a key from Party A and one from Party B, like so:

#Generate keys for Party A and B
openssl genrsa -out a.priv.pem 2048 
openssl rsa -in a.priv.pem -out a.pub.pem -pubout
openssl genrsa -out b.priv.pem 2048 
openssl rsa -in b.priv.pem -out b.pub.pem -pubout
# Get TPM names of the public keys
tpm2_loadexternal -G rsa -C o -u a.pub.pem -c a.ctx -n party_a.key.name -Q
tpm2_loadexternal -G rsa -C o -u b.pub.pem -c b.ctx -n party_b.key.name -Q

# Start a new TPM trail session
tpm2_startauthsession -S session.ctx
# Update policy digest for Party A
tpm2_policyauthorize -S session.ctx -n party_a.key.name -Q
# Update policy digest for Party B and save as authorized.policy
tpm2_policyauthorize -S session.ctx -n party_b.key.name -L authorized.policy -Q
tpm2_flushcontext session.ctx

# Protect key with this policy
tpm2_createprimary -C o -g sha256 -G ecc -c prim.ctx -Q
tpm2_create -C prim.ctx -g sha256 -G ecc -u key.pub -r key.priv -L authorized.policy

However, this does not do what you expect; this only generates a policy where satisfying the signed policy of Party B is sufficient. If you rerun the commands from the trial session without the first policy authorize command, you will notice that the policy digest does not change.

# TPM trail session with A and B
tpm2_startauthsession -S session.ctx
# Update policy digest for Party A
tpm2_policyauthorize -S session.ctx -n party_a.key.name -Q
tpm2_policyauthorize -S session.ctx -n party_b.key.name -L authorized_ab.policy -Q
tpm2_flushcontext session.ctx

# TPM trail session with only B
tpm2_startauthsession -S session.ctx
tpm2_policyauthorize -S session.ctx -n party_b.key.name -L authorized_b.policy -Q
tpm2_flushcontext session.ctx

sha256sum authorized_ab.policy
sha256sum authorized_b.policy

Example code to show that only the last invocation of tpm2_policyauthorize does something

This actually makes sense when we consider how TPM policy authorization works. It unlocks the object if the current policy digest of the session matches the one specified in the object. If we allow for an arbitrary policy where the final digest is compared against a signature, the policy digest must be set to a value dependent only on the signature key and not the previous executed policy. TPM2_PolicyAuthorize does the following (Trusted Platform Module 2.0 Library Part 3: Commands, Section 23.16):

  1. Check whether the current policy digest matches the key specified in the policy via a ticket.
  2. If it succeeds, it resets the policy digest to a zero digest! This ensures that it is independent of the previous policies executed.
  3. Updates the policy digest using the command code and specified key name.

While executing most TPM policies sequentially does lead to a logical AND, for TPM2_PolicyAuthorize (and also TPM2_PolicyAuthorizeNV) this is only the case if it is the first policy in the chain and is not used twice. So we need to find another way of tying TPM2_PolicyAuthorize into our policies.

Idea

Examining the TPM policy commands, we find TPM2_PolicyNV, which only succeeds if a comparison operation on a given NV index is successful. Here, we can leverage the fact that the policy requires read access to the NV and specify an additional authorization as a parameter during policy execution. Our approach is therefore to create an NV index for each party containing their respective key within a TPM2_PolicyAuthorize policy, and then generate the final policy by combining these with a chain of TPM2_PolicyNV commands.

Completing the Puzzle

Now let's use our newly gained knowledge and the right building blocks to create a complete solution.

Signing Keys for Party A and B

Similarly to our example previously, first Party A and B need to create signing keys that are used to sign new the policy digests for TPM2_PolicyAuthorize.

# Generated by party A
openssl genrsa -out a.priv.pem 2048 
openssl rsa -in a.priv.pem -out a.pub.pem -pubout

# Generated by party A
openssl genrsa -out b.priv.pem 2048 
openssl rsa -in b.priv.pem -out b.pub.pem -pubout

In the next step, we will use a.pub.pem and b.pub.pem to obtain the TPM name of each and then include them in the respective policy.

Creating NV indices for A and B protected by Authorization Policies

The policies are TPM-independent, so A and B can generate them themselves and send them to C, where the object that needs to be protected by A and B lives. For convenience, we use a TPM to generate the policy instead of doing it by hand.

First, load the public keys in order to use them.

tpm2_loadexternal -G rsa -C o -u a.pub.pem -c a.key.ctx -n a.key.name
tpm2_loadexternal -G rsa -C o -u b.pub.pem -c b.key.ctx -n b.key.name

Next we create the two policies containing TPM2_PolicyAuthorize.

# Party A
tpm2_startauthsession -S session.ctx
tpm2_policyauthorize -S session.ctx -n a.key.name -L a.authorized.policy -Q
tpm2_flushcontext session.ctx

# Party B
tpm2_startauthsession -S session.ctx
tpm2_policyauthorize -S session.ctx -n b.key.name -L b.authorized.policy -Q
tpm2_flushcontext session.ctx

Now, let's create the NV indices for which we will use the policies to authorize reading. To simplify policy computation once more, we will create them on a single TPM. This works because the name of the indices contains the authorization policy (authPolicy field), but does not contain any information about the TPM they are on, and therefore remains stable between TPMs. The indices just actually need to exist when executing the policy.
Note that we also allow the owner to write to them; this is necessary because we can only use an NV index in a policy if it has been written to. The value is irrelevant, as we only care about the required policy authorization for reading. We could extend the policy to enforce a write once rule, but that goes beyond the scope of this example. We also set the orderly attribute so that the data is only written to actual persistent memory on TPM shutdown.

HANDLE_A="0x1000001"
HANDLE_B="0x1000002"

tpm2_nvdefine -C o -s 1 -a "policyread|ownerwrite|orderly" $HANDLE_A -L a.authorized.policy
tpm2_nvdefine -C o -s 1 -a "policyread|ownerwrite|orderly" $HANDLE_B -L b.authorized.policy

After that, we just write a zero byte to set the written attribute.

echo -n -e '\x0' | tpm2_nvwrite -C o $HANDLE_A -i-
echo -n -e '\x0' | tpm2_nvwrite -C o $HANDLE_B -i-

Create Policy that requires A and B to provide a Policy

If we already have sessions that satisfy the signed policies of parties A and B, we could simply use the TPM to generate the policy digest.

tpm2_startauthsession -S session.ctx
echo -n -e '\x0' | tpm2_policynv -S session.ctx -i- $HANDLE_A bc  -P <SESSION_AUTH_NV_A>
echo -n -e '\x0' | tpm2_policynv -S session.ctx -i- $HANDLE_B bc  -P <SESSION_AUTH_NV_B> -L key.policy
tpm2_flushcontext session.ctx

Example on how to generate it with sessions that satisfy the authorizations

During setup, this is unlikely to be the case. Instead, we use a simple script to derive the digest (using Trusted Platform Module 2.0 Library Part 3: Commands, Section 23.9).

import hashlib
import subprocess
import sys
from pathlib import Path

import yaml

# TPM NV Index Handles
HANDLE_A = "0x1000001"
HANDLE_B = "0x1000002"

# TPM Command Constants (pre-encoded as bytes)
POLICYNV_COMMAND = 0x00000149.to_bytes(4, byteorder="big")  # TPM_CC_PolicyNV
NV_OPERAND_B = 0x0.to_bytes(1, byteorder="big")
NV_OFFSET = 0x0.to_bytes(2, byteorder="big")
NV_OPERATION = 0x000B.to_bytes(2, byteorder="big")  # TPM_EO_BITCLEAR

POLICY_BASE_SIZE = 32  # SHA256 digest size

def get_nv_index_name(handle: str) -> bytes:
    """Retrieve the TPM name of an NV index using tpm2_nvreadpublic."""
    result = subprocess.run(
        ["tpm2_nvreadpublic", handle], capture_output=True, check=True
    )

    nv_data = yaml.safe_load(result.stdout.decode("utf-8"))
    handle_int = int(handle, base=16)
    name_hex = nv_data[handle_int]["name"]

    return bytes.fromhex(name_hex)


def extend_policy_nv(
    policy_hash: hashlib._hashlib.HASH,
    nv_index_name: bytes,
    operand: bytes,
    offset: bytes,
    operation: bytes,
) -> None:
    """Extend a policy digest with a PolicyNV command."""
    # Build argument hash: H(operand || offset || operation)
    arg_hash = hashlib.sha256()
    arg_hash.update(operand)
    arg_hash.update(offset)
    arg_hash.update(operation)

    # Extend policy: H(policy || command || arg_hash || nv_name)
    policy_hash.update(POLICYNV_COMMAND)
    policy_hash.update(arg_hash.digest())
    policy_hash.update(nv_index_name)


def calculate_policy_digest(name_a: bytes, name_b: bytes) -> bytes:
    """Calculate policy digest with two PolicyNV assertions."""
    # Start with base policy: H(0x00 * 32)
    policy_hash = hashlib.sha256(b"\x00" * POLICY_BASE_SIZE)

    # Extend with first NV index
    extend_policy_nv(policy_hash, name_a, NV_OPERAND_B, NV_OFFSET, NV_OPERATION)

    # Rehash and extend for second NV index
    policy_hash = hashlib.sha256(policy_hash.digest())
    extend_policy_nv(policy_hash, name_b, NV_OPERAND_B, NV_OFFSET, NV_OPERATION)

    return policy_hash.digest()


def main() -> bool:
    """Generate TPM policy digest and write to file."""
    try:
        # Retrieve NV index names
        name_a = get_nv_index_name(HANDLE_A)
        name_b = get_nv_index_name(HANDLE_B)

        policy_digest = calculate_policy_digest(name_a, name_b)

        output_path = Path("key.policy")
        output_path.write_bytes(policy_digest)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        exit(1)
    exit(0)


if __name__ == "__main__":
    main()

We finally have a policy called key.policy that we can use to protect any other TPM object. The hard part is done!

A great thing about this policy is that it is both TPM- and object-independent. This means that, as long as the NV indices exist on a TPM, we can reuse this policy. Please note that signed NV policies can be used to authorize access to any object with our final policy, so do this with caution! Unfortunately, due to the level of indirection, we cannot use TPM2_PolicyNameHash to solve this issue.

Creating a protected Key

Now, let's create a key to protect the policy.

# Protect key with this policy
tpm2_createprimary -C o -g sha256 -G ecc -c prim.ctx -Q
tpm2_create -C prim.ctx -g sha256 -G ecc -u key.pub -r key.priv -L key.policy

Example to Satisfy the Policy

To provide an example, let us assume that Party A is concerned with a state measured in PCR0, while Party B is concerned with a state measured in PCR1.

Setup

First, let us use the TPM to generate the digest for the PCR policies.

# Policy for A
tpm2_pcrread -opcr0.sha256 sha256:0 -Q
tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:0 -f pcr0.sha256 -L a.policy_desired -Q
tpm2_flushcontext session.ctx

# Policy for B
tpm2_pcrread -opcr1.sha256 sha256:1 -Q
tpm2_startauthsession -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:1 -f pcr1.sha256 -L b.policy_desired -Q
tpm2_flushcontext session.ctx

The next step is using the respective private keys to sign the digests.

# Create signatures
openssl dgst -sha256 -sign a.priv.pem -out a.signature a.policy_desired
openssl dgst -sha256 -sign b.priv.pem -out b.signature b.policy_desired

Now we can use a.signature and b.signature with a.policy_desired and b.policy_desired to generate validation tickets to use in the policy later on.

Usage

In order to use our protected key, we first need to create two sessions for which we use as authorization for our NV indices.

# Generate tickets for signatures
tpm2_loadexternal -G rsa -C o -u a.pub.pem -c a.key.ctx -n a.key.name -Q
tpm2_verifysignature -c a.key.ctx -g sha256 -m  a.policy_desired -s a.signature -t a.verification.tkt -f rsassa
tpm2_loadexternal -G rsa -C o -u b.pub.pem -c b.key.ctx -n b.key.name -Q
tpm2_verifysignature -c b.key.ctx -g sha256 -m  b.policy_desired -s b.signature -t b.verification.tkt -f rsassa

# Create session to auth A
tpm2_startauthsession --policy-session -S a.session.ctx 
tpm2_policypcr -S a.session.ctx -l sha256:0 -L a.policy_read -Q
tpm2_policyauthorize -S a.session.ctx -i a.policy_desired -n a.key.name -t a.verification.tkt -Q

# Create session to auth B
tpm2_startauthsession --policy-session -S b.session.ctx
tpm2_policypcr -S b.session.ctx -l sha256:1 -L b.policy_read -Q
tpm2_policyauthorize -S b.session.ctx -i b.policy_desired -n b.key.name -t b.verification.tkt -Q

Next, we create a session to authorize the key.

tpm2_startauthsession --policy-session -S session.ctx
echo -n -e '\x0' | tpm2_policynv -S session.ctx -i- $HANDLE_A bc  -P session:a.session.ctx -Q
echo -n -e '\x0' | tpm2_policynv -S session.ctx -i- $HANDLE_B bc  -P session:b.session.ctx

Finally, let's load the key and sign the message.

# Recreate the primary key
tpm2_createprimary -C o -g sha256 -G ecc -c prim.ctx -Q
# Load the key into the TPM
tpm2_load -C prim.ctx -u key.pub -r key.priv -c key.ctx -n key.name -Q

# Sign a mesaage with the key!
echo "important message" > message.dat
tpm2_sign -c key.ctx -g sha256 -o message.sig message.dat -p session:session.ctx

# Cleanup sessions
tpm2_flushcontext session.ctx
tpm2_flushcontext a.session.ctx
tpm2_flushcontext b.session.ctx

Done! We were successfully granted access to our key by satisfying the policies of two different entities.

(Optional) Attest that the Object has the Correct Policy

Now, both A and B may wish to confirm that an object has this policy. To achieve this, a few more things need to be done:

  • Have an Attestation Key (AK)
  • Use TPM2_Certify on the object (i.e the protected key) signed key
  • Verify the signature over the object name
  • Check that the object name includes the correct public key and our policy.

We could even go the extra mile and compute the policy digest fully without using a TPM, but for simplicity we treat the above key.policy as a golden value.

In the first step, let us create an AK. Again, for simplicity's sake, we are not actually carrying out secure enrollment of the AK by checking its properties and linking its presence to the Endorsement Key (EK). Refer to the tpm.dev tutorial or Keylime's design on how to do this securely.

tpm2_createek -c ek.ctx -G ecc -u ek.pub
tpm2_createak -C ek.ctx -c ak.ctx -u ak.pub

Now we can generate a TPMS_ATTEST for the key we generated using TPM2_Certify and also immediately verify the signature.

# Generate TPMS_ATTEST structure which is signed by the AK
tpm2_certify -Q -c key.ctx -C ak.ctx -g sha256 -o key.attest -s key.sig
# Verify the siganture 
tpm2_verifysignature -c ak.ctx -m key.attest -s key.sig

The only thing left to do is to check that the key in the TPMS_ATTEST structure is our key with the correct policy. This involves parsing a fair amount of data structures, but is otherwise relatively straightforward. First, we check the TPM2B_PUBLIC structure of our key to see if it includes our policy. Next, we calculate the name of the key and compare it with the name in the TPMS_ATTEST structure. The script below also checks some additional fields and verifies that the nonce is the default one.

import struct
import hashlib
import sys
from pathlib import Path

TPM_GENERATED_VALUE = 0xFF544347
TPM_ST_ATTEST_CERTIFY = 0x8017
TPM2_ALG_SHA256 = 0x000B
TPM2_ALG_ECC = 0x0023
NONCE = b"\x00\xff\x55\xaa"  # Default nonce of tpm2_certify


# TPMS_ATTEST structure
TPMS_ATTEST_MAGIC_SIZE = 4
TPMS_ATTEST_TYPE_SIZE = 2
TPMS_ATTEST_QUALIFIED_SIGNER_SIZE = 34
TPMS_ATTEST_EXTRADATA_MAX_SIZE = 4
TPMS_ATTEST_CLOCK_INFO_SIZE = 17  # 8 (clock) + 4 (reset) + 4 (restart) + 1 (safe)
TPMS_ATTEST_FIRMWARE_VERSION_SIZE = 8
TPMS_ATTEST_NAME_SIZE = 34

# TPM2B_PUBLIC/TPMT_PUBLIC 
TPM2B_PUBLIC_SIZE_FIELD = 2
TPMT_PUBLIC_TYPE_SIZE = 2
TPMT_PUBLIC_NAME_ALG_SIZE = 2
TPMT_PUBLIC_OBJECT_ATTR_SIZE = 4

# Digest size
TPM2_SHA256_DIGEST_SIZE = 32


def extract_name(attest_data: bytes) -> bytes:
    """Extract the certified key name from a TPMS_ATTEST structure.

    Parses a TPMS_ATTEST structure and extracts the name field, which contains
    the TPM name of the key that was certified. Also validates the structure's
    magic constant, attestation type, and nonce.
    """
    # Parse magic and attestation type
    offset = 0
    (magic, attest_type) = struct.unpack_from(">IH", attest_data, offset)

    if magic != TPM_GENERATED_VALUE:
        raise ValueError(
            f"Invalid magic constant: expected 0x{TPM_GENERATED_VALUE:08X}, "
            f"got 0x{magic:08X}"
        )

    if attest_type != TPM_ST_ATTEST_CERTIFY:
        raise ValueError(
            f"Invalid attestation type: expected 0x{TPM_ST_ATTEST_CERTIFY:04X}, "
            f"got 0x{attest_type:04X}"
        )

    # Parse qualified signer (not used for validation)
    offset += TPMS_ATTEST_MAGIC_SIZE + TPMS_ATTEST_TYPE_SIZE
    (_qualified_signer_size, _qualified_signer) = struct.unpack_from(
        f">H{TPMS_ATTEST_QUALIFIED_SIGNER_SIZE}s", attest_data, offset
    )
    # Could verify this matches the AK, but not required for basic validation
    offset += 2 + TPMS_ATTEST_QUALIFIED_SIGNER_SIZE

    # Parse and validate extra data (nonce)
    (extradata_size, extradata) = struct.unpack_from(
        f">H{TPMS_ATTEST_EXTRADATA_MAX_SIZE}s", attest_data, offset
    )
    if extradata_size != len(NONCE) or extradata[:extradata_size] != NONCE:
        raise ValueError(
            f"Nonce mismatch: expected {NONCE.hex()}, "
            f"got {extradata[:extradata_size].hex()}"
        )
    offset += 2 + TPMS_ATTEST_EXTRADATA_MAX_SIZE

    # Skip clock info
    offset += TPMS_ATTEST_CLOCK_INFO_SIZE

    # Skip firmware version
    offset += TPMS_ATTEST_FIRMWARE_VERSION_SIZE

    # Extract certified key name
    (certified_name_size, certified_name) = struct.unpack_from(
        f">H{TPMS_ATTEST_NAME_SIZE}s", attest_data, offset
    )
    return certified_name[:certified_name_size]


def calculate_name(key: bytes) -> bytes:
    """Calculate the TPM name of a public key.

    The TPM name is calculated by hashing the TPMT_PUBLIC structure (the inner
    structure within TPM2B_PUBLIC, excluding the 2-byte size prefix) and
    prepending the hash algorithm identifier.

    For SHA256, the name is: 0x000B (TPM2_ALG_SHA256) || SHA256(TPMT_PUBLIC)
    """
    digest = hashlib.sha256(key[TPM2B_PUBLIC_SIZE_FIELD:]).digest()
    return struct.pack(f">H{TPM2_SHA256_DIGEST_SIZE}s", TPM2_ALG_SHA256, digest)


def validate_key(key: bytes, key_policy: bytes) -> bool:
    """Validate key properties and policy.

    Checks that the key uses:
    - ECC algorithm for the key type
    - SHA256 for the naming algorithm
    - The expected policy digest
    """
    # Parse key algorithm type
    offset = TPM2B_PUBLIC_SIZE_FIELD
    (key_type,) = struct.unpack_from(">H", key, offset)

    if key_type != TPM2_ALG_ECC:
        raise ValueError(
            f"Expected ECC key (0x{TPM2_ALG_ECC:04X}), got 0x{key_type:04X}"
        )

    # Parse naming algorithm
    offset += TPMT_PUBLIC_TYPE_SIZE
    (name_alg,) = struct.unpack_from(">H", key, offset)

    if name_alg != TPM2_ALG_SHA256:
        raise ValueError(
            f"Expected SHA256 naming algorithm (0x{TPM2_ALG_SHA256:04X}), "
            f"got 0x{name_alg:04X}"
        )

    # Parse and validate policy digest (skip object attributes)
    offset += TPMT_PUBLIC_NAME_ALG_SIZE + TPMT_PUBLIC_OBJECT_ATTR_SIZE
    (policy_size, policy_digest) = struct.unpack_from(
        f">H{TPM2_SHA256_DIGEST_SIZE}s", key, offset
    )

    if policy_digest[:policy_size] != key_policy:
        raise ValueError(
            f"Policy digest mismatch: expected {key_policy.hex()}, "
            f"got {policy_digest[:policy_size].hex()}"
        )

    return True


def main():
    try:
        key = Path("key.pub").read_bytes()
        attest_data = Path("key.attest").read_bytes()
        key_policy = Path("key.policy").read_bytes()

        validate_key(key, key_policy)

        expected_name = calculate_name(key)
        found_name = extract_name(attest_data)
        if expected_name != found_name:
            raise ValueError(
                f"Name mismatch: expected {expected_name.hex()}, "
                f"found {found_name.hex()}"
            )

        print("Attestation successful")
        exit(0)

    except FileNotFoundError as e:
        print(f"Error: Required file not found: {e.filename}", file=sys.stderr)
        exit(1)
    except ValueError as e:
        print(f"Validation failed: {e}", file=sys.stderr)
        exit(1)
    except Exception as e:
        print(f"Unexpected error: {e}", file=sys.stderr)
        exit(1)

if __name__ == "__main__":
    main()

Done! We can now check remotely whether a key uses our policy.

Alternatives

You might wonder why TPM2_PolicySecret cannot be used to implement this, as it allows to delegate the authentication to another objects authentication. Unfortunately it just ties to the authValue (i.e. password) and we cannot tie it to the authPolicy of an object. It would have been a great addition, but looking at the original use case of changing the authValue of group keys, it makes sense, because authPolicy is part of the name, while the authValue is not. This means the only time this would be useful was if the policy was pointing to an authorization policy. If one only cares about signatures and not additional policies, TPM_PolicySigned is definitely also an option. Just limit the ticket in time or to the session.

I hope you enjoyed exploring the intricacies of TPM EA policies with me and combining building blocks in unusual ways to solve a problem.

]]>
<![CDATA[Introduction to Terminology around Attestation in Trusted and Confidential Computing]]>In my master's thesis, I compiled a glossary of terms related to attestation, with a focus on Trusted and Confidential Computing. Although much of the literature and projects predate the development of a common terminology, we now have definitions for most concepts, allowing us to discuss them using

]]>
https://thson.de/2025/11/23/introduction-to-terminology-around-attestation-in-trusted-and-confidential-computing/69119f44c6f9eb0001ce8928Sun, 23 Nov 2025 14:48:44 GMTIn my master's thesis, I compiled a glossary of terms related to attestation, with a focus on Trusted and Confidential Computing. Although much of the literature and projects predate the development of a common terminology, we now have definitions for most concepts, allowing us to discuss them using precise terminology. This article adapts my original section to provide an overview of the key concepts, primarily based on RFC 9334[1]: Remote Attestation ProcedureS (RATS) Architecture, for which also exists a useful cheat sheet.

Root-of-Trust (RoT)

The TCG defines a RoT component as:

a component that performs one or more security-specific functions, such as measurement, storage, reporting, verification, and/or update. It is trusted always to behave in the expected manner, because its misbehavior cannot be detected (such as by measurement) under normal operation. [2]

This can range from a hardware module such as Trusted Platform Module (TPM)[3] to a certificate issued by a Public Key Infrastructure (PKI).

Attestation – Attester, Attestable, Claims and Evidence

Attestation describes the entire process of establishing a trust relationship between two or more parties by using Evidence. We mainly follow the roles and artifacts defined in RFC 9334 Section 4[1:1] and the extensions proposed by Sardar et al.[4]. The figure below illustrates how the different terms are connected to each other.

Connections between the different defintions

Attester

RATS defines Attester as:

A role performed by an entity (typically a device) whose Evidence must
be appraised in order to infer the extent to which the Attester is considered trustworthy, such as when deciding whether it is authorized to perform some operation.[1:2] (Section 4.1)

We extend the definition by also including requiring that an Attester must be able to produce Evidence about an identity, which is unique, is protected against outsiders, can be used to prove their identity to third-parties, and can be used from third-parties to seal data for the Attester. The issue of identity was also considered in newer drafts to related RFCs, for example in RATS Endorsements[5] Section 4.

Claims and Evidence

RATS defines a Claim as:

A piece of asserted information, often in the form of a name/value pair. Claims make up the usual structure of Evidence and other RATS conceptual messages.[1:3] (Section 4.2)

and Evidence as:

A set of Claims generated by an Attester to be appraised by a Verifier. Evidence may include configuration data, measurements, telemetry, or inferences.[1:4] (Section 4.2)

We argue that Evidence also needs to include a mechanism to verify the authenticity the Evidence, specifically a way to validate that the Evidence was generated by the Attester (e.g. signature). Authenticity does not tell anything about the state of the Attester. For example, the Claims could show that the Attester was compromised, but the signature of the Evidence is still valid.

Local and Remote Attestation

We differentiate between Local and Remote Attestation,
in the same way Sardar, Fossati, and Frost do[4:1].

  • Local: In this case the Attestation is done by the machine itself. This can be a check of a signature before a container is loaded into the runtime.
  • Remote: In this case the Attestation is done by another party outside the machine. For example, this applies to a Verifier requesting Attestation from a different machine.

When talking about Attestation in the context of interaction between systems, we generally refer to Remote Attestation.

Verifier and Relaying Party

Let use use the same definitions as In RATS for the Verifier which is defined as

A role performed by an entity that appraises the validity of Evidence about an Attester and produces Attestation Results to be used by a Relying Party.[1:5] (Section 4.1)

and Relaying Party as

A role performed by an entity that depends on the validity of information about an Attester for purposes of reliably applying application-specific actions. [1:6] (Section 4.1)

Depending on the implementation, the Verifier and the Relaying Party are the same component. Which is often the case in real world implementations[6]. in This is described by Sardar, Fossati, and Frost as the Verifying Relying Party model[4:2]. Later we see how the Verifier can produce data, so that the Relaying Party can form a trust relationship with the Attester.

Authenticity, Trustworthiness, and Trust

With the Evidence we generally can easily validate its authenticity. This does not necessarily imply that the Evidence can be trusted.

Authenticity in the context of Evidence means that the attesting party is able to validate that the claims are actually coming from the Attester and were not manipulated. In many instances this also includes validating the identity of the Attester. For trust, we use the definition given by Bursell[7] based on work from Gambetta[8], where the latter was the first big look at trust trust in socicial sciences:

Trust is the assurance that one entity holds that another will perform
particular actions according to a specific expectation. […]

  • First Corollary: Trust is always contextual
  • Second Corollary: One of the contexts for trust is always time
  • Third Corollary: Trust relationships are not symmetrical

[7:1] (p. 5)

A simple example even without technology for the first and third corollary is the relationship between you and your bank. You would only trust them with your money, not with health matters. Furthermore, just because you trust the bank with your money does not mean that they trust you with theirs. For the second one just because something was true a decade ago, does not necessarly mean it applies today.

Sometimes, trustworthiness and trust are used interchangeably, but there is a distinct difference. We argue that trustworthiness is the information, properties and context of a component that are used to conclude that the component is trustworthy. Which is similar to the ideas proposed by O’Doherty[9] and Cheshire[10] in the field of ontology. We can take the authenticity of the Evidence as an example for what contributes to the trustworthiness, but does not make the component trustworthy.

Trust in comparison is the decision and relationship that is established between the Attester and the other party based on the Evidence. There can be different levels of trust and therefore multiple relationships between the same two components.

As Bursell stated, trustworthiness cannot be a global property of the Attester itself. If that were the case the trust relationship from two different parties to the same Attester would be the same, which is not always the case.

Integrity

For Integrity, we follow definition from United States Code[11] which is also used by FIPS 200:

integrity, which means guarding against improper information modifica
tion or destruction, and includes ensuring information nonrepudiation and authenticity.

Trusted Computing

Combining trust, trustworthiness, authenticity, and integrity we arrive at Trusted Computing. It describes the concept that an entity has some trust relationship with a computer system (or a part of it) that then behaves in an expected way[12]. How this relationship is formed, i.e via Remote Attestation and which parts of the system it covers depends on the entity, system, and environment. The TCG produces a set of standards to build systems that can leverage Trusted Computing.

Confidential Computing and Confidentiality

For the definition of Confidential Computing, we follow definition published by the Confidential Computing Consortium (CCC):

The Confidential Computing Consortium has defined Confidential Com-
puting as "the protection of data in use by performing computation in a
hardware-based, attested Trusted Execution Environment", and identified three primary attributes for what constitutes a Trusted Execution Environment: data integrity, data confidentiality, and code integrity. As described in "Confidential Computing: Hardware-Based Trusted Execution for Applications and Data", four additional attributes may be present (code confidentiality, programmability, recoverability, and attestability) but only attestability is strictly necessary for a computational environment to be classified as Confidential Computing.

We notice Confidential Computing includes the notion of integrity, but integrity is independent of confidentiality. Integrity is not required to establish trust into a component or to verify confidentiality, but it does protect the data against actors on the same system.


  1. Henk Birkholz et al. Remote ATtestation procedureS (RATS) Architecture. Request for Comments RFC 9334. Internet Engineering Task Force, Jan. 2023: https://datatracker.ietf.org/doc/rfc9334 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Trusted Computing Group. TCG Glossary. May 11, 2017. url: https://trustedcomputinggroup.org/wp-content/uploads/TCG-Glossary-V1.1-Rev-1.0.pdf ↩︎

  3. Trusted Computing Group. Trusted Platform Module Library Family“2.0”Level 00 Revision 01.59. Trusted Computing Group, Nov. 8, 2019. url: https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part1_Architecture_pub.pdf ↩︎

  4. Muhammad Sardar, Thomas Fossati, and Simon Frost. SoK: Attestation in Confidential Computing. Jan. 20, 2023. preprint. https://www.researchgate.net/publication/367284929_SoK_Attestation_in_Confidential_Computing ↩︎ ↩︎ ↩︎

  5. Dave Thaler, Henk Birkholz, and Thomas Fossati. RATS Endorsements. Internet
    Draft draft-ietf-rats-endorsements-07. Internet Engineering Task Force, Aug. 25, 2025. https://datatracker.ietf.org/doc/draft-ietf-rats-endorsements/ ↩︎

  6. Harsh Vardhan Mahawar. Harmonizing Open-Source Remote Attestation: My LFX Mentorship Journey. Confidential Computing Consortium Blog, Jul. 29, 2025. https://confidentialcomputing.io/2025/07/29/harmonizing-open-source-remote-attestation-my-lfx-mentorship-journey/ ↩︎

  7. Mike Bursell. Trust in Computer Systems and the Cloud. John Wiley & Sons, Oct. 25, 2021. isbn: 978-1-119-69231-7. ↩︎ ↩︎

  8. Diego Gambetta. “Can We Trust Trust?” In: Trust: Making and Breaking Cooperative Relations. Basil Blackwell, 1988, pp. 213–238. isbn: 0-631-15506-6. ↩︎

  9. Kieran C. O’Doherty. “Trust, Trustworthiness, and Relationships: Ontological Reflections on Public Trust in Science”. In: Journal of Responsible Innovation 10.1 (Jan. 2, 2023), p. 2091311. issn: 2329-9460. doi: 10.1080/23299460.2022.2091311. ↩︎

  10. Coye Cheshire. “Online Trust, Trustworthiness, or Assurance?” In: Daedalus 140.4 (Oct. 2011), pp. 49–58. issn: 0011-5266, 1548-6192. doi: 10.1162/DAED_a_00114. ↩︎

  11. United States Code, 2006 Edition, Supplement 2, Title 44 - PUBLIC PRINTING AND DOCUMENTS. Jan. 5, 2009. ↩︎

  12. Chris Mitchell and Institution of Electrical Engineers, eds. Trusted Computing. IEE Professional Applications of Computing Series 6. London: Institution of Electrical Engineers, 2005. 313 pp. isbn: 978-0-86341-525-8. ↩︎

]]>
<![CDATA[Brutalisten, Stockholm]]>I was surprised to hear that one of my favourite restaurants in Stockholm is closing at the end of 2025, as I planned to go back for the next summer season. If you are up for an food adventure and have the chance to go, go now!

Let me introduce

]]>
https://thson.de/2025/11/02/restaurants-brutalisten-stockholm/68fe528729f30800011cafd8Sun, 02 Nov 2025 21:14:43 GMT

I was surprised to hear that one of my favourite restaurants in Stockholm is closing at the end of 2025, as I planned to go back for the next summer season. If you are up for an food adventure and have the chance to go, go now!

Let me introduce you to Brutalisten, a small restaurant located in the heart of Norrmalm in Stockholm. When you walk in, you are greeted by a steel counter, a view of the kitchen, and lamps that resemble mushrooms. What makes this place different is that it adheres to the brutalist manifesto by Carsten Höller, who with his team, is also behind this restaurant. Inspired by brutalist art style, the idea is that dishes are made up of one ingredient only, with possibly the addition of salt and water. For semi-brutalist cuisine, a few additional ingredients may be used (the most common of which is some type of fat or sugar). The full manifesto can be found here. Guests can combine dishes and enjoy interesting drinks or wine with them, e.g. one of those was smoked apple juice with the scallops and the pumpkin dish. It was a very good match.

Is it food? Is it art? Is it food inspired by art or art inspired by food? Yes to all.

I found it pretty much by chance while travelling in Stockholm at the end of 2022. It was listed in the newcomer category on the Visit Stockholm website. One day, I just walked in, also after reading the review I mentioned int the how to find spots post, sat at the stainless steel counter and enjoyed my first Brutalist meal. While using local ingredients is now pretty common, reducing the number of ingredients to one, or maybe four, is unique to this place. Is every dish in the classical sense refined, tasty and balanced? Not always, but you are up definitely for an awesome adventure, where an ingredient is often taken apart and put back together or served with minimal changes.

What you can probably call one of the signature dishes of the SRB Dairy Cow, where the raw beef of an dairy cow is served with yoghourt, caramelized cream, and stock.

Brutalisten, Stockholm
Dairy Cow (2022)

Besides the pumpkin dish, the other dish that combined a lot of things from the same plant was the yellow pea dish. I never thought that such a complex dish could be made with only peas.

Brutalisten, Stockholm

Another dish from the 2022 menu that stood out to me was a serving of pork crackling seasoned with a powder made from pork and lard, smoked potato chips and lightly grilled hamachi. Interestingly from the November 2022 menu compared to 2024, you do not find orthodox brutalist dishes in the 2024.

Brutalisten, Stockholm

It was the first time I had seen miso made from bread. At that time, they used to use leftover bread for it. I have seen this a couple of times now; the last time was from Mimiferments. It was always great to see how they used different techniques to push a single ingredient to its limit.

Although the restaurant in Stockholm is closing, the concept will live on. They have hinted at launching a new venture in Italy and a cookbook is planned for release in 2026. Thank you to everyone involved in the restaurant and the concept! I'm excited what the new endeavours bring.

]]>
<![CDATA[The Microwave]]>After thinking again about which tools I use most frequently. In this case, it was the microwave. It sometimes gets a bad reputation for reheating low-quality prepackaged food, but in my nearly daily usage, this has never really been the case. It solves my breakfast problem and is used for

]]>
https://thson.de/2025/10/26/the-microwave/68f5c41429f30800011cafcaSun, 26 Oct 2025 19:28:36 GMTAfter thinking again about which tools I use most frequently. In this case, it was the microwave. It sometimes gets a bad reputation for reheating low-quality prepackaged food, but in my nearly daily usage, this has never really been the case. It solves my breakfast problem and is used for many other tasks. Its one of the kitchen tools that fully integrated in my daily routine.

My weekly lunch planning revolves around whether or not a food can be microwaved. It's pretty much the only way to reheat food. Though I may use the electric hot plates at some point to reheat corn tortillas. This means food that can be steamed (i.e. not something crispy) and that is not easily overcooked. When it comes to things like noodles and sauce, I try to keep them separate so that they don't turn into mush, but of course this doesn't always work. Also bringing sometimes three boxes to lunch is not really practical.

Besides that, it's great for quickly cooking ingredients, whether that's heating frozen peas, making a warm carrot salad or baking a quick cake. One fun thing is, if you want the crunch of a raw onion but not the raw flavour, microwave it for about 30 seconds to mellow the flavour. This is great in salads, for example. Do you have any unconventional uses for the microwave?

]]>
<![CDATA[When Advertising Works]]>Similar to the childhood recipes, when reading All Consuming by Ruby Tandoh, I wondered how social media and the advertising that comes with it influence my day-to-day life. While one would like to think that we are impermeable to adverts, at least sometimes I'm not. The question is:

]]>
https://thson.de/2025/10/19/when-advertising-works/6894fd8e7cdfc000012dba71Sun, 19 Oct 2025 20:17:15 GMTSimilar to the childhood recipes, when reading All Consuming by Ruby Tandoh, I wondered how social media and the advertising that comes with it influence my day-to-day life. While one would like to think that we are impermeable to adverts, at least sometimes I'm not. The question is: when does this happen and do I get a positive or negative thing out of it?

While I try to avoid ads in my everyday life, there are still some platforms where they are not fully blocked. I've somehow managed to tailor my personal profile so that I mostly see content related to food and restaurants, and surprisingly not many scams. This is probably because I've given them far too much personal information over the years. Instead I get events or restaurants, which are sometimes in places, that are not even close in a remote sense. My other favourite are ads aimed at industry professionals, which even for my hobby level are a far fetch, or events that have already taken place.

This got me thinking about the things I bought or did this year as a direct result of advertising. The main category is events, which is actually useful in a big city where it's impossible to find out everything that's happening and might be interesting to you. From the positive side, these were a couple of food- and drink-related events and one concert. The only item I can directly attribute to ads is a poster. I was already looking for one, but I couldn't find the right one initially. Then I saw an ad for a style that I actually liked. Not worth it were some restaurants, that I checked out because of their ads. As things rarely fall into the not worth it category, my hope is that I can still filter pretty well when it comes to deciding whether or not to follow an ad.

Lastly, there is something that falls between recommendations and traditional advertising. These are posts or recommendations from other people on social media, where the line can easily become blurred. I follow them because I'm generally interested in what they share, and I trust some of their recommendations. Overall, this has worked well so far, from getting recommendations for restaurants to books. Coincidentally, I initially added the book that inspired this post to my reading list after seeing a post about it by Fuchsia Dunlop. Have you ever thought what role ads in combination with your hobbies play a role in your day-to-day life?

]]>
<![CDATA[Out-Of-Place Ingredients: Möhrenauflauf Edition]]>Sometimes, when browsing a cookbook, you may wonder what the purpose of an ingredient in a recipe is. This recently happened when I was looking through "Belorussische Küche", a book published in 1988 by Verlag der Frau Leipzig that I acquired somewhat randomly over time. The

]]>
https://thson.de/2025/10/12/out-of-place-ingredients-mohrenauflauf-edition/68e773838232040001db3821Sun, 12 Oct 2025 21:10:53 GMTSometimes, when browsing a cookbook, you may wonder what the purpose of an ingredient in a recipe is. This recently happened when I was looking through "Belorussische Küche", a book published in 1988 by Verlag der Frau Leipzig that I acquired somewhat randomly over time. The recipe is for Möhrenauflauf (carrot casserole), but it looks more like a carrot cake. I was really surprised to see yeast in the ingredients list, as there isn't that much flour for it to hold the gas bubbles. The original recipe is as follows:

Zutaten: 500 bis 600 g Möhren, 10 bis 12 g Hefe, 1 bis 2 Eier, Salz, 1 bis 2 Esslöffel Mehl, 1 Esslöffel Zucker, 30 bis 40g Öl.

Beschreibung: In die rohe geriebene Möhrenmasse die in Wasser aufgelöste Hefe geben, dazu Eier, Salz, Mehl, Zucker und Öl. Alles vermischen und an einem warmen Ort zum Gehen abstellen. Danach den Teig in eine gefettete Metallform geben und 20 Minuten in der Röhre überbacken.

Which translates to:

Ingredients: 500 to 600g carrots, 10 to 12g yeast, 1 to 2 eggs, salt, 1 to 2 tablespoons flour, 1 tablespoon sugar, 30 to 40g oil.

Description: Add the yeast dissolved in water to the raw grated carrot mixture, along with the eggs, salt, flour, sugar, and oil. Mix everything together and leave in a warm place to rise. Then place the dough in a greased metal pan and bake in the oven for 20 minutes.

Judging by the amount of yeast, I would guess it's fresh yeast, but there are many other unknowns. For example, how finely should the carrots be grated, to bake at what temperature, and how long should it rise for? Despite missing this information, I made the recipe, letting it rise for around 2 hours at around 20°C and using the fine side of the IKEA box grater. I baked it in an greased glass box, as I currently don't have that small metal pan. Baking it at 180°C for 20 minutes turned it into a carrot cake casserole hybrid that was still a little underdone. It wasn't great, but edible.

So, after taking a stab at it, I still don't know why this recipe calls for yeast.. Searching online did not help, as it only turned up this recipe on a website that republished it from the book. Looking at other carrot casserole recipes, I haven't seen yeast in any of them, only in cake or bread recipes. I therefore started to track down the original book, which is a German translation of a Russian book called Белорусская кухня (Belarusian Cuisine), published by Ураджай. The 1993 third edition is relatively easy to find. My issue is that I can't read or understand Russian, but luckily technology to the rescue. Lo and behold, on page 145, I found a recipe that looks similar to the German one!

Ingredients: 6 морковей, 2 ст. ложки муки, 2 ст. ложки масла, 1 яйцо, 15 г дрожжей, сахар, жир, со'ль.

Description: Морковь натереть на крупной терке, добавить разведенные в воде дрожжи, яй ца, со'ль, муку, сахар, масло, перемешать и поставить в теплое место для брожения. Затем массу выложить в мета.л.лическую форму, смазанную жиром, и поставить в печь или духовку на 20 минут.

Which translates to the following:

Ingredients: 6 carrots, 2 tablespoons flour, 2 tablespoons oil, 1 egg, 15 g yeast, sugar, fat, salt.

Description: Grate the carrots using a coarse grater, add the yeast dissolved in water, egg, salt, flour, sugar, butter, mix and place in a warm place to ferment. Then place the mixture in a metal mould greased with fat and bake in the oven for 20 minutes.

The quantities are different and it seems that we are dealing with coarsely grated carrots, but this does not solve the mystery of the yeast. The title, Бабка морковная, does not help much either. However, it seems that there are a decent number of carrot cake recipes that use yeast. They tend to have a lot more flour in them. Maybe the quantity of flour is just wrong in the recipe, or I'm missing something here. If anyone reading this has ever made a recipe similar to this one, please let me know. Have you ever found an out-of-place ingredient when going through a cookbook?

]]>
<![CDATA[Gemüse-Salagne (Chard Vegetable Lasagne)]]>As a follow-up to my last post about childhood recipes, I'm kicking things off with probably the recipe I request most when I go back home: Gemüse-Salagne. Since we are not optimising for SEO here, the recipe follows now immediately.

Recipe

Ingredients

  • 100-120g onions
  • 270g carrots
]]>
https://thson.de/2025/10/05/gemuse-salagne-chard-vegetable-lasagne/68e2532f8232040001db379aSun, 05 Oct 2025 14:42:48 GMT

As a follow-up to my last post about childhood recipes, I'm kicking things off with probably the recipe I request most when I go back home: Gemüse-Salagne. Since we are not optimising for SEO here, the recipe follows now immediately.

Recipe

Ingredients

  • 100-120g onions
  • 270g carrots
  • 500g chard or spinach
  • 200g heavy ream
  • 200g milk
  • ~10g starch to thicken
  • 200g emmentaler cheese
  • ~12 lasagne sheets
  • ~1,5g curry powder (yes I actually measured it)
  • oil
  • salt
  • pepper
  • nutmeg

Tools

  • stove
  • oven
  • casserole
  • pot

Step 1: Peel and chop ingredients

Peel and slice the carrots. Then, roughly chop the green part of the chard and slice the stems. Dice the onion fairly finely.

Step 2: Sweat the onions and cook the vegetables

Gemüse-Salagne (Chard Vegetable Lasagne)

Heat a tablespoon of oil in a pot over a medium heat and sweat the onions for a few minutes without letting them take on any colour. Then add the carrots and chard stems. After about five minutes, add a splash of water and the rest of the chard. Cook until the chard has wilted and the carrots have softened slightly. Season with salt as you go.

Step 3: Add milk and heavy cream

Add the milk and heavy cream now. Bring everything to a boil.

Step 4: Add spices and thicken

Add the curry powder, nutmeg and pepper. You want there to be a hint of curry, not for it to dominate the dish completely. Start with around a gram and then add more if necessary. Then add some nutmeg and salt. Afterwards, mix the starch with a little cold water to thicken the mixture.

Step 5: Layering

Now we can start layering the ingredients. Begin by placing a small amount of the sauce on the bottom. Then add the first layer of lasagne sheets. If you are lucky, your dish will fit exactly three sheets per layer; mine doesn't, so I need to make the last one fit. Add the sauce and alternate until you reach the final layer, then top with the remaining sauce.

Step 6: Adding cheese

Gemüse-Salagne (Chard Vegetable Lasagne)

Now add the cheese on top. While grating is always an option, I have also found that you can achieve an even coverage of cheese by simply layering slices on top. The end result is roughly the same.

Step 7: Bake

Cook for around 45 minutes at 175°C, or until the lasagne sheets are cooked and the cheese is lightly browned.

Step 8: Wait and eat

Now comes the hardest part: waiting. You should wait until the bubbles have stopped, or for about 20 minutes. This allows the sauce to thicken and prevents you from burning your mouth on the cheese. Then slice it into pieces and either eat the whole dish immediately, begrudgingly share it, or freeze the rest for later in the week (or day).

Gemüse-Salagne (Chard Vegetable Lasagne)
Final result


You can use all the tricks from the Serious Eats recipes, but this recipe already results in a good dish without any extra steps that make it more complicated. What are childhood recipes?

]]>
<![CDATA[Keeping Family and Childhood Recipes Alive]]>I'm currently reading All Consuming by Ruby Tandoh, and one passage in particular has got me thinking. Where do my childhood recipes come from, and what are they actually? My daily cooking looks very different to that of my parents, and even more so to that of my

]]>
https://thson.de/2025/09/28/keeping-family-and-childhood-recipes-alive/68d917a30290d100014cb09cSun, 28 Sep 2025 13:33:35 GMTI'm currently reading All Consuming by Ruby Tandoh, and one passage in particular has got me thinking. Where do my childhood recipes come from, and what are they actually? My daily cooking looks very different to that of my parents, and even more so to that of my grandparents. I like the food, it's just that in my own cooking, I often cook different cuisines and chase new tastes, so it moves into the background, and maybe I cook something from it every couple of weeks. This is a shame, as I don't want the taste of my childhood and the attached stories to be forgotten over time.

I have started collecting a list of common dishes from home and, if possible, their history. I am slowly recreating them and writing down the recipes. I might publish some of them over time, like the potato and onion bread, which I actually make quite frequently. Similar to fairy tales, writing recipes down makes them static in the sense that they don't evolve easily over time, but it also preserves them for the future. So my advice is to do it now while you can, and share them so they don't get lost.

]]>
<![CDATA[Small Updates - Fridge Tetris, Coffee Grinder & Finding Spots]]>These topics do not warrant their own posts, but I thought I would provide some brief updates to previous ones.

First, my Fridge Tetris got slightly easier with the addition of another fridge. That doesn't mean this one isn't also filling up with ingredients, whether they&

]]>
https://thson.de/2025/09/21/small-updates-fridge-tetris-coffee-grinder-finding-spots/68d015290290d100014cb030Sun, 21 Sep 2025 17:49:27 GMTThese topics do not warrant their own posts, but I thought I would provide some brief updates to previous ones.

First, my Fridge Tetris got slightly easier with the addition of another fridge. That doesn't mean this one isn't also filling up with ingredients, whether they're ferments, things I've prepared for the week, or a collection of different pastes and sauces. I still follow the mantra of putting most things into boxes so they can be stacked, but there's now less need to squeeze everything into the small fridge.

I had a fun motorizing my hand grinder, and it still works like a charm. But recently, I decided to bite the bullet and get a D54 grinder. It's nice to have your beans ground quickly and with decent precision. Let's see how it holds up over time. For filter coffee, I stick with my hand grinder so I don't have to adjust my grind settings too often.

Over the past few weeks, I've been travelling quite a bit again. This meant being in different cities and exploring. There were many great places, but also a few disappointments. In addition to the list I initially posted in my guide, I have noticed a few more things. First, more places are obviously deleting reviews. A place that has been open for a decade will receive a negative review at some point. It is now something that I explicitly check for. Second, I also saw a few places that exchanged good reviews for freebies, such as, drinks. I have been asked by new places in the past to leave an honest review so they can be more easily discovered, which I think is fine. However, asking for a good review and offering something in return makes the entire review system pointless. Ultimately, relying on recommendations from people you trust is still the best option. On the positive side, walking around is still an awesome way to find a spot. For example, I had some time to wait, so I picked a coffee shop to go to. But on the way, I spotted a different one that looked nice, so I went there instead and had a great time.

Things are ever evolving, so I might try present the current state from time to time if there is something to report.

]]>
<![CDATA[A Decade of THSOn]]>Looking back, it's crazy to think that this blog is 10 years old today. Although there was a five-year gap, it is still going, and this year has been the most active yet. The content is simply what I deem worthy of sharing over time. Most of it

]]>
https://thson.de/2025/09/09/a-decade-of-thson/68959c46592bc90001aa0c27Tue, 09 Sep 2025 06:00:04 GMTLooking back, it's crazy to think that this blog is 10 years old today. Although there was a five-year gap, it is still going, and this year has been the most active yet. The content is simply what I deem worthy of sharing over time. Most of it is cooking-related, but there is also some technical content and some of my school projects.

I don't have any long-term statistics on what content is actually viewed. From the recent ones, the fermented potato bread recipe seems to be the most popular, according to Google Search. Interestingly, the short music analysis still receives a decent number of views every year. I wonder which teacher sets the task that results in this being found every year.

Although the technology behind the content isn't usually important, in this case it's what has kept this blog alive. After playing around with a few CMSs, I ended up with Ghost. It started as a simple blogging platform, but has now evolved into a fully-fledged newsletter and subscription platform, most of which I don't really need. At its core, it's still a simple blogging editor, and upgrading from version 0.7 to 6.0 was mostly painless.

Thank you to the few people subscribed via RSS! I hope you enjoy my ramblings on various topics. You can now also subscribe via ActivityPub at @[email protected].


To the next decade of THSOn and many more posts to come!

]]>
<![CDATA[Doing the Dishes]]>I was thinking. How many hours do I spend doing the dishes each month? Even if it's only 20 minutes a day, that's still easily ten hours a month. I'm sure it's more than that. Tracking it accurately over a month or

]]>
https://thson.de/2025/09/07/doing-the-dishes/68b72bae1eeb600001c280efSun, 07 Sep 2025 19:35:29 GMTI was thinking. How many hours do I spend doing the dishes each month? Even if it's only 20 minutes a day, that's still easily ten hours a month. I'm sure it's more than that. Tracking it accurately over a month or so would be interesting and a challenge in itself.

The easy option out would be to buy a dishwasher. Unfortunately, there isn't really a good place for it in my current apartment. The only place with the necessary space and water outlets is the bathroom. It might be unconventional, but I was seriously considering it. In the end, however, I decided against it as it would not be very convenient. An astiankuivauskaappi, a cabinet with a built-in drying rack normally placed over the sink, would solve at least the drying problem. Though I haven't seen one outside of Finland. I wonder why they aren't in more homes without a dishwasher.

Ultimately, if you enjoy cooking, there's no getting around it. However, some things can help to mitigate it. Start with a clean kitchen to avoid making more mess than necessary. Then, cut things in an order that makes sense (e.g. vegetables first, then meat), see if bowls can safely be reused (e.g. if it had chopped carrots in it before, it can also hold peppers) and clean and reset your station, which is likely the whole kitchen, between major steps. Having lots of dish towels that can be used for everything also makes life in the kitchen easier. While it might not be the glamorous part of cooking, it is always there, so you might as well make it a bit more enjoyable.

]]>
<![CDATA[Cocktail Bars]]>As some of you might have guessed, cocktails are one of my hobbies. Whether it's making them or travelling around the world and trying new and familiar creations in different places, is what makes it intresting. As I am sometimes asked for recommendations, here are some of them.

]]>
https://thson.de/2025/08/24/cocktail-bars/6894fd8e7cdfc000012dba6fSun, 24 Aug 2025 20:00:00 GMT

As some of you might have guessed, cocktails are one of my hobbies. Whether it's making them or travelling around the world and trying new and familiar creations in different places, is what makes it intresting. As I am sometimes asked for recommendations, here are some of them. Composing a list was harder than I anticipated. Ultimately, I decided to list bars that have influenced me or that I want to highlight. Starting with ones that I have been to multiple times. Following these are recommendations for places that I have only been to once. This isn't intended to be a complete list of all the bars I've visited, nor is it a list of all recommendations for different cities. That might be a project for another day.

Brill No.6, Bremen

Address: Am Brill 6, 28195 Bremen
Social: https://www.instagram.com/brillno6

Let's start with Brill No.6 in Bremen, the first actual cocktail bar that I have been to. Spanning over three floors, with the actual bar on the first one, you find well made classics and their own creations. Served with a focus on the cocktail, followed always with some water and snacks. They sparked my passion for cocktails and I remember having my first Aviation and Blood and Sand there. Although I haven't been back for a few years, they set me on this journey and shaped my initial expectations of what a cocktail bar should be.

Nou 7, Hamburg

Address: Gertigstraße 7, 22303 Hamburg
Social: https://www.instagram.com/nou7bar

In 2024, I stumbled upon a post from Le Lion announcing a new bar opening in Winterhude, which I bookmarked as a place I wanted to visit. One day, when we were in Hamburg, we ended up making our way to the Nou 7, where we found a place with absolutely creative and very good drinks. Hai uses probably every available technique, from foams, over carbonation, to rotovaps, in order to create something new and delicious. I have yet to visit another place in Germany that is innovating cocktails to that level. This is clear not only from the drinks on the menu, but also from ones that Hai creates on the spot when you're sitting at the bar; a place that I highly recommend sitting at. If you're ever in Hamburg, go there enjoy the drinks, and say moin (or hi)!

Heir Beverage House, Munich

Address: Parkstraße 30, 80339 München
Social: https://www.instagram.com/heir.beverage.house

The Heir Beverage House is relatively new to Munich and is located outside the city centre in the Westend district. If you're willing to travel a bit, you'll be greeted at your new neighbourhood bar with a small welcome in liquid form, popcorn and water. Max and Johannes will make sure you have a great time and enjoy some tasty drinks. If you ever feel bored, you can try to beat someone on the arcade machine in the back. They have now launched their second cocktail menu, comprising very sippable, lovely summer drinks. Alternatively, they can make you something according to your preference. There are also many places to eat in the area, so you're guaranteed a good night out.

Le Lion, Hamburg

Address: Rathausstraße 3, 20095 Hamburg
Social: https://www.instagram.com/barlelion

One that doesn't really need an introduction, as the Le Lion is definitely an institution in Hamburg for a reason. It is perhaps best known internationally for Jörg Meyer inventing the Gin Basil Smash there, and it offers a range of classic drinks and its own tasty creations. This bar taught me how good drinks and attentive service go hand in hand. I don't think my water glass ever ran low there.
It's pretty much guaranteed to be packed at the weekend, but it's always a good stop during the early hours or during the week when in the area.

Fox Den, Brussels

Address: Rue Josse Impens 2, 1030 Schaerbeek
Social: https://www.instagram.com/foxden_cocktailbar

Being in Brussels at least once a year, I decided to check out what cocktail bars are around there. By chance I found Fox Den, a small bar outside the city center in Schaerbeek, run by the same people also behind Life is Beatiful in Brussels. The menu is structured like a world map, with a small passport to explore different cocktails. A playful concept that is nicely executed with good and creative drinks. It was one of those unexpected, fun discoveries. The main centrepiece of the bar is the counter itself, where most people are seated. If I ever had the space to build a bar, I would probably do it in a similar style.

Cocktail Bars
The bar counter at Fox Den

Seed Library, London

Address: 100 Shoreditch High St, London E1 6JQ
Social: https://www.instagram.com/seedlibraryshoreditch

Have you ever wondered how many parts of a cow can be turned into a tasty drink? Or are you just looking for great drinks? Either way, the Seed Library is then the right place for you. Here, you can enjoy creative drinks made using a variety of fermentation techniques, including garums, in a cozy atmosphere, complete with a long bar to sit at. While I'm not that often in London, its is always fun to see what new they have come up with.

Note that there are many cocktail bars within walking distance in Shoreditch, so it's a good area to explore. If you're feeling hungry, head to Beigel Bake, which is guaranteed to be open and has great bagels.

Further places

These are places that I have only visited once (so far). From experience, I'm reluctant to recommend a place based on just one visit, as things are not always consistent and places evolve. Nevertheless, I would like to highlight the following ones.

Furek Cocktail Stand, Kyoto

Address: Higashiyamaku-kaneicho 352, Kyoto
Social: https://www.instagram.com/cocktail_stand_furek/

I visited them based on a personal recommendation when they were on the fifth floor of a building that was difficult to find. What makes them special is that they create a variety of unique flavours using their spring water to make excellent cocktails. Combined with great hospitality, I had a wonderful evening there. Since then, they have moved to their own building, which has its own well for the water. I hope to visit their new place some time in the future.

Cocktail Bars
Distill running at Furek with their own spring water.

Bar Cham, Seoul

Address: 34 Jahamun-ro 7-gil, Jongno District, Seoul
Social: https://www.instagram.com/bar.cham

I went to Bar Cham by chance when visiting Seoul, as I walked past shortly after they opened. At other times, it is normally completely full and booked out. The bar is housed in a wooden building and has a fully wooden interior. When I was there, the cocktail menu featured the best creations from previous years, most of which had a Korean twist. I had some very good cocktails there, such as a Gimbab-inspired one and one with hot milk and pumpkin. Many of their drinks are served in ceramic vessels, which really fits with the wooden interior. One new thing was that you could get either cold or warm water, which is not surprising for Korea, but a first for a cocktail bar, and something I started to enjoy.

Bar Us, Bangkok

Address: 61/37 Floor, Opposite side of security kiosk (Glass door) Floor 1, 1 Sukhumvit 26, Khlong Tan, Khlong Toei, Bangkok
Social: https://www.instagram.com/bar.us.bkk

While travelling in Chiang Mai, I asked a bartender at the White Rabbit if he could recommend in Bangkok. Bar Us was one of them. Once arrived, I went. Their menu features food-inspired cocktails. This ranges from familiar flavours such as satay, which translate quite well, to more experimental combinations such as beef and onion, which is definitely unusual, but somehow they made it work. They took this concept and executed it extremely well. The fact that they made it into the top 50 in the world a few months later didn't really surprise me.

Cocktail Bars

Velour, Copenhagen

Address: Store Regnegade 26A, København
Social: https://www.instagram.com/velourcph

This bar is a little different from the rest as it's a project of the Nordic EtOH distillery and only serves drinks made with its own spirits. When you go there, you have two options: you can sit at one of the tables and order cocktails from the menu, or you can sit at the bar and enjoy a set tasting menu. The latter option is quite unusual, and I was a little hesitant at first, but I assure you that you will have a great time. You will be served a selection of drinks, along with the spirits they contain. It's pretty much guaranteed that you'll try something interesting with all the spirits from the distillery.

]]>
<![CDATA[Useful Tools for the Kitchen]]>Having fancy tools such as wireless thermometers that calculate the time it will take to reach a desired temperature is pretty awesome. However, these have not made my day-to-day cooking simpler or better. You don't need much to cook: a knife, a chopping surface and something to cook,

]]>
https://thson.de/2025/08/17/useful-tools-for-the-kitchen/68a20579592bc90001aa0d4fSun, 17 Aug 2025 17:46:16 GMTHaving fancy tools such as wireless thermometers that calculate the time it will take to reach a desired temperature is pretty awesome. However, these have not made my day-to-day cooking simpler or better. You don't need much to cook: a knife, a chopping surface and something to cook, whether that's directly over a fire or a pot, pan or wok. OK, maybe some utensils like forks, chopsticks or a spoon would also be helpful. Still, these are the rather inexpensive things I miss when I don't have them, and they have also travelled with me abroad.

As someone who is passionate about tea, coffee and fermentation, scales make life much simpler by taking most of the guesswork out of the process. While there are some very expensive models, a simple gram-accurate scale for most things and a cheap jewellery scale for when I need greater precision, e.g. for measuring salt or working with hydrocolloids, also does the job very well. While eyeballing is good enough for most cooking, sometimes it's nice to have precise measurements. It's also handy to have the ratios written down when something goes exceptionally well, so you can repeat it or scale to fit the use case.

In a similar vein, a simple but fast kitchen thermometer. Combined with the scales, it brings much more precision and repeatability to the kitchen. Guessing whether the meat or bread is ready? Want to make sure that the rice is cold enough to start growing koji? Easy! Initially, I made the mistake of buying the cheapest one I could find. Those are useless as they take forever to respond and are often imprecise. On the other hand, the really expensive ones aren't really worth it if you don't have a use for them. I'm still happy with the TFA Thermojack for everyday use. However, I'm now on my third one over the last decade, as the previous two broke from heavy use.

Although I have a blender as part of my kitchen equipment, I rarely use it. In contrast, my immersion blender I use almost weekly. Be it for emulsifying dressings and sauces, such as hollandaise, blending soups or even to make whipped cream if necessary. I bought mine when I started university, and it still works as intended.

A dough disk or bench scraper is helpful when working with dough and also surprisingly for a lot of other things. Whether it's cleaning a table or quickly transferring cut vegetables. I'm still sad that the one I had for many years, got lost in one of the moves. I got a replacement one, but it is not the same.

A Microplane is definitely more of a 'nice to have', but it's such a useful tool. It's great for grating nutmeg, zesting, grating ginger, garlic or Parmesan. If you ever need a gift for someone who likes to cook and doesn't have one, it's a useful option. Over the years, I got a second, medium-coarse one, which is a nice addition.

While the list of kitchen items that I took with me abroad is even longer, most of them are neither inexpensive nor necessary. Case in point: the sous vide machine and the pressure cooker. The former is very good for specific uses, but not really necessary; the latter speeds up a lot of things and you can make interesting dishes such as caramelised carrot soup (yes it's actually worth it). Unfortunately, the nicer ones are definitely not cheap. What kitchen tools could you not be without?

]]>
<![CDATA[Go-To Recipes]]>After writing the post about chasing the new, I thought about which recipes or concepts have remained in my regular cooking repertoire over the years. Many of them are weeknight meals, which is one of the reasons they are made more often. Also, the original sources are often only used

]]>
https://thson.de/2025/08/10/go-to-recipes/6894fd8e7cdfc000012dba73Sun, 10 Aug 2025 10:22:45 GMT

After writing the post about chasing the new, I thought about which recipes or concepts have remained in my regular cooking repertoire over the years. Many of them are weeknight meals, which is one of the reasons they are made more often. Also, the original sources are often only used as rough guidance. Although I keep fairly detailed notes on what I usually make, I don't maintain a daily log. This means this list is more of an educated guess, as no two weeks are really similar. I have some rough notes from my university days, but they only help so much.

When I was living abroad, I made so many Flammenkuchen that I became known for making them in the shared kitchen. Make a basic dough, leave it to rest, then roll it out as thinly as possible. Add crème fraîche mixed with nutmeg, salt and pepper, and your choice of toppings. Bake until crispy. Those flimsy IKEA chopping boards work well as a pizza peel in a pinch. Just make sure they don't touch the oven tray or they will melt! In my opinion, it's still one of the best recipes in terms of taste to effort, and it's still easily achievable in a home oven compared to most pizza styles.

When fennel is in season, I make an apple and fennel salad that Chefsteps published years ago. The only difference is that I add a 9:1 mix of gum arabic and xanthan gum to stabilise the dressing if I take it to work. If eaten in larger quantities as a main course, reduce the salt content. Speaking of seasonal ingredients, matjes, whether served with potatoes as a salad or on a roll, is quite common in my kitchen when it is in season. Unfortunately, I can't easily get hold of the really good ones where I currently live. Oven-roasted Brussels sprouts are a staple of my diet when they are in season, appearing on a weekly basis.

While sauerkraut has always been a staple food for me, kimchi has only recently become part of my regular culinary life. I usually make it myself as it is too prohibitively expensive to buy here. When it's hot, I often make cold buckwheat noodles with a spicy kimchi sauce and cucumbers. I first tried this in Korea last year, and it has been a staple ever since. Furthermore, bibimbap as a template is a great way to use up any leftovers.

I tried tracing back through my notes and the public record to pinpoint when my interest in Chinese cuisine began, but I can't exactly find out the first dish I cooked. It must have been 2017 or 2018 with Chinese Cooking Demystified coming up and one of their recipes. Under the first dishes I cooked was Peking Sauce pork, and I still cook it regularly, often just eating it with rice instead of the pancakes. Tianmianjiang (甜面酱) was one of those ingredients, that were a new flavour profile to me. The next in this category was Pixian Doubanjiang (郫县豆瓣酱), which is just an awesome ingredient, just make sure that you get the one from Sichuan Pixian Douban Co., Ltd without oil. It is the best variety commonly available in Europe. Mapo tofu is also a staple dish that uses this as one of its main ingredients. You can make it while the rice is cooking, and I always have all the ingredients (except for minced meat) at home. Another quick option is Dan Dan Noodles, which can be whipped up in about 10 minutes if you have all the ingredients. If I need to make something really quickly, I like to make the MSG noodles recipe or a smashed cucumber salad with chilli oil (or Lao Gan Ma when the chilli oil has run out again).

There's one recipe that I never really make in its original form: roasted carrots with couscous, pickled onions and feta. This recipe comes from The Flavor Equation, a really well thought out cookbook. I often use different flavours to roast the carrots, other than sesame and chilli, and I also use different vegetables and switch the pickled shallots for other onions. Sometimes I switch the couscous with something else, such as rice or bulgur. Partly due to this recipe, I normally always have some pickled onions in the fridge, which are a versatile topping for many things. Similarly, I often make marinated eggs in lapsang souchong, soy sauce, koji, or some other brine. They make a nice and quick addition to many meals.

I don't bake quiches that often at the moment, but when I do, I like to take inspiration from Alex the French Guy Cooking by adding spices to the pastry itself. I think it works really well and it's a technique that has really stuck with me over time. When it comes to bread and pastries, potato and onion bread is probably the most common in my rotation. Also, I started making Franzbrötchen more regularly when I first moved to a place where I couldn't get them. Fortunately, they freeze well, too.

Ultimately, everything you try expands your repertoire. This could be an actual recipe, a technique, a flavour or an entirely new idea. Furthermore, it is also a good skill to have a few things to always come back to and to become better and better at them.

]]>