|
1 | | -use anyhow::Result; |
| 1 | +use anyhow::{bail, Context as _, Result}; |
2 | 2 | use hex::{encode as hex_encode, FromHexError}; |
3 | 3 | use http_client_unix_domain_socket::{ClientUnix, Method}; |
4 | 4 | use reqwest::Client; |
@@ -86,32 +86,52 @@ pub struct DeriveKeyResponse { |
86 | 86 | } |
87 | 87 |
|
88 | 88 | impl DeriveKeyResponse { |
89 | | - /// Decodes the key from PEM/base64 format to bytes, optionally truncating to max_length |
90 | | - pub fn to_bytes(&self, max_length: Option<usize>) -> Result<Vec<u8>, anyhow::Error> { |
91 | | - let mut content = self.key.clone(); |
92 | | - |
93 | | - // Remove PEM headers if present |
94 | | - content = content.replace("-----BEGIN PRIVATE KEY-----", ""); |
95 | | - content = content.replace("-----END PRIVATE KEY-----", ""); |
96 | | - content = content.replace('\n', ""); |
97 | | - |
98 | | - let binary = if content.chars().all(|c| c.is_ascii_hexdigit()) { |
99 | | - // Handle hex-encoded content |
100 | | - hex::decode(content)? |
101 | | - } else { |
102 | | - // Handle base64-encoded content |
103 | | - use base64::Engine; |
104 | | - base64::engine::general_purpose::STANDARD |
105 | | - .decode(content) |
106 | | - .map_err(|e| anyhow::anyhow!("Failed to decode base64: {}", e))? |
107 | | - }; |
| 89 | + /// Decodes the key from PEM format and extracts the raw ECDSA P-256 private key bytes |
| 90 | + pub fn to_bytes(&self) -> Result<Vec<u8>, anyhow::Error> { |
| 91 | + use x509_parser::der_parser::der::parse_der; |
| 92 | + use x509_parser::pem::parse_x509_pem; |
| 93 | + |
| 94 | + let key_content = self.key.trim(); |
| 95 | + |
| 96 | + let (_, pem) = parse_x509_pem(key_content.as_bytes()).context("Failed to parse PEM")?; |
| 97 | + // Parse PKCS#8 PrivateKeyInfo structure |
| 98 | + // PKCS#8 format: SEQUENCE { version, algorithm, privateKey } |
| 99 | + let (_, der_seq) = parse_der(&pem.contents).context("Failed to parse DER")?; |
| 100 | + let sequence = der_seq.as_sequence().context("Expected SEQUENCE")?; |
| 101 | + if sequence.len() < 3 { |
| 102 | + bail!("Invalid PKCS#8 structure: expected at least 3 elements"); |
| 103 | + } |
108 | 104 |
|
109 | | - if let Some(max_len) = max_length { |
110 | | - if binary.len() > max_len { |
111 | | - return Ok(binary[..max_len].to_vec()); |
112 | | - } |
| 105 | + // The privateKey is the 3rd element (index 2) and should be an OCTET STRING |
| 106 | + let private_key_data = sequence[2] |
| 107 | + .content |
| 108 | + .as_slice() |
| 109 | + .context("Could not extract privateKey data")?; |
| 110 | + |
| 111 | + // For ECDSA keys, the private key is wrapped in another DER structure |
| 112 | + // Parse the inner ECDSA private key structure |
| 113 | + let (_, inner_der) = parse_der(private_key_data).context("Failed to parse inner DER")?; |
| 114 | + |
| 115 | + let inner_sequence = inner_der.as_sequence().context("Expected inner SEQUENCE")?; |
| 116 | + |
| 117 | + if inner_sequence.len() < 2 { |
| 118 | + return Err(anyhow::anyhow!("Invalid ECDSA private key structure")); |
| 119 | + } |
| 120 | + |
| 121 | + // The actual private key value is the 2nd element (index 1) as OCTET STRING |
| 122 | + let key_bytes = inner_sequence[1] |
| 123 | + .content |
| 124 | + .as_slice() |
| 125 | + .context("Could not extract key bytes")?; |
| 126 | + |
| 127 | + if key_bytes.len() != 32 { |
| 128 | + bail!( |
| 129 | + "Expected 32-byte ECDSA P-256 private key, got {} bytes", |
| 130 | + key_bytes.len() |
| 131 | + ); |
113 | 132 | } |
114 | | - Ok(binary) |
| 133 | + |
| 134 | + Ok(key_bytes.to_vec()) |
115 | 135 | } |
116 | 136 | } |
117 | 137 |
|
@@ -298,68 +318,10 @@ impl TappdClient { |
298 | 318 | Ok(response) |
299 | 319 | } |
300 | 320 |
|
301 | | - /// Sends a TDX quote request using SHA512 as the default hash algorithm |
302 | | - pub async fn tdx_quote(&self, report_data: Vec<u8>) -> Result<TdxQuoteResponse> { |
303 | | - self.tdx_quote_with_hash_algorithm(report_data, QuoteHashAlgorithm::Sha512) |
304 | | - .await |
305 | | - } |
306 | | - |
307 | | - /// Sends a TDX quote request with a specific hash algorithm |
308 | | - pub async fn tdx_quote_with_hash_algorithm( |
309 | | - &self, |
310 | | - mut report_data: Vec<u8>, |
311 | | - hash_algorithm: QuoteHashAlgorithm, |
312 | | - ) -> Result<TdxQuoteResponse> { |
313 | | - // For RAW algorithm, ensure report_data is exactly 64 bytes |
314 | | - if matches!(hash_algorithm, QuoteHashAlgorithm::Raw) { |
315 | | - if report_data.len() > 64 { |
316 | | - anyhow::bail!("Report data is too large, it should be at most 64 bytes when hash_algorithm is RAW"); |
317 | | - } |
318 | | - if report_data.len() < 64 { |
319 | | - // Left-pad with zeros |
320 | | - let mut padded = vec![0u8; 64 - report_data.len()]; |
321 | | - padded.extend_from_slice(&report_data); |
322 | | - report_data = padded; |
323 | | - } |
324 | | - } |
325 | | - |
326 | | - let payload = json!({ |
327 | | - "report_data": hex_encode(report_data), |
328 | | - "hash_algorithm": hash_algorithm.as_str(), |
329 | | - }); |
330 | | - |
331 | | - let response = self |
332 | | - .send_rpc_request("/prpc/Tappd.TdxQuote", &payload) |
333 | | - .await?; |
334 | | - Ok(response) |
335 | | - } |
336 | | - |
337 | | - /// Sends a TDX quote request with custom prefix and hash algorithm |
338 | | - pub async fn tdx_quote_with_prefix( |
339 | | - &self, |
340 | | - report_data: Vec<u8>, |
341 | | - hash_algorithm: QuoteHashAlgorithm, |
342 | | - prefix: Option<&str>, |
343 | | - ) -> Result<TdxQuoteResponse> { |
344 | | - let mut payload = json!({ |
345 | | - "report_data": hex_encode(report_data), |
346 | | - "hash_algorithm": hash_algorithm.as_str(), |
347 | | - }); |
348 | | - |
349 | | - if let Some(prefix) = prefix { |
350 | | - payload["prefix"] = json!(prefix); |
351 | | - } |
352 | | - |
353 | | - let response = self |
354 | | - .send_rpc_request("/prpc/Tappd.TdxQuote", &payload) |
355 | | - .await?; |
356 | | - Ok(response) |
357 | | - } |
358 | | - |
359 | 321 | /// Sends a raw quote request with 64 bytes of report data |
360 | | - pub async fn raw_quote(&self, report_data: Vec<u8>) -> Result<TdxQuoteResponse> { |
| 322 | + pub async fn get_quote(&self, report_data: Vec<u8>) -> Result<TdxQuoteResponse> { |
361 | 323 | if report_data.len() != 64 { |
362 | | - anyhow::bail!("Report data must be exactly 64 bytes for raw quote"); |
| 324 | + bail!("Report data must be exactly 64 bytes for raw quote"); |
363 | 325 | } |
364 | 326 |
|
365 | 327 | let payload = json!({ |
|
0 commit comments