Using a TPM or an HSM
Sometimes it is desirable to use a Trusted Platform Module (TPM) or a Hardware Security Module (HSM) to further secure the chain of trust at a site.
Without a TPM or HSM the site is unsealed, ie, the seal key to the Strongbox vault is made available to the site, using the following procedure.
Pre-requesite: When the site is initially set up the following procedure is followed to enable unseal at a later time if the entire site is restarted.
-
A unique key is created by the site, ie a seal key. This key is never stored or communicated outside the site in clear text.
-
A token is obtained from the Control Tower. This token gives limited API access to the Control Tower for storing and fetching an unseal secret.
-
The token is encrypted using a site specific key-pair (the Site Key), where the private part never leaves the site.
-
The now encrypted token is broken into a number of pieces equal to the number of controllers at the site. This is done using the Shamir secret sharing algorithm. A key property of the Shamir algorithm is that the whole can be recovered from the parts if, and only if, a majority of shares are present.
-
Each controller host at the site stores a unique Shamir share of the token.
-
The seal key of the site is encrypted with the public key of the Site Key used to encrypt the token above. The result is stored at the Control Tower for later retrieval by the site, if needed. Since it is encrypted using the public key, it can only be decrypted using the private key, which is only known to the site.
When a site is restarted it is initially sealed, ie, the keys to decrypt the secret store is unknown to the site. In order to unseal the site the following procedure is used.
-
Each controller host at the site has a piece of a token that can be used to request unseal from the Control Tower. The full token is initially split into a number shares using the Shamir Secret Sharing algorithm and distributed to all controller hosts, as described above. The token can later be recovered from the shares provided that a majority of shares are present.
When a site is started each host attempts to gather shares from the other controller hosts at the site. When enough shares are present the token is recovered.
-
The recovered token is de-crypted using the Site Key and a clear text version of the token is obtained.
-
The clear text token is used to request the previously stored encrypted seal key from the Control Tower.
-
The encrypted seal key is decrypted using the private key of the Site Key.
This is a fairly secure scheme if the site consists of multiple controller hosts since a majority of hosts needs to be present in order to recover the unseal token. If a single controller host is copied, or compromised, then access cannot be obtained through that host. However, if the site consists of a single host, then it would be desirable to have some additional security mechanism.
The problem is even worse if a site is allowed to perform automatic
unseal without contacting the Control Tower. This can be allowed by
setting the allow-local-unseal
setting to true
. It may be
desirable if a site is expected to be off-line for long stretches of
time.
When allow-local-unseal
is true
the procedure is a bit different
from above. When the system is initiated it encrypts the seal key
directly instead of encrypting the access token to the Control
Tower. The encrypted seal key is then split using the Shamir Secret
Sharing algorithm, and each share is stored on a controller host.
This works well when a site consists of multiple controller hosts, but works less well for single host sites since the seal key is then available directly on the host.
One way of improving the situation is to use a TPM or HSM to protect
the share stored by each host. On multi-host sites there will
be multiple shares, each protected by a TPM/HSM. On single host sites
the unseal token (or seal key in the case of allow-local-unseal
) will
be protected by a TPM/HSM, vastly improving the situation.
Accessing TPM/HSM using Parsec
In order for the Edge Enforcer to access a local TPM or HSM it relies on a system service called Parsec (https://github.com/parallaxsecond/parsec). This becomes the integration point between the Edge Enforcer and any local TPM or HSM.
The system assumes that a Parsec service is properly configured
running. It further assumes that the service is configured to create
the parsec socket at /run/parsec/parsec.sock
and that the service is
configured for auth_type = UnixPeerCredentials
.
Parsec provides a common abstraction layer for both TPMs and HSMs and allows the local unseal token to be secured by a key stored in a TPM or a HSM .
It is possible to test the setup even without an actual TPM or HSM by letting the Parsec service use a software based emulator of a TPM.
Note that the Parsec service must be running before a host is added since the root of trust configuration is part of the very early setup of a host.
Parsec Service for Testing
Setting up a Parsec service for testing is fairly straightforward. However, observe that this must be done before adding a host to the system.
Download the Parsec quickstart package from Github and unpack. Note that the quickstart package has dependencies on specific glibc versions. If they do not match your system you may have to build locally (see Parsec Service with TPM below).
wget https://github.com/parallaxsecond/parsec/releases/download/1.3.0/quickstart-1.3.0-linux-x86_64.tar.gz
tar xfz quickstart-1.3.0-linux-x86_64.tar.gz
The package contains a config file for testing. Modify the parsec socket location to the production location.
cd quickstart-1.3.0-linux-x86_64.tar.gz/quickstart
sed -i -e 's|"./parsec.sock"|"/run/parsec/parsec.sock"|' config.toml
The config.toml will look like this:
[core_settings]
log_level = "info"
allow_root = true
[listener]
listener_type = "DomainSocket"
timeout = 200 # in milliseconds
socket_path = "/run/parsec/parsec.sock"
[authenticator]
auth_type = "UnixPeerCredentials"
[[key_manager]]
name = "sqlite-manager"
manager_type = "SQLite"
sqlite_db_path = "./sqlite-key-info-manager.sqlite3"
[[provider]]
provider_type = "MbedCrypto"
key_info_manager = "sqlite-manager"
Some directories also needs to be created, if not present.
sudo mkdir /etc/parsec
sudo mkdir /run/parsec
sudo chown docker /run/parsec
sudo chmod 755 /run/parsec
sudo cp config.toml /etc/parsec
sudo mv ../bin/parsec /usr/bin/parsec
The Parsec service may now be started manually using the following command:
sudo /usr/bin/parsec --config /etc/parsec/config.toml
It should result in a a printout like this:
[INFO parsec] Parsec started. Configuring the service...
[INFO parsec_service::key_info_managers::sqlite_manager] SQLiteKeyInfoManager - Found 0 key info mapping records
[INFO parsec_service::utils::service_builder] Creating a Mbed Crypto Provider.
[WARN parsec_service::front::domain_socket] Removing the existing socket file at /run/parsec/parsec.sock.
[INFO parsec] Parsec is ready.
As an alternative if systemd
is used a service specification can
be created like this (as root):
cat > /etc/systemd/system/parsec.service <<EOF
[Unit]
Description=Parsec Service
Documentation=https://parallaxsecond.github.io/parsec-book/parsec_service/install_parsec_linux.html
[Service]
WorkingDirectory=/home/docker
ExecStart=/usr/bin/parsec --config /etc/parsec/config.toml
User=docker
Group=docker
[Install]
WantedBy=multi-user.target
EOF
Then enabled and started like this:
systemctl --no-pager daemon-reload
systemctl --no-pager enable parsec.service
systemctl --no-pager start parsec.service
When a host is added, that has a Parsec service running, there should
be an INFO
level log message saying Using Parsec service to protect host.
. This indicates that the Shamir share assigned to the host
has been encrypted using the local Parsec service.
Parsec Service with TPM
In a production environment a real TPM or HSM should be used. In this case the Parsec service needs to be compiled with the proper drivers for the hardware that should be used. See https://parallaxsecond.github.io/parsec-book/getting_started/linux_x86.html and https://parallaxsecond.github.io/parsec-book/parsec_service/install_parsec_linux.html
For example, if an onboard TPM is used Parsec can be prepared as follows:
Install some tools
sudo apt install llvm-dev libclang-dev clang cmake rustc
Clone the Parsec service repo:
git clone --branch 1.3.0 https://github.com/parallaxsecond/parsec.git
Build the source code with with the tpm provider enabled
cd parsec
cargo build --features "tpm-provider,direct-authenticator,unix-peer-credentials-authenticator"
Create a config file where the provider type is set to Tpm
and
tcti
points to "device:/dev/tpmrm0"
.
cat > /etc/parsec/config.toml <<EOF
[core_settings]
log_level = "info"
allow_root = true
[listener]
listener_type = "DomainSocket"
timeout = 3000 # in milliseconds
socket_path = "/run/parsec/parsec.sock"
[authenticator]
auth_type = "UnixPeerCredentials"
[[key_manager]]
name = "sqlite-manager"
manager_type = "SQLite"
sqlite_db_path = "/etc/parsec/sqlite-key-info-manager.sqlite3"
[[provider]]
provider_type = "Tpm"
key_info_manager = "sqlite-manager"
tcti = "device:/dev/tpmrm0"
EOF
The service may now be started in the same way as above, ie
create some directories, if not present, and copy the parsec
executable to /usr/bin/parsec
.
sudo mkdir /etc/parsec
sudo mkdir /run/parsec
sudo chown docker /run/parsec
sudo chmod 755 /run/parsec
sudo cp config.toml /etc/parsec
sudo mv target/release/parsec /usr/bin/parsec
The Parsec service may now be started manually using the following command:
sudo /usr/bin/parsec --config /etc/parsec/config.toml
It should result in a a printout like this:
[INFO parsec] Parsec started. Configuring the service...
[INFO parsec_service::key_info_managers::on_disk_manager] Found 0 mapping files
[INFO parsec_service::utils::service_builder] Creating a TPM Provider.
[INFO parsec_service::providers::tpm] Checking for ciphers supported by the TPM.
[INFO tss_esapi::context] Closing context.
[INFO tss_esapi::context] Context closed.
[INFO parsec] Parsec is ready.
As an alternative if systemd
is used a service specification can
be created like this (as root):
cat > /etc/systemd/system/parsec.service <<EOF
[Unit]
Description=Parsec Service
Documentation=https://parallaxsecond.github.io/parsec-book/parsec_service/install_parsec_linux.html
[Service]
WorkingDirectory=/home/docker
ExecStart=/usr/bin/parsec --config /etc/parsec/config.toml
User=docker
Group=docker
[Install]
WantedBy=multi-user.target
EOF
Then enabled and started like this:
systemctl --no-pager daemon-reload
systemctl --no-pager enable parsec.service
systemctl --no-pager start parsec.service
When a host is added, that has a Parsec service running, there should
be an INFO
level log message saying Using Parsec service to protect host.
. This indicates that the Shamir share assigned to the host
has been encrypted using the local Parsec service.