Subkeys

Subkeys is an OP-TEE-specific implementation to provide a public key hierarchy. Subkeys can be delegated to allow different actors to sign different TAs without sharing a private key.

The first key in the chain is verified using a root key. Example Subkey hierarchy:

Each subkey defines a UUIDv5 namespace 1 to which another signed subkey or TA must belong. This avoids that one subkey can be used to sign TAs which should be signed with a different key. Since the UUIDs are restricted by this there is also a special kind of subkey, called identity subkey, which uses the same UUID as the TA it is supposed to sign.

A subkey consists of two files, a private key pair in a .pem file and the signed public key in a .bin file. Subkeys reuse the signed header (SHDR) format used for signed TAs, followed by a payload holding a public key and UUID among other fields. A subkey is formatted with all fields in little endian as:

Size in bytes

Field Name

Protected by field

Signed header (struct shdr)

4

magic

hash

0x4f545348

4

img_type

hash

3, SHDR_SUBKEY

4

img_size

hash

4

algo

hash

Signature algorithm, GP TEE_ALG_*

4

hash_size

hash

4

sig_size

hash

hash_size

hash

sig

sig_size

sig

previous pub key

Root key or a subkey higher up in the chain

Subkey header (struct shdr_subkey)

16

UUID

hash

UUIDv5 namespace for next subkey or TA

4

name_size

hash

Size of the UUIDv5 name for the next subkey or TA, 0 for identiy subkeys

4

subkey_version

hash

4

max_depth

hash

4

algo

hash

Signature algorithm for the next subkey or TA

4

attr_count

hash

Subkey attrs * attr_count

Attributes of the public key in this subkey

4

id

hash

GP TEE_ATTR_*

4

offs

hash

4

size

hash

opaque data padding up this binary to img_size

hash

The subkey attributes above point into this area using offset and size

All subkeys included in the subkey hierarchy are added in front when a TA is signed using a subkey. For example, if a TA is signed using the second-level subkey above it would look like this:

Size in bytes

Binary

first.img_size

First-level subkey | Signed by Root key

first.name_size

UUIDv5 name string for the next subkey

Not signed, used to prove that the next UUID is in the namespace of the First-level subkey. This size is from “name_size” of the previous subkey (First-level).

second.img_size

Second-level subkey

Signed by First-level subkey

second.name_size

UUIDv5 name string for the TA

Not signed used to prove that the next UUID is in the namespace of the Second-level subkey. This size is from “name_size” of the previous subkey (Second-level).

second.img_size

TA

Signed by Second-level subkey

The signed TA binary is self-contained with all the public keys needed for verification included, except the public root key which is embedded in the TEE core binary.

The UUIDv5 name string is a separate field between subkeys and the next subkey or TA to allow a subkey to be used to sign more than one other subkey or TA.

A signed TA or subkey can be inspected using the sign_encrypt.py script, for example:

$ scripts/sign_encrypt.py display --in 5c206987-16a3-59cc-ab0f-64b9cfc9e758.ta
Subkey
 struct shdr
  magic:      0x4f545348
  img_type:   3 (SHDR_SUBKEY)
  img_size:   320 bytes
  algo:       0x70414930 (TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256)
  hash_size:  32 bytes
  sig_size:   256 bytes
  hash:       f573f329fe77be686ce71647909c4ea35b5e1cd7de86369bd7d9fca31f6a4d65
 struct shdr_subkey
  uuid:       f04fa996-148a-453c-b037-1dcfbad120a6
  name_size:  64
  subkey_version: 1
  max_depth:  4
  algo:       0x70414930 (TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256)
  attr_count: 2
  next name:  "mid_level_subkey"
Next header at offset: 692 (0x2b4)
Subkey
 struct shdr
  magic:      0x4f545348
  img_type:   3 (SHDR_SUBKEY)
  img_size:   320 bytes
  algo:       0x70414930 (TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256)
  hash_size:  32 bytes
  sig_size:   256 bytes
  hash:       233a6dcf1a2cf69e50cde8e20c4129157da707c76fa86ce12ee31037edef02d7
 struct shdr_subkey
  uuid:       1a5948c5-1aa0-518c-86f4-be6f6a057b16
  name_size:  64
  subkey_version: 1
  max_depth:  3
  algo:       0x70414930 (TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256)
  attr_count: 2
  next name:  "subkey1_ta"
Next header at offset: 1384 (0x568)
Bootstrap TA
 struct shdr
  magic:      0x4f545348
  img_type:   1 (SHDR_BOOTSTRAP_TA)
  img_size:   84576 bytes
  algo:       0x70414930 (TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256)
  hash_size:  32 bytes
  sig_size:   256 bytes
  hash:       ea31ac7dc2cc06a9dc2853cd791dd00f784b5edc062ecfa274deeb66589b4ca5
 struct shdr_bootstrap_ta
  uuid:       5c206987-16a3-59cc-ab0f-64b9cfc9e758
  ta_version: 0
 TA offset:  1712 (0x6b0) bytes
 TA size:    84576 (0x14a60) bytes

Footnotes

1

UUIDv5 and namespaces are described in RFC4122. Note that OP-TEE uses a truncated SHA-512 instead of the weak SHA-1 hash when when deriving a new UUID from a namespace and name.