Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full Virtual TPM for VMs and Containers #4071

Merged
merged 9 commits into from
Sep 3, 2024
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
68 changes: 68 additions & 0 deletions docs/PTPM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# PTPM Server

*(warning! to be deprecated)* Proxy TPM (formerly VTPM), located in `pkg/vtpm/ptpm`, is a server listening on port `8877` in EVE, exposing limited functionality of the host's TPM to the clients. PTPM allows clients to execute [tpm2-tools](https://github.com/tpm2-software/tpm2-tools) binaries from a list of hardcoded options (listed below).

## Packet Structure

PTPM server accepts `EveTPMRequest` commands and outputs `EveTPMResponse`. Both structures are defined in probuf format in a file located in `pkg/vtpm/ptpm/proto/api.proto`.

## Communicating with PTPM

You can communicate with PTPM by consuming the probuf definitions in your own client or using external tools like `protoc` to generate raw protobuf commands to send over the network.

Another way is using `eve_run` client from [eve-tools](https://github.com/lf-edge/eve-tools). Using `eve_run` is easy because it already has a predefined command-file table and can map a command's required files to `EveTPMRequest`. To use this client, simply build it (or follow the [installation](https://github.com/lf-edge/eve-tools/blob/master/INSTALL.md) instructions if you prefer otherwise):

```bash
sudo apt-get install -y libprotobuf-dev libprotoc-dev protobuf-compiler cmake g++ libssl-dev libcurl4-openssl-dev uuid-dev
cd ~
git clone https://github.com/lf-edge/eve-tools.git
cd eve-tools/eve-tools
make
```

To run `eve_run` without installation make sure the `libevetools.so` is accessible:

```bash
LD_LIBRARY_PATH=~/eve-tools/eve-tools
export LD_LIBRARY_PATH
```

### Commands

Currently execution of the following commands are allowed:
| No |Command |
|--|--|
| 1 |tpm2_getcap |
| 2 |tpm2_readpublic |
| 3 |tpm2_startauthsession |
| 4 |tpm2_policysecret |
| 5 |tpm2_activatecredential |
| 6 |tpm2_flushcontext |
| 7 |tpm2_startauthsession |
| 8 |tpm2_policysecret |
| 9 |tpm2_import |
| 10 |tpm2_flushcontext |
| 11 |tpm2_load |
| 12 |tpm2_hmac |
| 13 |tpm2_hash |
| 14 |tpm2_sign |
| 15 |tpm2_verifysignature |

To get the details about each command, please consult the related [documentation](https://github.com/tpm2-software/tpm2-tools/tree/master/man). As an example, signing a message using TPM through `eve_run` goes as follows:

```bash
#Using well-known AIK handle 0x81000003 (RSA cipher and RSASSA signing scheme, with SHA256)
echo "secret data" > data_to_be_signed

# Preparing ticket file to pass for signing
eve_run tpm2_hash -Q -C e -t ticket.bin -g sha256 -o digest.bin data_to_be_signed

# Performing signing...
eve_run tpm2_sign -Q -c 0x81000003 -g sha256 -s rsassa -o data.out.sign -t ticket.bin -f plain data_to_be_signed

# Reading public key for using it in openssl
eve_run tpm2_readpublic -Q -c 0x81000003 -o ak.pub -f pem

# Verifying signature using openssl
openssl dgst -verify ak.pub -keyform pem -sha256 -signature data.out.sign data_to_be_signed
```
81 changes: 28 additions & 53 deletions docs/VTPM.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,43 @@
# VTPM Server
# VTPM

VTPM (located in `pkg/vtpm`) is a server listening on port `8877` in EVE, exposing limited functionality of the TPM to the clients. VTPM allows clients to execute [tpm2-tools](https://github.com/tpm2-software/tpm2-tools) binaries from a list of [hardcoded options](https://github.com/lf-edge/eve/blob/883547fe7978550a30e4389aac24d562d1dae105/pkg/vtpm/src/server.cpp#L58).
*(if you're looking for old VTPM documents, please refer to [PTPM](docs/PTPM.md))*

## Packet Structure
Virtual TPM container integrates SWTPM with QEMU, in order to emulate a full Virtual TPM 2.0 (1.2 not supported) for running VMs and bare-metal container. It creates a SWTPM instance per VM. The SWTPM instance is configured to use a Unix Domain Socket as a communication line, by passing the socket path to the QEMU virtual TPM configuration, QEMU automatically creates a virtual TPM device for the VM which is accessible like a normal TPM under `/dev/tpm*`.

VTPM server accepts `EveTPMRequest` commands and outputs `EveTPMResponse`. Both structures are defined in probuf format in a file located in `pkg/vtpm/proto/vtpm_api.proto`.
VTPM configures SWTPM to saves and loads TPM state on/from the `/persist/swtpm/tpm-state-[VM-UUID]`, so at the next VM boot all the TPM keys, TPM NVRAM data, etc. are present in the virtual TPM.

## Communicating with VTPM
## VTPM Security Guarantees

You can communicate with VTPM by consuming the probuf definitions in your own client or using external tools like `protoc` to generate raw protobuf commands to send over the network.
VTPM offers security guarantee against the following scenarios:

Another way is using `eve_run` client from [eve-tools](https://github.com/lf-edge/eve-tools). Using `eve_run` is easy because it already has a predefined command-file table and can map a command's required files to `EveTPMRequest`. To use this client, simply build it (or follow the [installation](https://github.com/lf-edge/eve-tools/blob/master/INSTALL.md) instructions if you prefer otherwise):
1. Virtual TPM data confidentiality
2. Virtual TPM uniqueness (cloning detection)

```bash
sudo apt-get install -y libprotobuf-dev libprotoc-dev protobuf-compiler cmake g++ libssl-dev libcurl4-openssl-dev uuid-dev
cd ~
git clone https://github.com/lf-edge/eve-tools.git
cd eve-tools/eve-tools
make
```

To run `eve_run` without installation make sure the `libevetools.so` is accessible:

```bash
LD_LIBRARY_PATH=~/eve-tools/eve-tools
export LD_LIBRARY_PATH
```

### Commands
The first scenario is guaranteed by state encryption. SWTPM is configured to encrypt each VM/Container's virtual TPM state data using a 256-bit AES key, this key is stored in the HWTPM with a PCR policy and is only accessible to EVE. The access to this key is protected using the same PCR policy as the vault key (measured boot using PCR values), as result any tampering with EVE such as cloning or a persistent backdoor will result in unavailability of the VTPM encryption key. In case of tampering with the system, VTPM will not be made available to VM/Container, and VM/Container should consider such case as evidence of tampering with EVE.

Currently execution of the following commands are allowed:
| No |Command |
|--|--|
| 1 |tpm2_getcap |
| 2 |tpm2_readpublic |
| 3 |tpm2_startauthsession |
| 4 |tpm2_policysecret |
| 5 |tpm2_activatecredential |
| 6 |tpm2_flushcontext |
| 7 |tpm2_startauthsession |
| 8 |tpm2_policysecret |
| 9 |tpm2_import |
| 10 |tpm2_flushcontext |
| 11 |tpm2_load |
| 12 |tpm2_hmac |
| 13 |tpm2_hash |
| 14 |tpm2_sign |
| 15 |tpm2_verifysignature |
The second guarantee is secured by signing the VTPM's Endorsement Key (EK) using a signing key (HWTPM AIK) that is stored in HWTPM. EVE utilizes TPM to lay out a process that is true to our zero-trust promises and allows a remote party to establish trust in AIK and prove its security attributes (for example AIK resides inside HWTPM and is not import/exportable). This is how the chain of trust if established:

To get the details about each command, please consult the related [documentation](https://github.com/tpm2-software/tpm2-tools/tree/master/man). As an example, signing a message using TPM through `eve_run` goes as follows:
1- HWTPM's Endorsement Key is a special purpose TPM-resident RSA key that is never visible outside the TPM. EK can be used for **decryption only** (EK cannot be used to produce a digital signature). EK is generated base on a **unique per TPM** seed, so it is deterministic and it's creation results in same key every single time (even after TPM is cleared). Trust in EK is established using a certificate issue by the OEM, either through a EK certificate or a Platform
certificates [ADD REF, TPM 2.0 SPEC]. EVE is TPM-OEM agnostic and it can only provide the VM/Containers with the HWTPM EK, verifying and trusting it is outside of scope of EVE and should be done by the attestor (VM/Container running on EVE or ideally a remote trusted server that wants to verify the SWTPM security guarantees).

```bash
#Using well-known AIK handle 0x81000003 (RSA cipher and RSASSA signing scheme, with SHA256)
echo "secret data" > data_to_be_signed
2- EVE generates a Attestation Identity Key (AIK) inside the HWTPM. AIK is a signing key and it is used to sign the VTPM's EK. This signature and subsequent attestation process (described below) proofs that the VTPM is running on a TPM with a specific HWTPM EK and running a cloned VTPM on a another TPM detectable. AIK comes with security attributes like FixedTPM, FixedParent and SensitiveDataOrigin (meaning the key is generated on the TPM and duplication/exporting/importing it is not possible). Attestor can make sure these values hold before trusting the AIK and verifying the VTPM's EK signature. This is done by first making sure that calculated AIK public blob's digest matches the provided digest and second the attributes hold by decoding and examining the AIK public blob.

# Preparing ticket file to pass for signing
eve_run tpm2_hash -Q -C e -t ticket.bin -g sha256 -o digest.bin data_to_be_signed
3- The attestor uses the AIK's public blob digest (AKA name which contains the security attributes as mentioned above) and HTWPM EK to encrypt a credential (AKA nonce). Creating the credential links the AIK to HWTPM EK. This is done by generating a seed, encrypting it using HWTPM EK and running the AIK's digest and the seed through a KDF to create an asymmetric key. The final asymmetric key is used to encrypt the credential. Please note that this operation doesn't require a TPM, so it can happen on a trusted remote server with no TPM.

# Performing signing...
eve_run tpm2_sign -Q -c 0x81000003 -g sha256 -s rsassa -o data.out.sign -t ticket.bin -f plain data_to_be_signed
4- At the final stage, the credential is passed to EVE. EVE uses HWTPM to decrypt the credentials. Per TCG TPM 2.0 specifications, this operation only succeeds if HWTPM has the private part of the matching EK and contains an AIK with matching attributes inside. After successfully decrypting the credentials, the plain text is passed to the attestor as a proof. If this operation fails, the attestor should not trust the VTPM and should halt any operation that relies on a TPM.

# Reading public key for using it in openssl
eve_run tpm2_readpublic -Q -c 0x81000003 -o ak.pub -f pem
```mermaid
sequenceDiagram
EVE ->> Attestor: HWTPM EK Pub, HWTPM AIK Pub, HTWPM AIK Name
Attestor -->> Attestor: Verify HWTPM AIK security attributes
Attestor -->> Attestor: Make credentials : MC(HWTPM EK Pub, HWTPM AIK Name, Random nonce)
Attestor ->> EVE: Encrypted credential
EVE -->> EVE: Decrypt the cred using HWTPM EK and AIK : TpmDec(EK, AIK, cred)
EVE ->> Attestor: Decrypted credential
Attestor ->> EVE: Ask for VTPM EK Sig, if Random nonce == Decrypted credential
EVE ->> Attestor: Sig(HWTPM AIK Pub, VTPM EK Pub)
Attestor->> VM running on EVE: Trust VTPM, if Sig is valid

# Verifying signature using openssl
openssl dgst -verify ak.pub -keyform pem -sha256 -signature data.out.sign data_to_be_signed
```

For a working example of this operation, please check `/pkg/vtpm/vtpm-attest/`. Please note that if a hardware TPM is not available to EVE, these security guarantees are nulled and the virtual TPM state is stored unencrypted.
2 changes: 1 addition & 1 deletion pkg/apparmor/aa-init.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
#

# profiles are located in /etc/apparmor.d, loop over and load them.
for profile in /etc/apparmor.d/*; do
if [ -f "$profile" ]; then
Expand Down
14 changes: 14 additions & 0 deletions pkg/apparmor/profiles/usr.bin.ptpm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) 2023 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0

#include <tunables/global>

@{exec_path} = /usr/bin/ptpm
profile ptpm @{exec_path} {
#include <abstractions/base>

# allow necessary access for operations
/home/{,*,**} rw,
/usr/bin/tpm2 rPx,
network inet stream,
}
19 changes: 19 additions & 0 deletions pkg/apparmor/profiles/usr.bin.swtpm
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2024 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0

#include <tunables/global>

@{exec_path} = /usr/bin/swtpm
profile swtpm @{exec_path} {
#include <abstractions/base>

# allow necessary access for operations
/usr/bin/swtpm rm,

# to rw socket, log, etc files.
owner /run/swtpm/{,*,**} rwk,

# to save/load tpm state for vms.
owner /persist/swtpm/{,*,**} rwk,

}
2 changes: 1 addition & 1 deletion pkg/apparmor/profiles/usr.bin.tpm2
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ profile tpm2 @{exec_path} {

# allow necessary access for operations
/usr/bin/tpm2 rm,
/jail/{,*,**} rw,
/home/{,*,**} rw,

# allow access to tpm device
/dev/tpm0 rw,
Expand Down
22 changes: 17 additions & 5 deletions pkg/apparmor/profiles/usr.bin.vtpm
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
# Copyright (c) 2023 Zededa, Inc.
# Copyright (c) 2024 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0
eriknordmark marked this conversation as resolved.
Show resolved Hide resolved

#include <tunables/global>

@{exec_path} = /usr/bin/vtpm_server
@{exec_path} = /usr/bin/vtpm
profile vtpm @{exec_path} {
#include <abstractions/base>

# allow necessary access for operations
/jail/{,*,**} rw,
/usr/bin/tpm2 rPx,
network inet stream,
owner /usr/bin/vtpm rm,
owner /home/{,*,**} rw,

# writes temporary tpm-state encryption key here.
owner /run/swtpm/{,*,**} rw,

# crates the per-vm tpm-state dir here.
owner /persist/swtpm/{,*,**} rw,

# access to host tpm to unseal the encryption key.
/dev/tpm0 rw,
/dev/tpmrm0 rw,

# allow executing swtpm
/usr/bin/swtpm Px,
}
59 changes: 56 additions & 3 deletions pkg/pillar/hypervisor/hypervisor.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// Copyright (c) 2017-2020 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

// Package hypervisor contains the Hypervisor interface and the implementation of the
// interface for different hypervisors.
package hypervisor

import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/lf-edge/eve/pkg/pillar/base"
"github.com/lf-edge/eve/pkg/pillar/types"
Expand Down Expand Up @@ -60,10 +65,10 @@ var hypervisorPriority = []string{
// GetHypervisor returns a particular hypervisor implementation
func GetHypervisor(hint string) (Hypervisor, error) {
if _, found := knownHypervisors[hint]; !found {
return nil, fmt.Errorf("Unknown hypervisor %s", hint)
} else {
return knownHypervisors[hint].constructor(), nil
return nil, fmt.Errorf("unknown hypervisor %s", hint)
}

return knownHypervisors[hint].constructor(), nil
}

func bootTimeHypervisorWithHVFilePath(hvFilePath string) Hypervisor {
Expand Down Expand Up @@ -238,3 +243,51 @@ func PCISameControllerGeneric(id1 string, id2 string) bool {

return tag1 == tag2
}

func launchSwtpm(id string, timeoutSeconds uint) (string, error) {
conn, err := net.Dial("unix", types.VtpmdCtrlSocket)
if err != nil {
return "", fmt.Errorf("failed to connect to vtpmd control socket: %w", err)
}
defer conn.Close()

pidPath := fmt.Sprintf(types.SwtpmPidPath, id)
sockPath := fmt.Sprintf(types.SwtpmCtrlSocketPath, id)

// Send the id to the swtpm control socket,ask it to launch swtpm instance.
_, err = conn.Write([]byte(fmt.Sprintf("%s\n", id)))
if err != nil {
return "", fmt.Errorf("failed to write to vtpmd control socket: %w", err)
}

// loop and wait for swtpm to launch
startTime := time.Now()
for {
if time.Since(startTime).Seconds() >= float64(timeoutSeconds) {
return "", fmt.Errorf("timeout reached while waiting for swtpm to launch")
}

// check if swtpm is running by checking the pid file
if fileutils.FileExists(nil, pidPath) {
break
}

time.Sleep(1 * time.Second)
}

// read the pid file and make sure the process is running
content, err := os.ReadFile(pidPath)
if err != nil {
return "", fmt.Errorf("failed to read swtpm pid file: %w", err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(content)))
if err != nil {
return "", fmt.Errorf("failed to parse swtpm pid: %w", err)
}
_, err = os.FindProcess(pid)
if err != nil {
return "", fmt.Errorf("swtpm process not found: %w", err)
}

return sockPath, nil
}
Loading
Loading