-
Notifications
You must be signed in to change notification settings - Fork 92
Expand file tree
/
Copy pathhost_api.rs
More file actions
124 lines (113 loc) · 3.8 KB
/
Copy pathhost_api.rs
File metadata and controls
124 lines (113 loc) · 3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// SPDX-FileCopyrightText: © 2024-2025 Phala Network <dstack@phala.network>
//
// SPDX-License-Identifier: Apache-2.0
use crate::utils::{deserialize_json_file, sha256, SysConfig};
use anyhow::{anyhow, bail, Context, Result};
use dstack_types::{
shared_filenames::{HOST_SHARED_DIR, SYS_CONFIG},
Platform,
};
use host_api::{
client::{new_client, DefaultClient},
Notification,
};
use ra_tls::attestation::validate_tcb;
use sodiumbox::{generate_keypair, open_sealed_box, PUBLICKEYBYTES};
use tracing::warn;
pub(crate) struct KeyProvision {
pub sk: [u8; 32],
pub mr: [u8; 32],
}
pub(crate) struct HostApi {
client: Option<DefaultClient>,
pccs_url: Option<String>,
}
impl Default for HostApi {
fn default() -> Self {
Self::new(None, None)
}
}
impl HostApi {
pub fn new(base_url: Option<String>, pccs_url: Option<String>) -> Self {
Self {
client: base_url.map(new_client),
pccs_url,
}
}
pub fn load_or_default(url: Option<String>) -> Result<Self> {
let api = match url {
Some(url) => Self::new(Some(url), None),
None => {
let local_config: SysConfig =
deserialize_json_file(format!("{HOST_SHARED_DIR}/{SYS_CONFIG}"))?;
Self::new(local_config.host_api_url, local_config.pccs_url)
}
};
Ok(api)
}
pub async fn notify(&self, event: &str, payload: &str) -> Result<()> {
match Platform::detect_or_dstack() {
Platform::Dstack => {}
Platform::Gcp | Platform::NitroEnclave => {
// Skip notify on unsupported platforms
return Ok(());
}
}
let Some(client) = &self.client else {
return Ok(());
};
client
.notify(Notification {
event: event.to_string(),
payload: payload.to_string(),
})
.await?;
Ok(())
}
pub async fn notify_q(&self, event: &str, payload: &str) {
if let Err(err) = self.notify(event, payload).await {
warn!("Failed to notify event {event} to host: {:?}", err);
}
}
pub async fn get_sealing_key(&self) -> Result<KeyProvision> {
let (pk, sk) = generate_keypair();
let mut report_data = [0u8; 64];
report_data[..PUBLICKEYBYTES].copy_from_slice(pk.as_bytes());
let quote = tdx_attest::get_quote(&report_data).context("Failed to get quote")?;
let Some(client) = &self.client else {
return Err(anyhow!("Host API client not initialized"));
};
let provision = client
.get_sealing_key(host_api::GetSealingKeyRequest {
quote: quote.to_vec(),
})
.await
.map_err(|err| anyhow!("Failed to get sealing key: {err:?}"))?;
// verify the key provider quote
let verified_report = dcap_qvl::collateral::get_collateral_and_verify(
&provision.provider_quote,
self.pccs_url.as_deref(),
)
.await
.context("Failed to get quote collateral")?;
validate_tcb(&verified_report)?;
let sgx_report = verified_report
.report
.as_sgx()
.context("Invalid sgx report")?;
let key_hash = sha256(&provision.encrypted_key);
if sgx_report.report_data[..32] != key_hash {
bail!("Invalid key hash");
}
let mr = sgx_report.mr_enclave;
// write to fs
let sealing_key = open_sealed_box(&provision.encrypted_key, &pk, &sk)
.ok()
.context("Failed to open sealing key")?;
let sk = sealing_key
.try_into()
.ok()
.context("Invalid sealing key length")?;
Ok(KeyProvision { sk, mr })
}
}