Skip to content

Commit afb66e0

Browse files
committed
rust-sdk: Remove some methods from TappdClient
1 parent cb23763 commit afb66e0

7 files changed

Lines changed: 93 additions & 225 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ trap "kill $SIMULATOR_PID 2>/dev/null || true" EXIT
1313
popd
1414

1515
pushd rust/
16-
cargo test
16+
cargo test -- --show-output
1717
popd
1818

1919
pushd go/

sdk/rust/Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ authors = ["Encifher <encifher@rizelabs.io>"]
99
[dependencies]
1010
alloy = { workspace = true, features = ["signers", "signer-local"] }
1111
anyhow.workspace = true
12-
base64.workspace = true
1312
bon.workspace = true
14-
hex.workspace = true
15-
http.workspace = true
13+
hex.workspace = true
14+
http.workspace = true
1615
http-client-unix-domain-socket = "0.1.1"
1716
reqwest = { workspace = true, features = ["json"] }
18-
serde.workspace = true
19-
serde_json.workspace = true
20-
sha2.workspace = true
17+
serde.workspace = true
18+
serde_json.workspace = true
19+
sha2.workspace = true
20+
x509-parser.workspace = true
2121

2222
[dev-dependencies]
2323
dcap-qvl.workspace = true

sdk/rust/README.md

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4545
### TappdClient (Legacy API)
4646

4747
```rust
48-
use dstack_sdk::tappd_client::{TappdClient, QuoteHashAlgorithm};
48+
use dstack_sdk::tappd_client::TappdClient;
4949

5050
#[tokio::main]
5151
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -61,22 +61,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6161
println!("Key: {}", key_resp.key);
6262
println!("Certificate Chain: {:?}", key_resp.certificate_chain);
6363

64-
// Generate TDX quote with default SHA512
65-
let quote_resp = client.tdx_quote(b"test-data".to_vec()).await?;
64+
// Decode the key to bytes (extracts raw ECDSA P-256 private key - 32 bytes)
65+
let key_bytes = key_resp.to_bytes()?;
66+
println!("ECDSA P-256 private key bytes (32 bytes): {:?}", key_bytes.len());
67+
68+
// Generate quote (exactly 64 bytes of report data required)
69+
let mut report_data = b"test-data".to_vec();
70+
report_data.resize(64, 0); // Pad to 64 bytes
71+
let quote_resp = client.get_quote(report_data).await?;
6672
println!("Quote: {}", quote_resp.quote);
6773
let rtmrs = quote_resp.replay_rtmrs()?;
6874
println!("Replayed RTMRs: {:?}", rtmrs);
6975

70-
// Generate TDX quote with specific hash algorithm
71-
let quote_resp = client.tdx_quote_with_hash_algorithm(
72-
b"test-data".to_vec(),
73-
QuoteHashAlgorithm::Sha256
74-
).await?;
75-
76-
// Generate raw quote (exactly 64 bytes)
77-
let raw_data = vec![0u8; 64];
78-
let raw_quote = client.raw_quote(raw_data).await?;
79-
8076
Ok(())
8177
}
8278
```
@@ -131,22 +127,17 @@ Fetches metadata and measurements about the CVM instance.
131127

132128
#### `derive_key(path: &str) -> DeriveKeyResponse`
133129
Derives a key for a specified path.
134-
- `key`: Private key (PEM or hex format)
130+
- `key`: ECDSA P-256 private key in PEM format
135131
- `certificate_chain`: Vec of X.509 certificate chain entries
132+
- `to_bytes()`: Extracts and returns the raw ECDSA P-256 private key bytes (32 bytes)
136133

137134
#### `derive_key_with_subject(path: &str, subject: &str) -> DeriveKeyResponse`
138135
Derives a key with a custom certificate subject.
139136

140137
#### `derive_key_with_subject_and_alt_names(path: &str, subject: Option<&str>, alt_names: Option<Vec<String>>) -> DeriveKeyResponse`
141138
Derives a key with full certificate customization.
142139

143-
#### `tdx_quote(report_data: Vec<u8>) -> TdxQuoteResponse`
144-
Generates a TDX quote using SHA512 hash algorithm.
145-
146-
#### `tdx_quote_with_hash_algorithm(report_data: Vec<u8>, algorithm: QuoteHashAlgorithm) -> TdxQuoteResponse`
147-
Generates a TDX quote with a specific hash algorithm (SHA256, SHA384, SHA512, etc.).
148-
149-
#### `raw_quote(report_data: Vec<u8>) -> TdxQuoteResponse`
140+
#### `get_quote(report_data: Vec<u8>) -> TdxQuoteResponse`
150141
Generates a TDX quote with exactly 64 bytes of raw report data.
151142

152143
### Structures

sdk/rust/examples/tappd_client_usage.rs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use dstack_sdk::tappd_client::{QuoteHashAlgorithm, TappdClient};
1+
use dstack_sdk::tappd_client::TappdClient;
22

33
#[tokio::main]
44
async fn main() -> anyhow::Result<()> {
@@ -29,34 +29,21 @@ async fn main() -> anyhow::Result<()> {
2929
}
3030
}
3131

32-
// 2. Get a TDX quote
33-
let report_data = b"Hello, world!".to_vec();
34-
match client.tdx_quote(report_data).await {
32+
// 2. Get a quote with 64 bytes of report data
33+
let mut report_data = b"Hello, world!".to_vec();
34+
// Pad to exactly 64 bytes for get_quote
35+
report_data.resize(64, 0);
36+
match client.get_quote(report_data).await {
3537
Ok(response) => {
36-
println!("TDX quote generated successfully!");
38+
println!("Quote generated successfully!");
3739
println!("Quote length: {}", response.quote.len());
3840
}
3941
Err(e) => {
40-
println!("Failed to get TDX quote: {}", e);
42+
println!("Failed to get quote: {}", e);
4143
}
4244
}
4345

44-
// 3. Get a TDX quote with specific hash algorithm
45-
let report_data = b"Hello, world!".to_vec();
46-
match client
47-
.tdx_quote_with_hash_algorithm(report_data, QuoteHashAlgorithm::Sha256)
48-
.await
49-
{
50-
Ok(response) => {
51-
println!("TDX quote with SHA256 generated successfully!");
52-
println!("Quote length: {}", response.quote.len());
53-
}
54-
Err(e) => {
55-
println!("Failed to get TDX quote with SHA256: {}", e);
56-
}
57-
}
58-
59-
// 4. Get instance info
46+
// 3. Get instance info
6047
match client.info().await {
6148
Ok(info) => {
6249
println!("Instance info retrieved successfully!");

sdk/rust/src/tappd_client.rs

Lines changed: 47 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Result;
1+
use anyhow::{bail, Context as _, Result};
22
use hex::{encode as hex_encode, FromHexError};
33
use http_client_unix_domain_socket::{ClientUnix, Method};
44
use reqwest::Client;
@@ -86,32 +86,52 @@ pub struct DeriveKeyResponse {
8686
}
8787

8888
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+
}
108104

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+
);
113132
}
114-
Ok(binary)
133+
134+
Ok(key_bytes.to_vec())
115135
}
116136
}
117137

@@ -298,68 +318,10 @@ impl TappdClient {
298318
Ok(response)
299319
}
300320

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-
359321
/// 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> {
361323
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");
363325
}
364326

365327
let payload = json!({

0 commit comments

Comments
 (0)