dstack_sdk_types/
tappd.rs

1// SPDX-FileCopyrightText: © 2025 Daniel Sharifi <daniel.sharifi@nearone.org>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use alloc::{
6    collections::BTreeMap,
7    string::{String, ToString},
8    vec::Vec,
9};
10use anyhow::{bail, Context as _, Result};
11use hex::{encode as hex_encode, FromHexError};
12use serde::{Deserialize, Serialize};
13use sha2::Digest;
14
15#[cfg(feature = "borsh_schema")]
16use borsh::BorshSchema;
17#[cfg(feature = "borsh")]
18use borsh::{BorshDeserialize, BorshSerialize};
19
20use crate::dstack::EventLog;
21
22const INIT_MR: &str = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
23
24/// Hash algorithms supported by the TDX quote generation
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
27#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
28pub enum QuoteHashAlgorithm {
29    Sha256,
30    Sha384,
31    Sha512,
32    Sha3_256,
33    Sha3_384,
34    Sha3_512,
35    Keccak256,
36    Keccak384,
37    Keccak512,
38    Raw,
39}
40
41impl QuoteHashAlgorithm {
42    pub fn as_str(&self) -> &'static str {
43        match self {
44            Self::Sha256 => "sha256",
45            Self::Sha384 => "sha384",
46            Self::Sha512 => "sha512",
47            Self::Sha3_256 => "sha3-256",
48            Self::Sha3_384 => "sha3-384",
49            Self::Sha3_512 => "sha3-512",
50            Self::Keccak256 => "keccak256",
51            Self::Keccak384 => "keccak384",
52            Self::Keccak512 => "keccak512",
53            Self::Raw => "raw",
54        }
55    }
56}
57
58fn replay_rtmr(history: Vec<String>) -> Result<String, FromHexError> {
59    if history.is_empty() {
60        return Ok(INIT_MR.to_string());
61    }
62    let mut mr = hex::decode(INIT_MR)?;
63    for content in history {
64        let mut content_bytes = hex::decode(content)?;
65        if content_bytes.len() < 48 {
66            content_bytes.resize(48, 0);
67        }
68        mr.extend_from_slice(&content_bytes);
69        mr = sha2::Sha384::digest(&mr).to_vec();
70    }
71    Ok(hex_encode(mr))
72}
73
74/// Response from a key derivation request
75#[derive(Debug, Serialize, Deserialize)]
76#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
77#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
78pub struct DeriveKeyResponse {
79    /// The derived key (PEM format for certificates, hex for raw keys)
80    pub key: String,
81    /// The certificate chain
82    pub certificate_chain: Vec<String>,
83}
84
85impl DeriveKeyResponse {
86    /// Decodes the key from PEM format and extracts the raw ECDSA P-256 private key bytes
87    pub fn decode_key(&self) -> Result<Vec<u8>, anyhow::Error> {
88        use pkcs8::der::asn1::{Int, OctetString};
89        use pkcs8::der::{Decode, Document, Reader, SliceReader};
90        use pkcs8::PrivateKeyInfo;
91
92        let key_content = self.key.trim();
93
94        // Parse PEM to DER using der's Document
95        let (label, doc) = Document::from_pem(key_content)
96            .map_err(|e| anyhow::anyhow!("Failed to parse PEM: {:?}", e))?;
97
98        // Verify it's a private key
99        if label != "PRIVATE KEY" {
100            bail!("Expected PRIVATE KEY PEM label, got: {}", label);
101        }
102
103        // Parse as PKCS#8 PrivateKeyInfo
104        let private_key_info = PrivateKeyInfo::from_der(doc.as_bytes())
105            .map_err(|e| anyhow::anyhow!("Failed to parse PKCS#8 private key: {:?}", e))?;
106
107        // Extract the private key bytes from the PKCS#8 structure
108        // For ECDSA P-256 keys, the private key data contains a DER-encoded ECPrivateKey
109        let private_key_data = private_key_info.private_key;
110
111        // Parse the ECPrivateKey structure to extract the raw key bytes
112        // ECPrivateKey ::= SEQUENCE {
113        //   version INTEGER,
114        //   privateKey OCTET STRING,
115        //   parameters [0] EXPLICIT ECParameters OPTIONAL,
116        //   publicKey [1] EXPLICIT BIT STRING OPTIONAL
117        // }
118        let mut reader = SliceReader::new(private_key_data)
119            .map_err(|e| anyhow::anyhow!("Failed to create reader: {:?}", e))?;
120        let key_bytes = reader
121            .sequence(|reader| {
122                // Skip version (INTEGER)
123                let _version: Int = reader.decode()?;
124                // Get the private key (OCTET STRING)
125                let private_key: OctetString = reader.decode()?;
126                // Skip optional fields (parameters and publicKey)
127                // We don't need to parse them, just consume remaining data
128                while !reader.is_finished() {
129                    let _: pkcs8::der::Any = reader.decode()?;
130                }
131                Ok(private_key.as_bytes().to_vec())
132            })
133            .map_err(|e| anyhow::anyhow!("Failed to parse ECPrivateKey structure: {:?}", e))?;
134
135        if key_bytes.len() != 32 {
136            bail!(
137                "Expected 32-byte ECDSA P-256 private key, got {} bytes",
138                key_bytes.len()
139            );
140        }
141
142        Ok(key_bytes)
143    }
144}
145
146/// Response from a TDX quote request
147#[derive(Debug, Serialize, Deserialize)]
148#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
149#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
150pub struct TdxQuoteResponse {
151    /// The TDX quote in hexadecimal format
152    pub quote: String,
153    /// The event log associated with the quote
154    pub event_log: String,
155    /// The hash algorithm used (if returned by server)
156    #[serde(default)]
157    pub hash_algorithm: Option<String>,
158    /// The prefix used (if returned by server)
159    #[serde(default)]
160    pub prefix: Option<String>,
161}
162
163impl TdxQuoteResponse {
164    pub fn decode_quote(&self) -> Result<Vec<u8>, FromHexError> {
165        hex::decode(&self.quote)
166    }
167
168    pub fn decode_event_log(&self) -> Result<Vec<EventLog>, serde_json::Error> {
169        serde_json::from_str(&self.event_log)
170    }
171
172    /// Replays RTMR history to calculate final RTMR values
173    pub fn replay_rtmrs(&self) -> Result<BTreeMap<u8, String>> {
174        let parsed_event_log: Vec<EventLog> = self.decode_event_log()?;
175        let mut rtmrs = BTreeMap::new();
176        for idx in 0..4 {
177            let mut history = Vec::new();
178            for event in &parsed_event_log {
179                if event.imr == idx {
180                    history.push(event.digest.clone());
181                }
182            }
183            rtmrs.insert(
184                idx as u8,
185                replay_rtmr(history)
186                    .ok()
187                    .context("Invalid digest in event log")?,
188            );
189        }
190        Ok(rtmrs)
191    }
192}
193
194/// TCB (Trusted Computing Base) information
195#[derive(Debug, Serialize, Deserialize)]
196#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
197#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
198pub struct TappdTcbInfo {
199    /// The measurement root of trust
200    pub mrtd: String,
201    /// The value of RTMR0 (Runtime Measurement Register 0)
202    pub rtmr0: String,
203    /// The value of RTMR1 (Runtime Measurement Register 1)
204    pub rtmr1: String,
205    /// The value of RTMR2 (Runtime Measurement Register 2)
206    pub rtmr2: String,
207    /// The value of RTMR3 (Runtime Measurement Register 3)
208    pub rtmr3: String,
209    /// The event log entries
210    pub event_log: Vec<EventLog>,
211    /// The application compose file
212    pub app_compose: String,
213}
214
215/// Response from a Tappd info request
216#[derive(Debug, Serialize, Deserialize)]
217#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
218#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
219pub struct TappdInfoResponse {
220    /// The application identifier
221    pub app_id: String,
222    /// The instance identifier
223    pub instance_id: String,
224    /// The application certificate
225    pub app_cert: String,
226    /// Trusted Computing Base information
227    pub tcb_info: TappdTcbInfo,
228    /// The name of the application
229    pub app_name: String,
230}