Skip to content

Conversation

@tylerfanelli
Copy link
Contributor

@tylerfanelli tylerfanelli commented Sep 30, 2025

TEEs are most likely found in security-sensitive environments where applications are deployed on untrusted systems such as the cloud or edge. In additions of TEE protections, some users in these environments would also like runtime attestation services to ensure that systems running sensitive applications are not compromised at any point; and if they are, that a trusted party can be notified and take action.

Keylime (whitepaper found here) is a CNCF project to measure and verify the boot and runtime environments of systems using TPMs and the Linux IMA subsystem.

For general purpose confidential VMs on AMD SEV-SNP (and eventually Intel TDX), it is recommended to use the Secure VM Service Module (SVSM) for privileged operations and device emulations to guest operating systems. One crucial service offered by the SVSM is marshaling a virtual TPM (vTPM) that a guest OS can use in a trusted manner without the respective TPM hardware.

However, since the SVSM runs at an even higher privilege than the guest OS, it must also be attested before any meaningful processing can take place in the confidential VM. As such, there has been work done to add attestation to the SVSM boot process in order to establish trust in:

  • The SVSM firmware responsible for bootstrapping the rest of the system.
  • The vTPM that is provided by the SVSM.

For SVSM attestation, the Trustee KBS is already supported. However, no one solution provides the needed TEE+firmware attestation of Trustee along with the runtime attestation services provided by Keylime. As such, to set up the required services for this combined use-case, one would need to deploy:

            ┌───────────┐                                         
  ┌───────┐ │Trustee    │ ┌────────┐      ┌────────┐ ┌─────────┐  
  │Trustee│ │Attestation│ │Trustee │      │Keylime │ │Keylime  │  
│ │KBS    │ │Service    │ │RVPS    │ │  │ │Verifier│ │Registrar│ │
│ └───────┘ └───────────┘ └────────┘ │  │ └────────┘ └─────────┘ │
│                                    │  │                        │
└─────────────────┬──────────────────┘  └────────────┬───────────┘
                  │                                  │            
                                                                  
                 TEE                             TPM+IMA          
                 boot                            runtime          
              attestation                       attestation       

Although complex, this is entirely able to be automated in a cloud native environment. However, without a cloud native environment, this offers considerable complexity to deploy for general purpose VMs. Also, for legacy VM applications (i.e. not running other cloud-native software within them), the TEE attestation policies are relatively simple. Thus, there was proposal in Keylime to add simple TEE verification for the sole purpose of establishing a trusted vTPM in confidential environments. With this, legacy VMs could use SVSM for attestation and use their vTPMs in exactly the same way they always have been.

This PR adds a backend attestation service module for the Keylime TEE verifier with the optional keylime-as feature. It also creates and signs EAR tokens on behalf of the Keylime Verifier for successfully-attested clients. With this, the TEE boot+runtime attestation scenario is simplified to:

  ┌───────┐┌────────┐┌─────────┐  
  │Trustee││Keylime ││Keylime  │  
│ │KBS    ││Verifier││Registrar│ │
│ └───────┘└────────┘└─────────┘ │
│                                │
└────────────────┬───────────────┘
                 │                
                                  
                TEE               
                boot              
                 +                
              runtime             
             attestation              

Legacy clients can also run legacy applications and use their vTPM in a trusted manner with SVSM.

TODO in later patch series

There still requires some proper integration between Keylime TPM and TEE policy support. As such, the EAR token marshaling still could use some improvement to report the specific verifier policies used to attest TEE evidence. Also, the Keylime TEE verifier only supports SEV-SNP at the moment, with eventual plans for TDX (contingent on SVSM support for the latter).

@tylerfanelli tylerfanelli requested a review from a team as a code owner September 30, 2025 02:07
Copy link
Member

@Xynnn007 Xynnn007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I right about the dataflow about the design?

  1. keylime verified the evidence, and it only returns Ok or Fail
  2. If Ok, kbs-keylime will sign a ear token and return

"{}/v{}.{}/verify/evidence",
self.config.base_url, self.config.api_version_major, self.config.api_version_minor
))
.header(CONTENT_TYPE, "application.json")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should be application/json?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you. Fixed.

@tylerfanelli
Copy link
Contributor Author

tylerfanelli commented Sep 30, 2025

Am I right about the dataflow about the design?

  1. keylime verified the evidence, and it only returns Ok or Fail

Yes, although this is temporary. Once we're able to merge good TPM+TEE policy support in Keylime, we'll likely return the policy ID and build associated with the evidence appraisal. This is secure due to mTLS being enabled by default between the KBS and Keylime verifier.

  1. If Ok, kbs-keylime will sign a ear token and return

