Skip to content

attest-enroll: enrolling a device for TPM2 remote attestation

This script, sbin/attest-enroll implements enrollment of a device into the attestation system using the device's TPM's Endorsement Key's public key (EKpub).

It takes as arguments an EKpub or the EKpub's public key in PEM form, and a desired hostname, and it creates the enrollment state for that tuple.

Enrollment state consists of:

  • the EKpub
  • the hostname
  • any configured secrets and metadata for that device, with secrets encrypted to the EKpub

Secrets are encrypted to the EKpub using sbin/tpm2-send. More on this below.

The enrollment database is file based. The directory structure looks like:

$DBDIR/??/...               # enrollment state for enrolled hosts
$DBDIR/${ekhash:0:2}/${ekhash}/     # <- enrollment state for SHA-256(EKpub)
$DBDIR/${ekhash:0:2}/${ekhash}/     # <- enrollment state for SHA-256(EKpub)
$DBDIR/hostname2ekpub/          # <- index by hostname
$DBDIR/hostname2ekpub/${hostname}   # <- file containing ${hostname}'s SHA-256(EKpub)

Configuration is via bash scripts sourced by sbin/attest-enroll:

/etc/safeboot-enroll/conf       # Principal config file (optional)
$DBDIR/attest-enroll.conf       # Additional config file (optional)

Configuration parameters can also be given on the command-line. See the sbin/attest-enroll usage message for more details.

Escrow of Enrolled Secrets

If an ESCROW_PUBS_DIR is configured, then every secret subsequently encrypted to any TPM is also encrypted to the defined escrow authorities' public keys:

$ESCROW_PUBS_DIR/           # <- EKpubs/PEM of escrow agents here (optional)
$ESCROW_PUBS_DIR/otherEscrowName.pem    # Public key in PEM form

Enrollment State Generation

Enrollment state is generated by configured genprogs. Two built-in genprogs are:

  • genhostname -- creates the metadata file recording the hostname,
  • genrootfskey -- creates a 64-byte secret key for root filesystem / volume encryption.

Other, external genprogs can be added and configured. The following external genprogs are included:

  • gencert -- creates a private key and a certificate for its public key naming the hostname,
  • genkeytab -- creates a "keytab" with the keys for the hostname's host service Kerberos principal.

Sites can provide additional genprogs to generate a large variety of credentials and metadata:

  • PKIX certificates for IPsec, TLS, and/or other purposes
  • OpenSSH host keys and possibly OpenSSH host key certificates
  • Service access tokens


Encryption is implemented by sbin/tpm2-send.

Decryption is implemented by sbin/tpm2-recv.

Two methods are possible for encryption to a target TPM's EKpub:

  • the "WK" method (our name for it)
  • the "TK" method (our name for it)

Both methods support setting a policy on the ciphertext such that any application using the target's TPM to decrypt it must first execute and satisfy that policy.

The "WK" method uses TPM2_MakeCredential() via tpm2-tools' tpm2 makecredential command, using the none TCTI (i.e., implemented in software). The target's EKpub is used as the handle input parameter to TPM2_MakeCredential(). A well-known key (WK), and the desired policy (if any) are used to compute the cryptographic name of the "activation object" (objectName) input parameter to TPM2_MakeCredential(). Decryption consists of calling TPM2_ActivateCredential() with the handle to the WK as the activationHandle input parameter, and the EK as the keyHandle input parameter of TPM2_ActivateCredential(). If a policy is desired then the adminWithPolicy attribute will be set on the WKname, which will cause TPM2_ActivateCredential() to require that the policy be satisfied.

The "TK" method uses TPM2_Duplicate() via tpm2-tools' tpm2 duplicate command using the none TCTI (i.e., implemented in software. An RSA keypair is generated and its private key is exported to the target TPM using TPM2_Duplicate() -- we call this the "transport key", the TK. The small secret will then be encrypted to the TK's public key (TKpub). Decryption works by importing the exported TK and then using TPM2_RSA_Decrypt() to decrypt the small secret encrypted to the TKpub.

Encryption of Larger Secrets

In all cases, regardless of a secret's size, we use sbin/tpm2-send to encrypt an ephemeral, random AES-256 key to the target's TPM, then we encrypt the actual secret in that AES key in confounded AES-256-CBC-HMAC-SHA-256 cipher mode. The final ciphertext consists of those two ciphertexts: the one generated by sbin/tpm2-send and the one generated by AES encryption.

Encryption using the confounded AES-256-CBC-HMAC-SHA-256 cipher mode consists of:

  • prepending a full cipher block of random bits (the "confounder") to the plaintext,
  • encrypting the plaintext in AES-256 in cipher block chaining (CBC) mode with padding and zero IV,
  • appending the HMAC of the resulting ciphertext using SHA-256 as the hash function for HMAC.

Decryption using the confounded AES-256-CBC-HMAC-SHA-256 cipher mode consists of:

  • computing the HMAC-SHA-256 of the ciphertext excluding the MAC,
  • checking that the HMAC of the ciphertext matches the HMAC in the ciphertext,
  • decryption of the ciphertext using AES-256 in CBC with zero IV,
  • removing the first full cipher block of the plaintext (the "confounder"),
  • removing the padding.

The confounder serves mainly to function as a sort of explicit random IV while allowing us to use a zero IV in the openssl enc command invocations. Since all of this is implemented in bash with openssl, and OpenSSL does not provide a decent authenticated encryption mode for AES, we script the confounded AES-256-CBC-HMAC-SHA-256 cipher mode, which turns out to be simple and elegant in bash.

With OpenSSL 3.0 we could use ciphertext stealing mode (CTS) instead of CBC, and then we'd be using exactly the same more as Kerberos uses (confounded CTS with HMAC).

The CTS cipher mode is a variant of CBC mode that avoids the need for padding, in exchange for which advantage CTS requires plaintexts to be at least one full cipher block (16 bytes) long, thus CTS is always used with a confounder, and the confounder functions as an explicit IV that allows the external IV to be zero.

Last update: July 26, 2021