Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ debian/safeboot
*.o
a.out
certs/*[0-9]
*.tar
*.tgz
*.log
*.out
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,12 @@ shellcheck:
for file in \
sbin/safeboot* \
sbin/tpm2-attest \
sbin/tpm2-send \
sbin/tpm2-recv \
sbin/tpm2-policy \
initramfs/*/* \
functions.sh \
; do \
shellcheck $$file ; \
shellcheck $$file functions.sh ; \
done

# Fetch several of the TPM certs and make them usable
Expand Down
167 changes: 167 additions & 0 deletions docs/attest-enroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# attest-enroll

This script, [`sbin/attest-enroll`](/sbin/attest-enroll) implements
enrollment of a device using its 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`](/sbin/tpm2-send). [More on this below](#Encryption).

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)
$DBDIR/hostname2ekpub/...
```

Configuration is via bash scripts sourced by
[`sbin/attest-enroll`](/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`](/sbin/attest-enroll#L165) 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/someEscrowName.pub # EKpub
$ESCROW_PUBS_DIR/otherEscrowName.pem # Public key in PEM form
```

## Enrollment State Generation

Enrollment state is generated by configured `genprog`s. 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 `genprog`s can be added and configured. The following
external `genprog`s 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 `genprog`s 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

Encryption is implemented by [`sbin/tpm2-send`](/sbin/tpm2-send).

Decryption is implemented by [`sbin/tpm2-recv`](/sbin/tpm2-recv).

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

- the "EK" 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 "EK" 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`](/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](/functions.sh#L396) and
[elegant](/functions.sh#L425) in bash.

With OpenSSL 3.0 we could use [ciphertext stealing mode
(CTS)](https://en.wikipedia.org/wiki/Ciphertext_stealing) instead of
CBC, and then we'd be using exactly the same more as Kerberos uses
(confounded CTS with HMAC).

> The [CTS cipher mode](https://en.wikipedia.org/wiki/Ciphertext_stealing)
> 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.
107 changes: 80 additions & 27 deletions docs/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "tpm2-attest: TPM2 Remote Attestation"
summary: >-
tpm2-attest is a simple way to have an untrusted Linux machine
generate signed TPM quotes, validate those quotes and endorsement keys
on a remote attestation server, and seal a secret message that will
on a remote attestation server, and seal secret messages that will
only be unsealable on that specific TPM.
image: "images/tpm-header.jpg"
---
Expand Down Expand Up @@ -31,24 +31,39 @@ those two packages into a simple script that provides the four
main attestation functions: sign a quote, validate a signed quote,
seal a secret for a specific TPM, and unseal it with that TPM.

Related to this is [`attest-enroll`](attest-enroll.md), a script that
enrolls a device (e.g., a laptop, a server, etc.) given just its
TPM's endorsement key's public key (`EKpub`) and a name. Enrollment
can produce and encrypt to that `EKpub` any number of long-term
secrets that are later sent to that device during successful
attestation.

## tl;dr

* Client: Get `$nonce` and `$pcrs` from server (or use a time based nonce)
* Client: `tpm2-attest $nonce $pcrs > quote.tgz`
* Client: Send `quote.tgz` to server
* Server: `tpm2-attest verify-and-seal quote.tgz $nonce < secret.txt > cipher.bin`
* Server: Send `cipher.bin` to client
* Client: `tpm2-attest unseal < cipher.bin > secret.txt`
* Client: Use `secret.txt` to decrypt disk, authenticate to network, etc
* Enroll:

- extract `EKpub`, pick a device name
- run [`attest-enroll`](attest-enroll.md) (typically via an HTTP
API) with that `EKpub` and name

* Attest:

- Client: `tpm2-attest > quote.tar`
- Client: Send `quote.tar` to server (typically via an HTTP API)
- Server: `tpm2-attest verify quote.tar | attest-verify` to verify the client's state
- Server: `tpm2-attest seal quote.tar < enrolled-secrets.tar > cipher.bin`
- Server: Send `cipher.bin` to client
- Client: `tpm2-attest unseal < cipher.bin > enrolled-secrets.tar`
- Client: use `tpm2-recv` to decrypt long-term secrets in `enrolled-secrets.tar`

---------------------

## Attestation protocol

The protocol requires a few round-trips between the local machine
The protocol requires a one round-trip between the local machine
(the client) and the remote attestation machine (the server), and all
comunication between the Client and the Server can be in the clear.
There is no sensitive data exchanged -- the `quote.tgz` file contains
There is no sensitive data exchanged -- the `quote.tar` file contains
only public keys and PCR values that are essentially public, and the
`cipher.bin` reply is encrypted with the TPM's Endorsement Key, so it
should only be unsealable by that specific TPM.
Expand All @@ -67,32 +82,68 @@ The keys involved are:
* TPM Endorsement certificate (`ek.crt`), signed by the TPM manufacturer, often stored in the TPM NVRAM
* Attestation Key (`AK`), a signing-only key generated by the TPM, but not signed by it (for inexplicable reasons), used to sign the PCR quotes

The protocol between the client and the server goes in four phases: communication initiation, quote signing, quote validation, and secret sealing
There are two protocols: enrollment, and attestation. Each protocol
is one round trip.

### Enrollment

* An administrator, or the client itself, sends the client's `EKpub`
and desired hostname to the enrollment server.
* The enrollment server checks that the client's enrolled state does
not exist, then it creates it.

See [`sbin/attest-enroll`](sbin/attest-enroll).

The enrolled state consists of secrets encrypted to the client's
`EKpub` and cleartext metadata.

### Attestation

### Initiation
* Client creates an Attestation Key (`AK`), quotes all the PCRs and
current time, and sends its `EKpub`, `AKpub`, quote, and eventlog
to the server.

* Client contacts the attestation server, requests a nonce that is used to prevent reply attacks, and the list of PCRs to be signed.
* Server sends the nonce (in the clear is fine, since it is literally a random number)
This is done with

```
tpm2-attest quote $nonce $pcrs > quote.tar
```

* The server checks the quote, calls `TPM2_MakeCredential()` with the
`AKpub` as the activation object, the `EKpub` as the key to encrypt
to, and an AES-256 session key, and sends back the output as well
as the enrolled state encrypted in that AES-256 session key.

This is done with

```
tpm2-attest verify | attest-verify | tpm2-attest seal
```

The client recovers the AES-256 session key using
`TPM2_ActivateCredential()` and then decrypts the secrets in its
enrolled state. This is done with:

### Quote signing
```
tpm2-attest quote $nonce $pcrs > quote.tgz
tpm2-attest unseal
```

With this command the client machine will:
then

```
tpm2-recv
```

* Extracts the public part of the TPM Endorsement Key and the x509 certificate signed by the TPM manufacturer
* Generates a signing-only Attestation Key (`AK`) inside the TPM and exports the public key (`ak.pub`)
* Uses the TPM to sign a "quote" of the requested PCRs plus the nonce with the Attestation Key
* Extract the TPM event log and IMA event log, if they are available
* Bundle up all of the pieces into a tar file: `ek.crt`, `ek.pub`, `ak.pub`, `quote.sig`, `quote.pcr`, `quote.msg`, `eventlog` and the `nonce`
for each secret.

The Client then sends this quote file to the Server.
An optional second round-trip allows the client to prove possession
of the `EK` and `AK`, and this can be used for logging the client's
attestation status.

### Quote validation
When the Server receives the quote file from the client, it runs:
```
tpm2-attest verify quote.tgz $nonce
tpm2-attest verify quote.tar $nonce
```

With this command the server will:
Expand Down Expand Up @@ -127,7 +178,7 @@ unseal it, and has faith that the TPM will not unseal it if it has been
reset (to prevent attacks that reboot into untrusted firmware):

```
cat secret.txt | tpm2-attest seal quote.tgz > cipher.bin
cat secret.txt | tpm2-attest seal quote.tar > cipher.bin
```

With this command the Server will:
Expand All @@ -140,7 +191,7 @@ Note that there is a `verify-and-seal` that combines both the quote validation
and the sealing of the data to the attestation key in one step:

```
cat secret.txt | tpm2-attest seal quote.tgz $nonce > cipher.bin
cat secret.txt | tpm2-attest seal quote.tar $nonce > cipher.bin
```

### Secret unsealing
Expand Down Expand Up @@ -225,7 +276,9 @@ Google Cloud's ShieldedVM service enables vTPM for the guests, although
it does not provide an EK in the NVRAM either.
The key can be retrieved out of band [with these instructions](https://cloud.google.com/security/shielded-cloud/retrieving-endorsement-key),
or the public component can be read from the `tpm2 createek` command
described above.
described above. Using the [Google Cloud ShieldedVM lookup service](https://cloud.google.com/security/shielded-cloud/retrieving-endorsement-key)
can function as an EKcert as far as establishing trust in an
instance's vTPM.

### Remote attestation demo

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ systems, etc.
* [Source code](https://github.com/osresearch/safeboot)
* [`safeboot` subcommands](safeboot.md)
* [`tpm2-attest` subcommands](tpm2-attest.md)
* [`attest-enroll`](attest-enroll.md)

## Status

Expand Down
Loading