Yes, the KBS signs an EAR token on behalf of the Keylime Verifier (that is, the EAR token will be signed with Keylime's key).

Copy link
Member

@fitzthum fitzthum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Keylime API seems kinda weak tbh, but I guess we can support it.

A few comments.

"attestation_report": evidence_to_verify[0].tee_evidence,
"nonce": runtime.nonce,
"tee_pubkey_x_b64": x,
"tee_pubkey_y_b64": y,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So keylime has its own way of packing the nonce and tee pubkey into the report data? Does this match what we do on the attester side?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the question. The KBS generates the nonce, but the Keylime Verifier recreates the freshness hash and compares it with report data. This is similar to what is done on the attester side, yes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the attester we populate the runtime data not just with the nonce, but with the nonce and tee public key (per the RuntimeData struct). When we validate the report data we want to make sure both of those things match. So the question is how does keylime handle that? You are separating the report data into three fields here. Will keylime recombine them in the right way to calculate the expected report data? Does it only put the nonce in the report data? Does it fail if things don't match? etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, keylime will combine them in the same way to calculate the expected report data. Both the nonce and TEE pubkey are included. It fails if the expected report data doesn't match the actual.

client: Client,
cert: X509,
priv_key_pem: Vec<u8>,
pub_key: Rsa<Public>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These keys are all used for making the JWT attestation token, right? Do we do anything to validate the response coming from keylime (besides https)? I guess keylime itself doesn't return any kind of JWT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This group of keys serves two purposes:

  • The first being mTLS between the KBS/Keylime so they both are confident they are talking to the right counterpart. Since we have mTLS, we don't need to validate the response.

  • The second being giving the KBS the ability to sign JWTs on behalf of the Keylime Verifier. As mentioned before, the Keylime Verifier requires mTLS to communicate with. For a CVM scenario with SVSM, it is not wise to give the CVM access to the mTLS certs, so we restrict communication to the KBS, which acts as a intermediary to the Keylime Verifier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I guess it could be more clear which is which.

@tylerfanelli
Copy link
Contributor Author

Cool. Keylime API seems kinda weak tbh, but I guess we can support it.

It is weak right now. Keylime has its own policy format it follows for TPMs, and we're trying to square that with TEE evidence. It'll be much more mature at that point. These changes coupled with coconut-svsm/svsm#828 define the e2e story for SVSM <--> KBS <--> Keylime however, which certainly helps with testing the new policy changes in Keylime.

@tylerfanelli tylerfanelli marked this pull request as draft September 30, 2025 23:52
@tylerfanelli tylerfanelli force-pushed the keylime-as branch 2 times, most recently from 3cbd5f6 to 10c51ff Compare October 1, 2025 03:10
@tylerfanelli
Copy link
Contributor Author

@fitzthum After reviewing your comments, I opted to just return a simple JWT (i.e. not EAR yet) from the KBS, rather than a half-baked EAR implementation. This is only temporary, as Keylime itself is working out how to address EAR tokens internally at the moment.

I also added claims support returned from the Keylime Verifier, as I agreed that it made more sense to come from there. I'll add EAR support in another series in the future.

Would you mind re-reviewing?

@tylerfanelli tylerfanelli marked this pull request as ready for review October 1, 2025 03:13
));
}

