Skip to content

aproxy: decouple KBS backend and shift runtime_data hashing from kernel#1122

Open
armenon-rh wants to merge 2 commits into
coconut-svsm:mainfrom
armenon-rh:pr2-kbs-attestation-fix
Open

aproxy: decouple KBS backend and shift runtime_data hashing from kernel#1122
armenon-rh wants to merge 2 commits into
coconut-svsm:mainfrom
armenon-rh:pr2-kbs-attestation-fix

Conversation

@armenon-rh

Copy link
Copy Markdown

This PR fixes the SEV-SNP attestation flow against a trustee KBS server.

  1. Decouple KBS backend and use sev crate as a dependency for AttestationReport struct.
  2. Send pubkey in the negotiation request, allowing aproxy to precompute the hash.
  3. Hash algorithm changed to sha-384

Verified that the attestation token is successfully issued by the trustee KBS instance, see kbs container logs below

2026-06-16T07:48:09.055691Z  INFO actix_web::middleware::logger: 10.8.1.184 "POST /kbs/v0/attest HTTP/1.1" 200 2368 "-" "-" 0.164425
2026-06-16T07:48:09.056259Z  INFO kbs::attestation::backend: Cookie 60e3c85e8d4c471eaadd0ce24c5f6e5d request to get resource
2026-06-16T07:48:09.058087Z  INFO actix_web::middleware::logger: 10.8.1.184 "GET /kbs/v0/resource/coco/mykey/k2 HTTP/1.1" 200 554 "-" "-" 0.001929

@stefano-garzarella stefano-garzarella self-assigned this Jun 16, 2026
@00xc 00xc added the needs-rebase The PR needs to be rebased to the latest upstream branch label Jun 22, 2026
Comment thread kernel/src/attest.rs Outdated

@luigix25 luigix25 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I did a quick review.

Don't forget to run make clippy cargo fmt and make test before submitting :)

Comment thread tools/aproxy/src/backend/kbs.rs Outdated
Comment thread tools/aproxy/src/backend/kbs.rs Outdated
Comment thread Cargo.toml Outdated
#[serde(rename = "attestation_report")]
snp_report: sev::firmware::guest::AttestationReport,
#[serde(rename = "cert_chain")]
certs_buf: Option<Vec<u8>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is this parameter used? I see it is set to None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Our implementation checks against the KBS verifier SnpEvidence

I recall I was getting this error when these fields were not in place.

Indeed its set to None and sent by the aproxy, so maybe the KBS verifier fetches the certificate automatically. I am not entirely sure.

Comment thread kernel/src/attest.rs
Currently the attestation flow only works against the mock attestation
server. Against the production-ready Trustee KBS (CoCo), attestation
fails immediately because the cryptographic hashes do not match.

The RCAR (Request-Challenge-Attestation-Response) handshake forms the
basis of secret retrieval from KBS. This has 2 parts, negotiation and
attestation. Negotiation phase typically only allows the client to get a
nonce from the KBS, and this nonce will be used in the attestation
request in the subsequent step. For SVSM, the client is SVSM + aproxy.

The current negotiation request:
  SVSM sends only version and tee to aproxy. SVSM public key is not sent.
The current negotiation response:
  aproxy fetches the challenge nonce from KBS and send it back to SVSM.
The current hashing in svsm:
  SVSM does the hashing locally in the kernel by concatenating the raw
bytes of the nonce and the pub_key coordinates, hashing them with
SHA-512.

Report data = sha512(nonce + pub_key_x + pub_key_y).
This is signed by the AMD processor.

The real Trustee KBS does not expect a raw concatenation of bytes hashed
with SHA-512. Instead, the KBS verifier expects the runtime data to be a
JWS-formatted JSON object containing specific parameters, hashed with
SHA-384, see below.

{
  "nonce": kbs-nonce,
  "additional-evidence": "",
  "tee-pubkey": {
    "kty": "EC",
    "crv": "P-521",
    "x": "..."
    "y": "..."
  }
}

Although the hashing and serializing can be done in SVSM, it is not wise
to add/implement the JSON serializer in the kernel. We would also
require a base64url encoder. Besides, if SVSM constructs the
KBS-specific JSON, SVSM becomes tightly coupled to the specific API
implementation of a single KBS (Trustee).

For those reasons, the job of serialising and hashing should be done by
the aproxy. Because the baremetal SVSM kernel cannot do complex JSON
formatting, serialization, JWS mapping and base64 encoding, SVSM could
not compute this JSON-based hash. As a result the hash SVSM signed in the report
never matches the hash that KBS expected, and the attestation failed.

This commit allows aproxy to have the public key of svsm ready to use
during the negotiation phase itself. By sending SVSM's public key during
negotiation, aproxy can compute the SHA-384 of the constructed JSON first,
and pass it back to SVSM. SVSM can pass that hash to the AMD CPU to be
hardware-signed.

SVSM              APROXY        Trustee KBS
  |                 |                |
  |                 |                |
  | 1. Negotiation  |                |
  +---------------->|                |
  |    Request      |                |
  |(tee, ver, pk)   |2. GET/auth     |
  |                 +--------------->|
  |                 | 3. Returns     |
  |                 |<---------------+
  |                 | challenge nonce|
  |                 |                |
  |                 |                |
  |                 |                |
  |                 |4. Precomputes  |
  |                 |   KBS hashing. |
  |                 |a. Constructs runtime_data JSON
  |                 |  (with nonce + SVSM key)
  |                 |b. Computes sha-384 of this JSON
  |                 |   This is called challenge hash
  |                 |                |
  |                 |                |
  |                 |                |
  |  5. Negotiation |                |
  |<----------------+                |
  |     Response    |                |
  |                 |                |
  |                 |                |
  |                 |                |
  |6. Requests Hardware Report       |
  |   Passes Challenge hash as       |
  |   REPORT_DATA. AMD CPU signs     |
  |   Challenge Hash|                |
  |                 |                |
  |                 |                |
  |                 |                |
  |                 |                |
  |  7. Attestation |                |
  +---------------->|                |
  |     Request     |                |
  | Sends TEE report|                |
  | + SVSM pubkey   |                |
  |                 | 8. POST/attest |
  |                 +--------------->|
  |                 | -Sends TEE rep |
  |                 | -Sends runtime_|
  |                 |  data JSON with|
  |                 | (nonce + pubkey)
  |                 |                |
  |                 |                |
  |                 |                | 9.Verification at KBS
  |                 |                |  a. Compute sha384 of runtime
  |                 |                |     data (covers nonce+pubkey)
  |                 |                |  b. Reads REPORT_DATA from TEE
  |                 |                |     Report. Contains (challenge Hash)
  |                 |                |  c. Compares both. Attestation
  |                 |                |     accepted. Issue attestation token.
  v                 v                v

A subsequent commit will move the JSON construction and hashing
responsibility out of the kernel, and put it into the aproxy.

Signed-off-by: Arun Menon <armenon@redhat.com>
Running SVSM against a trustee KBS service to verify the
attestation report and get the attestation token back fails with the
error "Deserialize SNP Evidence failed" [1]

On further inspection, here are a few problems identified with the
current attestation flow:

1. The trustee KBS service depends on the virtee sev crate
   AttestationReport structure [2] and it does not match with the
   structure of AttestationReport that SVSM sends to aproxy.
   As a result the deserialization fails.
2. The additional_evidence is not passed in the report at all.
3. Runtime data is hashed using sha512 whereas KBS service expects to
   use Sha384 hash function.
4. nonce should be stored from the negotiation response, so that it can
   be sent back in the attestation request.

In this commit, we add the official 'sev' crate (from virtee, with snp
and serde features enabled) to aproxy's dependencies, and implement a
decoupled KBS backend in tools/aproxy. [3]

The aproxy KBS backend parses the raw report bytes received from SVSM
using sev::firmware::guest::AttestationReport. This completely decouples
the SVSM kernel from KBS serialization format requirements.
The standard parser utilized by the KBS evidence verifier is used in the
aproxy, resolving previous deserialization failures.

aproxy backend now constructs the complete, KBS-compliant runtime_data
JSON object (which contains the nonce and the tee-pubkey formatted
with JWS parameters like alg: 'ECDH-ES+A256KW' and crv: 'P-521').
aproxy serializes this entire JSON object, computes its SHA-384 hash,
and returns this combined hash to SVSM as the NegotiationResponse challenge.
SVSM then simply hashes this single challenge into its SEV-SNP report.

Update the hash function in kernel/src/attest.rs to directly copy the
48 byte challenge into a zero padded 64 byte array. Hashing is actually
performed in the aproxy and the hash function used is Sha384

Casting SNP report bytes into sev crate's AttestationReport structure
via read_unaligned causes undefined behaviour due to optimized Option
enums. Replace unsafe cast with sev::parser::Decoder to safely decode
the report byte-by-byte into the correct memory layout.

