Skip to main content

rialo_types/
attestation.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! # Attestation Module
5//!
6//! Provides attestation functionality for REX services running in Trusted Execution Environments.
7//!
8//! This module handles the generation and verification of attestation reports that prove:
9//! - The REX service is running in a genuine TEE
10//! - The REX service's code hasn't been tampered with
11//! - The REX service's signing keys are protected by the TEE
12//!
13//! ## Overview
14//!
15//! Attestation reports contain:
16//! - The REX service's public key (base64 encoded)
17//! - A cryptographic signature over this field
18//! - Optional platform-specific attestation evidence (when running in a TEE)
19//!
20//! ## Usage
21//!
22//! To generate attestation reports, use the `AttestationReportExt` trait from `rialo_tee_attestation`.
23//! To verify attestation reports, use the `AttestationReportVerifiable` trait from `rialo_tee_verification`:
24//!
25//! ```ignore
26//! use rialo_types::{AttestationReport, PublicKey};
27//! use rialo_tee_attestation::AttestationReportExt;
28//! use rialo_tee_verification::AttestationReportVerifiable;
29//! use ring::signature::Ed25519KeyPair;
30//! use rcgen::PKCS_ED25519;
31//!
32//! // Replace with your actual key generation logic
33//! let keypair_der = rcgen::KeyPair::generate_for(&PKCS_ED25519)
34//!         .unwrap()
35//!         .serialize_der();
36//! let keypair = Ed25519KeyPair::from_pkcs8(&keypair_der).unwrap();
37//!
38//! // Create a wrapping public key for secret sharing
39//! let wrapping_key = PublicKey::from_bytes([42u8; 32]);
40//!
41//! // Generate the attestation report
42//! let report = AttestationReport::generate(&keypair, wrapping_key).unwrap();
43//! let verified = report.verify(None).unwrap();
44//! let verification_key = verified.verification_key();
45//! ```
46//!
47//! ## Platform Support
48//!
49//! Currently supported TEE platforms:
50//! - AMD SEV-SNP Bare Metal (planned)
51//! - AWS SEV-SNP
52//! - Azure CVM (planned)
53//! - Intel TDX (planned)
54
55#[cfg(feature = "non-pdk")]
56use anyhow::Result;
57use borsh::{BorshDeserialize, BorshSerialize};
58#[cfg(feature = "non-pdk")]
59use fastcrypto::{hash, hash::HashFunction};
60#[cfg(feature = "non-pdk")]
61use ring::signature::{Ed25519KeyPair, KeyPair, Signature};
62use serde::{Deserialize, Serialize};
63use serde_big_array::BigArray;
64
65use crate::{tee_types::AttestationEvidence, PublicKey};
66
67/// Attestation report that will be sent to the caller
68#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
69pub struct AttestationReport {
70    pub inner: AttestationReportInner,
71    #[serde(with = "BigArray")]
72    pub signature: [u8; 64],
73    pub platform_attestation: Option<AttestationEvidence>,
74}
75
76#[cfg(feature = "non-pdk")]
77impl AttestationReport {
78    /// Computes the hash of the attestation report fields
79    pub fn compute_report_hash(inner: &AttestationReportInner) -> [u8; 32] {
80        let mut hash = hash::Sha256::new();
81        hash.update(inner.verification_key);
82        hash.update::<&[u8]>(inner.wrapping_key.as_ref());
83        hash.finalize().digest
84    }
85
86    /// Verifies the report signature
87    pub fn verify_signature(
88        &self,
89        digest: &[u8],
90    ) -> Result<VerifiedAttestation, VerificationError> {
91        ring::signature::UnparsedPublicKey::new(
92            &ring::signature::ED25519,
93            &self.inner.verification_key,
94        )
95        .verify(digest, &self.signature)
96        .map_err(|_| VerificationError::SignatureVerification)?;
97
98        Ok(VerifiedAttestation::new_verified_dangerous(
99            self.inner.clone(),
100        ))
101    }
102}
103
104impl Default for AttestationReport {
105    fn default() -> Self {
106        Self {
107            inner: AttestationReportInner::default(),
108            signature: [0; 64],
109            platform_attestation: None,
110        }
111    }
112}
113
114/// Inner structure of the attestation report containing all the fields that need to be verified
115#[derive(
116    Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
117)]
118pub struct AttestationReportInner {
119    pub verification_key: [u8; 32],
120    pub wrapping_key: PublicKey,
121}
122
123/// Result of attestation verification containing necessary public data
124#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
125pub struct VerifiedAttestation {
126    inner: AttestationReportInner,
127}
128
129impl VerifiedAttestation {
130    /// Creates a new VerifiedAttestation from the inner report
131    ///
132    /// # Safety / Warning
133    /// This constructor is marked "dangerous" because it bypasses all verification checks.
134    /// It must ONLY be called after proper cryptographic verification has been performed
135    /// by the verification trait in rialo-tee-verification.
136    ///
137    /// Misuse of this method can lead to severe security vulnerabilities as it allows
138    /// creating a "verified" attestation without actually verifying anything.
139    pub fn new_verified_dangerous(inner: AttestationReportInner) -> Self {
140        Self { inner }
141    }
142
143    /// Get the public verification key bytes from the verified attestation
144    pub fn verification_key(&self) -> [u8; 32] {
145        self.inner.verification_key
146    }
147
148    /// Returns the public wrapping key from the verified attestation.
149    pub fn wrapping_key(&self) -> PublicKey {
150        self.inner.wrapping_key
151    }
152}
153
154/// Errors that can happen while verifying an [`AttestationReport`].
155#[derive(Debug, thiserror::Error)]
156pub enum VerificationError {
157    #[error("Failed to verify attestation report's signature")]
158    SignatureVerification,
159
160    #[error("Invalid platform attestation evidence")]
161    InvalidPlatformAttestation,
162
163    #[error("Platform attestation verification failed: {0}")]
164    PlatformAttestationVerificationFailed(String),
165}
166
167/// Converts an Ed25519 signature to a fixed-size byte array.
168///
169/// # Arguments
170///
171/// * `signature` - The Ed25519 signature to convert.
172///
173/// # Returns
174///
175/// * `[u8; 64]` - The signature as a fixed-size byte slice.
176#[cfg(feature = "non-pdk")]
177pub fn ed25519_signature_to_slice(signature: &Signature) -> [u8; 64] {
178    let bytes = signature.as_ref();
179    if bytes.len() != 64 {
180        unreachable!(
181            "Ed25519 signatures are always 64 bytes, but got {}",
182            bytes.len()
183        );
184    }
185    bytes.try_into().unwrap()
186}
187
188/// Converts am Ed25519 keypair pubkey to a fixed-size byte array.
189///
190/// # Arguments
191///
192/// * `pubkey` - The Ed25519 keypair with the public key to convert.
193///
194/// # Returns
195///
196/// * `[u8; 32]` - The public key as a fixed-size byte array.
197#[cfg(feature = "non-pdk")]
198pub fn ed25519_keypair_to_pubkey_slice(keypair: &Ed25519KeyPair) -> [u8; 32] {
199    let pubkey = keypair.public_key().as_ref();
200    if pubkey.len() != 32 {
201        unreachable!(
202            "Ed25519 public keys are always 32 bytes, but got {}",
203            pubkey.len()
204        );
205    }
206    pubkey.try_into().unwrap()
207}
208
209#[cfg(all(test, feature = "non-pdk"))]
210mod tests {
211    use rcgen::PKCS_ED25519;
212    use ring::signature::Ed25519KeyPair;
213
214    use super::*;
215    use crate::tee_types::TeeType;
216
217    // Test constant for wrapping public key
218    const TEST_WRAPPING_KEY_BYTES: [u8; 32] = [42u8; 32];
219
220    fn test_wrapping_key() -> PublicKey {
221        PublicKey::from_bytes(TEST_WRAPPING_KEY_BYTES)
222    }
223
224    // Helper function to generate a test keypair
225    fn generate_test_keypair() -> Ed25519KeyPair {
226        let key_pair_der = rcgen::KeyPair::generate_for(&PKCS_ED25519)
227            .unwrap()
228            .serialize_der();
229        Ed25519KeyPair::from_pkcs8(&key_pair_der).unwrap()
230    }
231
232    // NOTE: Tests that use AttestationReport::generate have been moved to rialo-tee-attestation
233    // to avoid circular dependencies during test builds. The generate() method is now provided
234    // by the AttestationReportExt trait in rialo-tee-attestation.
235
236    #[test]
237    /// This test verifies that the constructor works correctly.
238    /// It tests the manual creation of attestation reports with platform evidence.
239    fn test_attestation_report_with_platform_attestation_constructor() {
240        let key_pair = generate_test_keypair();
241        let public_key_bytes = ed25519_keypair_to_pubkey_slice(&key_pair);
242
243        // Create test platform attestation evidence
244        let platform_evidence = AttestationEvidence {
245            tee_type: TeeType::SevSnp,
246            evidence_data: vec![1, 2, 3, 4, 5], // Mock evidence data
247        };
248
249        // Create the inner attestation report
250        let inner = AttestationReportInner {
251            verification_key: public_key_bytes,
252            wrapping_key: test_wrapping_key(),
253        };
254
255        // Compute hash and sign
256        let digest = AttestationReport::compute_report_hash(&inner);
257        let signature = ed25519_signature_to_slice(&key_pair.sign(&digest));
258
259        // Create attestation report with platform attestation
260        let attestation_report = AttestationReport {
261            inner,
262            signature,
263            platform_attestation: Some(platform_evidence.clone()),
264        };
265
266        // Verify that the platform attestation is included
267        assert!(attestation_report.platform_attestation.is_some());
268        let platform_attestation = attestation_report.platform_attestation.unwrap();
269        assert_eq!(platform_attestation.tee_type, platform_evidence.tee_type);
270        assert_eq!(
271            platform_attestation.evidence_data,
272            platform_evidence.evidence_data
273        );
274    }
275}