let data = &evidence_to_verify[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps add a check for keylime supported Tees here?

Copy link
Member

@fitzthum fitzthum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good. Should add some docs, but this seems reasonable to me.

@tylerfanelli
Copy link
Contributor Author

Looking pretty good. Should add some docs, but this seems reasonable to me.

Great. Yes, I'll add some docs showing purpose and how to set up.

@tylerfanelli tylerfanelli force-pushed the keylime-as branch 3 times, most recently from f674909 to 0ab36f2 Compare October 4, 2025 03:41
@tylerfanelli tylerfanelli marked this pull request as draft October 7, 2025 02:35
@tylerfanelli tylerfanelli force-pushed the keylime-as branch 2 times, most recently from 0f0a52c to 65d1cd7 Compare October 8, 2025 01:21
@tylerfanelli tylerfanelli marked this pull request as ready for review October 8, 2025 03:36
@tylerfanelli
Copy link
Contributor Author

@fitzthum @Xynnn007 @mythi I've cleaned up the API to open the possibility of other CPU TEE formats to be supported. Would you mind another review?

Adding documentation in the meantime.

Copy link
Member

@Xynnn007 Xynnn007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tylerfanelli there are some more code nits. Besides, there is one thing that I hope to include in this PR

  1. How to generate the certs and keys used by keylime
  2. What are the keys and how to set them in both keylime and KBS side

Also, a design perspective that is not a blocker. This PR looks like to let KBS play a role as a representative to sign JWT for KeylimeVerifier. From the perspective of functional decoupling, KBS should not be concerned with token issuance, leaving it to the Backend AS. Therefore, an optional refactoring direction in the future is to add an API for issuing JWTs to Keylime itself. Alternatively, a simple shim component, such as KeylimeAttestationService, can be built between Keylime and KBS. Its function is to call Keylime for authentication, issue tokens, and pass them to KBS.

Cargo.toml Outdated
] }
reqwest = { version = "0.12", default-features = false, features = [
"default-tls",
"native-tls",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to change this to native-tls? default-tls will use pure rust underlying crypto crates, while native-tls will use openssl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the ClientBuilder's identity (for mTLS) requires either native-tls or rustls-tls. It seems like you're looking for rustls-tls? I can switch.

Side note: I'd likely prefer OpenSSL, is there a reason you want to avoid it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, rustls.

We usually want to build static compilation to ensure that, for example, a binary can be executed on all x86 platforms without having to worry about the OS. Static compilation of openssl is not simple. Although KBS and AS have not yet achieved full static compilation, it is still generally preferred to avoid openssl under the same circumstances.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I've made the change.

pub api_version_major: u8,
pub api_version_minor: u8,
/// Path of the verifier CA certificates.
pub cv_ca_path: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the certs to be looked up are {cv_ca_path}/cacert.crt, {cv_ca_path}/server-cert.crt and some others. I recommend writing this down in a user guide documentation, including how to organize the configuration file names in this directory and the meaning of each file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, currently working on a setup document that will include this.

// Build an HTTP client to communicate with the Keylime verifier. Establish an identity
// using the verifier's certificates.
let client = ClientBuilder::new()
.identity(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like server_priv_pem and server_x509_pem is used for KBS side to make mTLS channel with keylime verifier. Some questions:

  1. This key and cert are not the ones that keylime verifier server uses to launch TLS server, right?
  2. Is it better to rename them to client_priv_pem and client_x509_pem?
  3. Logically this requires the keylime verifier side needs to set a trust root that trustes this client x509 cert - or did I miss anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like server_priv_pem and server_x509_pem is used for KBS side to make mTLS channel with keylime verifier. Some questions:

1. This key and cert are not the ones that keylime verifier server uses to launch TLS server, right?

Yes, it is. In traditional Keylime setups, the Verifier and Registrar use the server certificates, and are considered all part of the server. We're adding onto that and making the KBS also a part of this setup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this is a bit strange, because we're using the KeylimeVerifier certificate as the client certificate to access KeylimeVerifier. When you mention Verifier using the server certificate, are you referring to the Verifier's externally exposed interface using this certificate?

So, I'm a bit skeptical about whether a client certificate is necessary. In other words, do we need to add a TrustRoot for the target KeylimeVerifier root certificate to the client logic?

Copy link
Contributor Author

@tylerfanelli tylerfanelli Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think I misunderstood. KBS is using the verifier's client certificates for mTLS. They are not using the same certificates to communicate with each other.

However, since the KBS signs JWTs on behalf of the verifier, it also requires its public/private keypair to do that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the verifier's client certificate? Do you mean that by default keylime enables mTLS and connections between KeylimeVerifier and Agent use that?

However, since the KBS signs JWTs on behalf of the verifier, it also requires its public/private keypair to do that.

Do you mean KBS should use KeylimeVerifier's key pair? I am not an expert on keylime; do you mean that KeylimeVerifier have another key pair except for https key pair?

@tylerfanelli
Copy link
Contributor Author

Also, a design perspective that is not a blocker. This PR looks like to let KBS play a role as a representative to sign JWT for KeylimeVerifier. From the perspective of functional decoupling, KBS should not be concerned with token issuance, leaving it to the Backend AS.

We want to eventually get there, but this requires a bit more thought on the Keylime side, as token issuance is not something that is built into the current architecture. This is a compromise for the moment, allowing Keylime to not worry about it.

Therefore, an optional refactoring direction in the future is to add an API for issuing JWTs to Keylime itself. Alternatively, a simple shim component, such as KeylimeAttestationService, can be built between Keylime and KBS. Its function is to call Keylime for authentication, issue tokens, and pass them to KBS.

An interesting thought. I'll have to discuss with the community more on if they forsee token issuance being useful for other areas in the project in the future. If we're the only use-case, then a middle attestation service seems like the best option. But if they forsee other uses, then we should likely just build it into Keylime directly.

Thanks for the input.

@tylerfanelli tylerfanelli force-pushed the keylime-as branch 4 times, most recently from bc2cad6 to e812a89 Compare October 20, 2025 04:54
With the introduction of the Keylime one-shot attestation API and TEE
extensions, the Keylime Verifier is able to attest the state of TEE
attestation reports alongside TPM evidence. This allows Keylime to act
as an attestation service backend for the KBS.

Signed-off-by: Tyler Fanelli <[email protected]>
Create an example config file to show how one could configure the KBS to
use Keylime as an alternative attestation service.

Signed-off-by: Tyler Fanelli <[email protected]>
@tylerfanelli
Copy link
Contributor Author

@Xynnn007 The verifier features this relies on hasn't made it into a released API version yet. Would you mind if we added this as experimental and provided the documentation once the new verifier API (with the required TEE changes) is released to add docs?

Tee::Snp => (),
_ => bail!("invalid TEE"),
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if data.tee != Tee::Snp {
bail!("invalid TEE");
}

Copy link
Contributor Author

@tylerfanelli tylerfanelli Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this makes complete sense at the moment, I want to be able to just add to this match statement as new TEE architectures become supported, like TDX.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I once thought this would be detected by cargo clippy as a lint error but it is not. Btw you might need to add feature keylime-as to be covered by Rust lint checker here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants