Host OS Setup¶
On this page, we will introduce how an Intel TDX-enabled host OS can be configured. We assume that proper hardware was selected and the hardware setup was done.
Enable Intel TDX in the Host OS¶
Currently, the following host OS distributions ship with the necessary versions of kernel/kvm, QEMU, and libvirt to be able to run TDs.
- CentOS Stream 10
- openSUSE Leap 16
- Red Hat Enterprise Linux (RHEL) 10.2
- SUSE Linux Enterprise Server (SLES) 16.0 QU0
- Ubuntu 26.04
Configure Kernel Command Line Parameters¶
The required kernel command line parameters to enable Intel TDX are not included by default and must be added manually. Follow these steps for your distribution:
-
Add the required kernel command line parameters for your distribution:
sudo grubby --update-kernel=ALL --args="nohibernate kvm_intel.tdx=1"sudo bash -c 'changed=0 for arg in "kvm_intel.tdx=1" "nohibernate"; do key=${arg%%=*} already_set="^GRUB_CMDLINE_LINUX=.*[ \"]${arg//./\\.}[ \"]" grep -qE "$already_set" /etc/default/grub && continue drop_old_key="s/(^|[ \"])${key//./\\.}(=[^ \"]*)?/\1/g" append_arg="s/(GRUB_CMDLINE_LINUX=\"[^\"]*)\"/\1 ${arg}\"/" sed -i -E "/^GRUB_CMDLINE_LINUX=/{ $drop_old_key; $append_arg }" /etc/default/grub changed=1 done [ $changed = 1 ] && grub2-mkconfig -o /boot/grub2/grub.cfg'sudo grubby --update-kernel=ALL --args="nohibernate kvm_intel.tdx=1"sudo bash -c 'changed=0 for arg in "kvm_intel.tdx=1" "nohibernate"; do key=${arg%%=*} already_set="^GRUB_CMDLINE_LINUX=.*[ \"]${arg//./\\.}[ \"]" grep -qE "$already_set" /etc/default/grub && continue drop_old_key="s/(^|[ \"])${key//./\\.}(=[^ \"]*)?/\1/g" append_arg="s/(GRUB_CMDLINE_LINUX=\"[^\"]*)\"/\1 ${arg}\"/" sed -i -E "/^GRUB_CMDLINE_LINUX=/{ $drop_old_key; $append_arg }" /etc/default/grub changed=1 done [ $changed = 1 ] && grub2-mkconfig -o /boot/grub2/grub.cfg'sudo bash -c 'changed=0 for arg in "kvm_intel.tdx=1" "nohibernate"; do key=${arg%%=*} already_set="^GRUB_CMDLINE_LINUX=.*[ \"]${arg//./\\.}[ \"]" grep -qE "$already_set" /etc/default/grub && continue drop_old_key="s/(^|[ \"])${key//./\\.}(=[^ \"]*)?/\1/g" append_arg="s/(GRUB_CMDLINE_LINUX=\"[^\"]*)\"/\1 ${arg}\"/" sed -i -E "/^GRUB_CMDLINE_LINUX=/{ $drop_old_key; $append_arg }" /etc/default/grub changed=1 done [ $changed = 1 ] && update-grub' -
Reboot the system for the kernel command line changes to take effect:
sudo rebootsudo rebootsudo rebootsudo rebootsudo reboot -
If not done before, reboot the system into the BIOS setup menu and configure the necessary BIOS settings for Intel TDX-enabling. You can use the checks described in the next section to determine if your BIOS is already configured appropriately.
Check Intel TDX enablement¶
To check the status of your Intel TDX configuration, you can manually execute the following commands:
-
Check whether Intel TDX Module is initialized. The expected output contains
tdx: TDX module initialized.sudo dmesg | grep -i tdx -
As a prerequisite for the following commands, install the MSR Tools package and load the MSR module.
sudo dnf config-manager --set-enabled crb sudo dnf install -y epel-release sudo dnf install -y msr-tools sudo modprobe msrsudo zypper addrepo https://download.opensuse.org/repositories/openSUSE:Backports:SLE-15-SP6/standard/openSUSE:Backports:SLE-15-SP6.repo sudo zypper refresh sudo modprobe msr sudo zypper install -y msr-tools sudo zypper mr -d openSUSE_Backports_SLE-15-SP6sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm sudo dnf install -y msr-tools sudo modprobe msrsudo zypper addrepo https://download.opensuse.org/repositories/openSUSE:Backports:SLE-15-SP6/standard/openSUSE:Backports:SLE-15-SP6.repo sudo zypper refresh sudo modprobe msr sudo zypper install -y msr-tools sudo zypper mr -d openSUSE_Backports_SLE-15-SP6sudo apt install -y msr-tools sudo modprobe msr -
Check whether Intel TME is enabled. The expected output is
1.sudo rdmsr -f 1:1 0x982 -
Check the Intel SGX and MCHECK status. The expected output is
0.sudo rdmsr 0xa0 -
Check the Intel TDX status. The expected output is
1.sudo rdmsr -f 11:11 0x1401 -
Check the maximum number of Intel TME keys available for usage. The expected output depends on what is configured in the BIOS.
sudo rdmsr -f 50:36 0x981 | awk '{print strtonum("0x"$0)}' -
Check the number of activated Intel TME keys. The expected output depends on what is configured in the BIOS.
sudo rdmsr -f 31:0 0x87 | awk '{print strtonum("0x"$0)}' -
Check the number of activated Intel TDX keys. The expected output depends on what is configured in the BIOS.
sudo rdmsr -f 63:32 0x87 | awk '{print strtonum("0x"$0)}'
Set Up Quote Generation Service (QGS)¶
The main artifact used in a remote attestation flow is the TD Quote, which is generated on the Intel TDX hardware and then transferred to any other party/machine for verification. To generate a TD Quote, a TD first uses the hardware to generate a TD Report. This TD Report is then forwarded to an Intel SGX Architectural Enclave, called the TD Quoting Enclave (TDQE). This enclave takes the incoming TD Report, verifies that the TD Report was generated by a TD on the same platform, and then signs the TD Report with a signature key for which the trust is rooted in an Intel CA. More details can be found in the Intel® Trust Domain Extensions Data Center Attestation Primitives (Intel® TDX DCAP): Quote Generation Library and Quote Verification Library documentation.
The Quote Generation Service (QGS) is a service that runs in the host OS (or inside a dedicated VM) to host the TDQE. Note that the QGS cannot run on another machine, because the verification of the TD Report requires that the corresponding TD and the TDQE run on the same machine.
In the following, we describe how to install the QGS and how to configure it for your environment. Afterwards, we show how to restart the QGS so that the configuration changes take effect and how to check the QGS log to verify that the service is running as expected.
Install QGS¶
-
If not done during another component installation, set up the appropriate Intel SGX package repository for your distribution of choice:
sudo dnf install -y yum-utils wget tar sudo mkdir -p /opt/intel wget https://download.01.org/intel-sgx/latest/dcap-latest/linux/distro/centos-stream10/sgx_rpm_local_repo.tgz sudo tar -xvzf sgx_rpm_local_repo.tgz -C /opt/intel sudo yum-config-manager --add-repo file:///opt/intel/sgx_rpm_local_repo sudo wget https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key -O /opt/intel/intel-sgx.key sudo yum-config-manager --save --setopt=*sgx_rpm_local_repo.gpgkey=file:///opt/intel/intel-sgx.key # Set the priority of the local repo to 1 (highest) to avoid conflicts with other repos sudo yum-config-manager --save --setopt=*sgx_rpm_local_repo.priority=1sudo zypper install -y wget tar sudo mkdir -p /opt/intel sudo rpm --import https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key wget https://download.01.org/intel-sgx/sgx-dcap/1.27.1/linux/distro/suse16-server/sgx_rpm_local_repo.tgz sudo tar -xvzf sgx_rpm_local_repo.tgz -C /opt/intel sudo zypper addrepo /opt/intel/sgx_rpm_local_repo sgx_rpm_local_reposudo dnf install -y yum-utils wget tar sudo mkdir -p /opt/intel wget https://download.01.org/intel-sgx/latest/dcap-latest/linux/distro/rhel10.2-server/sgx_rpm_local_repo.tgz sudo tar -xvzf sgx_rpm_local_repo.tgz -C /opt/intel sudo yum-config-manager --add-repo file:///opt/intel/sgx_rpm_local_repo sudo wget https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key -O /opt/intel/intel-sgx.key sudo yum-config-manager --save --setopt=*sgx_rpm_local_repo.gpgkey=file:///opt/intel/intel-sgx.key # Set the priority of the local repo to 1 (highest) to avoid conflicts with other repos sudo yum-config-manager --save --setopt=*sgx_rpm_local_repo.priority=1sudo zypper install -y wget tar sudo mkdir -p /opt/intel sudo rpm --import https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key wget https://download.01.org/intel-sgx/sgx-dcap/1.27.1/linux/distro/suse16-server/sgx_rpm_local_repo.tgz sudo tar -xvzf sgx_rpm_local_repo.tgz -C /opt/intel sudo zypper addrepo /opt/intel/sgx_rpm_local_repo sgx_rpm_local_repoecho 'deb [signed-by=/etc/apt/keyrings/intel-sgx-keyring.asc arch=amd64]' \ 'https://download.01.org/intel-sgx/sgx_repo/ubuntu resolute main' \ | sudo tee /etc/apt/sources.list.d/intel-sgx.list curl -fsSLO https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key sudo mkdir -p /etc/apt/keyrings sudo mv intel-sgx-deb.key /etc/apt/keyrings/intel-sgx-keyring.asc sudo apt-get updateecho 'deb [signed-by=/etc/apt/keyrings/intel-sgx-keyring.asc arch=amd64]' \ 'file:///opt/intel/sgx_debian_local_repo resolute main' \ | sudo tee /etc/apt/sources.list.d/intel-sgx-local.list curl -fsSLO https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key sudo mkdir -p /etc/apt/keyrings /opt/intel sudo mv intel-sgx-deb.key /etc/apt/keyrings/intel-sgx-keyring.asc curl -fsSLO https://download.01.org/intel-sgx/latest/dcap-latest/linux/distro/ubuntu26.04-server/sgx_debian_local_repo.tgz sudo tar -xvzf sgx_debian_local_repo.tgz -C /opt/intel sudo apt-get update -
Install the QGS with the following command, which will also install the necessary prerequisites:
sudo dnf install -y tdx-qgssudo zypper install -y tdx-qgssudo dnf install -y tdx-qgssudo zypper install -y tdx-qgssudo apt install -y tdx-qgs
Configure QGS¶
The QGS is not configured through a single file. Depending on what you want to change, you have to adjust one of the following two configurations:
- QGS Daemon Configuration: behavior of the QGS daemon, e.g., the transport protocol that TDs use to reach the QGS, the number of worker threads, or the log level.
- PCK Certificate Retrieval Configuration: behavior of PCK Certificates retrieval, e.g., the address of the collateral caching service (e.g., PCCS) or Intel PCS, the lifetime of the local certificate cache, or the number of retries after a failed request.
The following two subsections explain these configurations in detail.
QGS Daemon Configuration¶
The QGS daemon can be configured in two ways: via a configuration file or via command line arguments. Both ways are described in the following subsections.
Configuration Sources Precedence
For every setting that is configured via configuration file and via command line argument, the command line argument takes precedence.
Configuration File Based Configuration¶
On start, the QGS reads its configuration file. The following options can be set in this file:
| Option | Description |
port = <port> |
The vsock port that the QGS listens on. Valid values are 0 to 65535. This setting is commented out by default, with 4050 as the suggested value. As long as it stays commented out, the QGS listens on the Unix domain socket /var/run/tdx-qgs/qgs.socket instead. |
number_threads = <count> |
The number of worker threads of the QGS. Valid values are 1 to 255. |
To change the configuration file of the QGS, follow these steps:
-
Open the configuration file of the QGS (
/etc/qgs.conf) in an editor of your choice. For example:sudo vim /etc/qgs.conf -
Add or adjust settings using the configuration file options presented above. A setting only takes effect if its line is not commented out.
-
For the change to take effect, restart the QGS.
Command Line Based Configuration¶
On start, the QGS evaluates its command line arguments, which take precedence over settings in its configuration file. The following options can be set as command line arguments:
| Option | Description |
-p=<port> |
The vsock port that the QGS listens on. Valid values are 0 to 65535. As long as this argument is not used, the QGS listens on the Unix domain socket /var/run/tdx-qgs/qgs.socket instead. |
-n=<count> |
The number of worker threads of the QGS. Valid values are 1 to 255. |
-l=<level> |
Log level, exactly one of error, warn, info, or debug. Each level includes all messages of the levels above it in the list, so error is the most restrictive value and debug logs everything. |
--no-daemon |
Run in the foreground and write log messages to standard output/standard error instead of the system log. Without this option the QGS runs as a background daemon and logs through syslog(3) under the user facility with the identifier qgsd. On a systemd host, view the log messages with journalctl -t qgsd. Otherwise, view the messages in the syslog file for the user facility (typically syslog or /var/log/messages). |
To change the command line of the QGS, follow these steps:
-
Open the override file of the service:
sudo systemctl edit qgsd -
Change the
ExecStart=entry of the service to add or adjust the command line options presented above. An emptyExecStart=is required to reset the original command before a new one is set. For example, the log level can be set todebugwith:[Service] ExecStart= ExecStart=/opt/intel/tdx-qgs/qgs -l=debug -
For the change to take effect, restart the QGS.
How to find out the path of the QGS binary?
The path /opt/intel/tdx-qgs/qgs is the one used by the Intel packages installed above.
If you use a different installation, look up the ExecStart line of the shipped unit file before you override it:
grep '^ExecStart=' "$(systemctl show --property=FragmentPath --value qgsd)"
The FragmentPath property always refers to the shipped unit file, so the output of this command is not affected by an override that you or the installation might have created before.
How to undo an override of the qgsd service?
All overrides that you create with systemctl edit qgsd are stored in the drop-in file /etc/systemd/system/qgsd.service.d/override.conf.
Steps to remove the override:
-
Delete the override file and make systemd reload its unit configuration:
sudo rm -f /etc/systemd/system/qgsd.service.d/override.conf sudo systemctl daemon-reloadWarning
Do not use
systemctl revert qgsdinstead. That command removes the wholeqgsd.service.d/directory with all the files it contains, including thesocket.confthat the installation of the QGS created to configure the runtime directory and the permissions of the Unix domain socket. -
For the change to take effect, restart the QGS.
PCK Certificate Retrieval Configuration¶
To generate a TD Quote, the QGS needs the PCK Certificate of the platform at the platform's current TCB level. In short, the QGS fetches the PCK Certificate using QCNL, which is why the settings below configure the QCNL. For additional details, see the "Additional Background" box below. On successful TD Quote generation, the QGS embeds the PCK Certificate in the TD Quote, enabling verifiers to validate the attestation chain back to Intel.
Additional Background
Retrieving the PCK Certificate involves several components that QGS loads in-process at runtime, each shipping in its own package:
- TDQE:
- Derives an asymmetric attestation key on demand, either when the QGS explicitly requests an initialization or lazily on the first TD Quote request.
The attestation key is cached and re-used as long as CPUSVN, TDQE ISVSVN, and PCE ISVSVN stay the same since the key was last generated; otherwise, the key is re-generated.
- For more details about the key derivation, see Section 3.5.2 of the Intel SGX ECDSA QuoteLib Reference (DCAP API).
- The attestation key is cached and re-used as long as CPUSVN, TDQE ISVSVN, and PCE ISVSVN stay the same since the key was last generated; otherwise, the key is re-generated.
- Submits a QE Report, which carries a hash of the attestation key's public part as report data, to the PCE for certification.
- Signs TD Quotes using the private part of the attestation key.
- Derives an asymmetric attestation key on demand, either when the QGS explicitly requests an initialization or lazily on the first TD Quote request.
The attestation key is cached and re-used as long as CPUSVN, TDQE ISVSVN, and PCE ISVSVN stay the same since the key was last generated; otherwise, the key is re-generated.
- PCE:
- Derives the private part of the platform's PCK and signs the TDQE's QE Report with it, certifying the attestation key against the platform's CPUSVN and PCE ISVSVN. The PCE is the only enclave allowed to derive a PCK.
- QPL:
- Receives a PCK Certificate request from the QGS for a certain CPUSVN and PCE ISVSVN.
- Delegates the request to the QCNL.
- QCNL:
- Receives a PCK Certificate request from the QPL for a certain CPUSVN and PCE ISVSVN.
- Fetches the PCK Certificate, which contains the PCK public key for the given CPUSVN and PCE ISVSVN, from a collateral caching service (e.g., PCCS) or Intel PCS, and handles the local caching described below.
The QCNL does not have to contact a collateral caching service (e.g., PCCS) or Intel PCS for every TD Quote request. Instead, it can keep a local cache on the host storing each PCK Certificate that it has retrieved, and only send a request if this cache holds no valid PCK Certificate.
The settings described in this section configure the QCNL, and thus determine where the QGS gets PCK Certificates from, whether the PCK Certificates are cached, and how the PCK Certificates are cached. The settings are stored in a configuration file in JSON format. As the QGS is only responsible for quote generation, and not for quote verification, we only describe the corresponding QCNL settings in the following. To see all settings and the default values, refer to the default configuration file.
| Setting | Description |
pccs_url |
Address of the service that the PCK Certificate is retrieved from, which in most cases is a collateral caching service (e.g., PCCS). Alternatively, the setting can be used to point to Intel PCS directly using https://api.trustedservices.intel.com/sgx/certification/v4/.Note: Directly using Intel PCS is only allowed for low-frequency testing purposes (e.g., Proof of Concepts, testing, CI/CD pipelines). For more info about using Intel PCS directly including restrictions, please see the notes in the Intel TDX Quote Generation and Quote Verification Collateral section. |
use_secure_cert |
Defines whether the HTTPS certificate of the collateral retrieval endpoint is verified. Set it to false to accept an insecure certificate, for example a self-signed one.Note: You must not use insecure HTTPS certificates in a production environment. |
retry_times |
Number of retries after a failed request. A request is retried if the connection could not be established, if it timed out, or if the service answered with the HTTP status code 503. The defined number of retries applies per endpoint: if local_pck_url is configured, it is retried independently from pccs_url, so failed PCK Certificate requests can result in up to twice the number of total attempts. A value of 0 disables retrying. |
retry_delay |
Delay in seconds before each retry. A value of 0 makes the QCNL wait one second before the first retry and then double the waiting time for every further retry. |
local_pck_url |
Address of an additional service for PCK Certificate retrieval. The lookup order is: local_pck_url → local cache → pccs_url. Responses from this additional service are never written to the local cache. |
pck_cache_expire_hours |
Lifetime in hours of the local cache for PCK Certificates that were retrieved from pccs_url. A value of 0 disables the client-side cache TTL, and values above 2160 (90 days) are reduced to 2160. Note that the Cache-Control: max-age header in the service's HTTP response takes precedence over this setting; if the service returns a non-zero max-age, responses are cached for that duration regardless of this setting. |
local_cache_only |
If set to true, the QCNL exclusively uses PCK Certificates from local cache files and does not send requests to any PCK Certificate service provider. In this case, an administrator has to pre-populate the cache folders, for example with cache files generated by the PCS Client Tool. |
Location of the local cache files
The local cache files live in a folder named .dcap-qcnl and its location is chosen from the ordered list $AZDCAP_CACHE, $XDG_CACHE_HOME, $HOME, $TMPDIR, and /tmp/.
To determine the location of .dcap-qcnl/, QCNL first traverses the list of locations in order and checks whether .dcap-qcnl/ exists.
If .dcap-qcnl/ does exist, QCNL uses the found folder.
If .dcap-qcnl/ doesn't exist in any of the locations, QCNL again traverses the list of locations in order and tries to create the folder.
A location defined by an environment variable is skipped if that variable is unset or empty; /tmp/ is a fixed fallback and is never skipped for this reason.
Additionally, a location is skipped if its directory cannot be created (e.g., no write permission or the parent directory doesn't exist).
Note that the environment variables are evaluated for the user that runs the process loading the QPL, which is the qgsd user in the case of the QGS.
To change the configuration of the QCNL, follow these steps:
-
Open the configuration file of the QCNL (default:
/etc/sgx_default_qcnl.conf) in an editor of your choice. For example:sudo vim /etc/sgx_default_qcnl.confHow to use a QCNL configuration file at a different path?
If you prefer, you can rename the
sgx_default_qcnl.conffile and/or move it to another location. On start of theqgsdservice, the QCNL is loaded andqgsdevaluates the environment variableQCNL_CONF_PATH, which can override the default path of the configuration file. To change the path of the QCNL configuration file, follow these steps:-
Open the override file of the
qgsdservice:sudo systemctl edit qgsd -
Add the environment variable to the
[Service]section, pointing to the new name and/or location of the configuration file:[Service] Environment=QCNL_CONF_PATH=/etc/sgx_qcnl_local_pccs.conf -
For the change to take effect, restart the QGS.
-
-
Add or adjust the settings presented above. For example, the URL of your collateral caching service can be adjusted by changing the following line appropriately:
"pccs_url": "https://<PCCS URL:port>/sgx/certification/v4/" -
For the change to take effect, restart the QGS.
Restart QGS¶
To restart the QGS, execute the following command:
sudo systemctl restart qgsd.service
sudo systemctl restart qgsd.service
sudo systemctl restart qgsd.service
sudo systemctl restart qgsd.service
sudo systemctl restart qgsd.service
Check QGS Log¶
To check the service log of the QGS, execute the following command:
# Note: remove `-f` for non-interactive log
sudo journalctl -u qgsd -f
# Note: remove `-f` for non-interactive log
sudo journalctl -u qgsd -f
# Note: remove `-f` for non-interactive log
sudo journalctl -u qgsd -f
# Note: remove `-f` for non-interactive log
sudo journalctl -u qgsd -f
# Note: remove `-f` for non-interactive log
sudo journalctl -u qgsd -f