Adding resource_id: Option<String> to KbsProtocol prevents the Protocol
enum from implementing Copy. Therefore pattern matching on self.protocol
directly inside HttpClient method now borrows self, and that conflicts
with subsequently passing self as mutable reference to the backend method
calls like negotiation and attestation. Use idiomatic mem::replace pattern
to avoid this.

Since sev crate introduces std, make clippy --all-features fails.
Invoking clippy with CARGO_HACK=1 succeeds.
make clippy CARGO_HACK=1

[1] https://github.com/confidential-containers/trustee/blob/main/deps/verifier/src/snp/mod.rs#L351
[2] https://github.com/confidential-containers/trustee/blob/main/deps/verifier/Cargo.toml#L96
[3] https://docs.rs/crate/sev/8.0.0/source/src/firmware/guest/types/snp.rs#255

Signed-off-by: Arun Menon <armenon@redhat.com>
@armenon-rh armenon-rh force-pushed the pr2-kbs-attestation-fix branch from a39bf21 to bc5b94e Compare July 1, 2026 21:18
@luigix25 luigix25 removed the needs-rebase The PR needs to be rebased to the latest upstream branch label Jul 2, 2026
@luigix25

luigix25 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

@armenon-rh please resolve all the conversation that no longer applies. I run make clippy locally and it's failing:

error[E0152]: found duplicate lang item `panic_impl`
   --> boot/bldr/src/main.rs:403:1
    |
403 | / fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
404 | |     // SAFETY: raising an undefined instuction exception is always safe.
405 | |     unsafe { core::arch::asm!("ud2") }
406 | |     unreachable!("");
407 | | }
    | |_^
    |
    = note: the lang item is first defined in crate `std` (which `uuid` depends on)
    = note: first definition in `std` loaded from /home/lleonard/.rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-8df6be531efb3fd0.rlib
    = note: second definition in the local crate (`bldr`)

not sure why, i didn't investigate, but reverting your changes it passes

Comment on lines +188 to +201
Note: Make sure to add the option --pcd PcdUninstallMemAttrProtocol=TRUE while building edk2 for Fedora 42 images

```bash
export PYTHON3_ENABLE=TRUE
export PYTHON_COMMAND=python3
make -j16 -C BaseTools/
source ./edksetup.sh --reconfig
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 \
-b DEBUG -t GCC \
-D DEBUG_ON_SERIAL_PORT \
-D DEBUG_VERBOSE \
-D TPM2_ENABLE
--pcd PcdUninstallMemAttrProtocol=TRUE
``` No newline at end of file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we already have a section where we talk about building edk2 in the file installation/INSTALL.md.
Can we just link it here somehow? If building instruction change, we'd need to two update two things

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes I will do it.

"EcPublicKeyBytes",
"Challenge"
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: i think this blank like can be dropped

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes

Comment thread kernel/src/attest.rs
/// Take 48 byte negotiation challenge nonce from aproxy into 64 byte array required for the TEE attestation evidence report
fn prepare_report_data(n: &NegotiationResponse) -> Result<Vec<u8>, AttestationError> {
let mut report_data = [0u8; 64];
report_data[..48].copy_from_slice(&n.challenge);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should we check for n.challenge size?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes. I shall add the check in v3.

init_data: None,
runtime_data: RuntimeData {
nonce: BASE64_STANDARD.encode(request.challenge),
nonce: self.original_nonce.take().unwrap_or_default(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should this be treated as an error? IIUC nonce is mandatory, right? I'm not an expert

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes, it should be treated as an error. I will change it in v3

@armenon-rh

Copy link
Copy Markdown
Author

@armenon-rh please resolve all the conversation that no longer applies. I run make clippy locally and it's failing:

error[E0152]: found duplicate lang item `panic_impl`
   --> boot/bldr/src/main.rs:403:1
    |
403 | / fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
404 | |     // SAFETY: raising an undefined instuction exception is always safe.
405 | |     unsafe { core::arch::asm!("ud2") }
406 | |     unreachable!("");
407 | | }
    | |_^
    |
    = note: the lang item is first defined in crate `std` (which `uuid` depends on)
    = note: first definition in `std` loaded from /home/lleonard/.rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-8df6be531efb3fd0.rlib
    = note: second definition in the local crate (`bldr`)

not sure why, i didn't investigate, but reverting your changes it passes

Hi Luigi,
I have mentioned in the commit message that make clippy does not work, because sev crate we added in the commit, pulls uuid which is std dependent. So we can run make clippy CARGO_HACK=1
I do not know if this is a good idea.

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