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 oracle services running in Trusted Execution Environments.
7//!
8//! This module handles the generation and verification of attestation reports that prove:
9//! - The oracle is running in a genuine TEE
10//! - The oracle's code hasn't been tampered with
11//! - The oracle's signing keys are protected by the TEE
12//!
13//! ## Overview
14//!
15//! Attestation reports contain:
16//! - The oracle'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().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}