Skip to content

Commit d0bf9bf

Browse files
committed
Add wasm verifers
1 parent dbfbaa3 commit d0bf9bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+7341
-99
lines changed

Cargo.lock

Lines changed: 1930 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ members = [
99
"integration-tests",
1010
"tools/trustee-cli",
1111
"deps/key-value-storage",
12-
"deps/policy-engine"
12+
"deps/policy-engine",
13+
"wasm-components/dcap-qvl-wasi",
14+
"wasm-components/tdx-verifier-component",
15+
"wasm-components/tdx-verifier-test",
16+
"wasm-components/snp-verifier-component",
17+
"wasm-components/snp-verifier-test"
1318
]
1419
resolver = "2"
1520

@@ -77,3 +82,6 @@ tonic-prost = "0.14"
7782
tonic-prost-build = "0.14"
7883
tracing = "0.1.43"
7984
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
85+
86+
[patch.crates-io]
87+
dcap-qvl = { git = "https://github.com/Phala-Network/dcap-qvl.git", tag = "v0.3.6" }

attestation-service/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ tracing-subscriber.workspace = true
7272
uuid = { version = "1.19.0", features = ["v4"] }
7373
verifier = { path = "../deps/verifier", default-features = false }
7474
actix-cors = { version = "0.7", optional = true }
75+
wasmtime = { version = "25.0.0", features = ["component-model", "cache"] }
76+
wasmtime-wasi = "25.0.0"
77+
wasmtime-wasi-http = "25.0.0"
78+
wasmsign2 = "0.2.6"
7579

7680
[build-dependencies]
7781
shadow-rs.workspace = true

attestation-service/docs/config.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,25 @@ section:
1818
| `work_dir` | String | The location for Attestation Service to store data. | False | Firstly try to read from ENV `AS_WORK_DIR`. If not any, use `/opt/confidential-containers/attestation-service` |
1919
| `rvps_config` | [RVPSConfiguration][2] | RVPS configuration | False | - |
2020
| `attestation_token_broker` | [AttestationTokenBroker][1] | Attestation result token configuration. | False | - |
21+
| `wasm_verifier` | [WasmVerifier][3] | Host Wasm component verifiers. | False | Disabled |
2122

2223
[1]: #attestationtokenbroker
2324
[2]: #rvps-configuration
25+
[3]: #wasmverifier
26+
27+
#### WasmVerifier
28+
29+
This section is **optional**. When enabled, the Attestation Service can load and invoke platform-specific verifier logic as Wasm *components*.
30+
31+
| Property | Type | Description | Required | Default |
32+
|------------------------|-------------------|-------------|----------|---------|
33+
| `enabled` | Boolean | Enable Wasm component verifier hosting. | No | `false` |
34+
| `allow_unsigned` | Boolean | If `false`, uploaded components must be signed with `wasmsign2` and verify against `trusted_public_keys`. | No | `false` |
35+
| `trusted_public_keys` | Array of String | Paths to trusted public keys (raw/DER/PEM/OpenSSH) for verifying components. | No | `[]` |
36+
| `registry_dir` | String | Directory to store registered components; defaults to `<work_dir>/components`. | No | - |
37+
| `default_component_id` | String | Default verifier component ID to use when a request does not provide `verifier_component` or `verifier_component_id`. | No | - |
38+
| `wasi_cache_dir` | String | Directory pre-opened to components as `cache/` (for collateral caching); defaults to `<work_dir>/wasm-cache`. | No | - |
39+
| `wasmtime_cache_config`| String | Wasmtime cache config file path; defaults to `<work_dir>/wasmtime-cache.toml`. | No | - |
2440

2541
#### AttestationTokenBroker
2642

attestation-service/docs/restful-as.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ The value is a base64 encoded JWT. The body of the JWT is showed in the [example
5757

5858
More configuration items please refer to the [document](./config.md).
5959

60+
## Wasm Component Verifiers
61+
62+
This build can host platform-specific verifiers as Wasm *components* (Component Model) instead of native verifier drivers.
63+
64+
- `POST /component`: register a verifier component and get a `component_id` back.
65+
- `POST /attestation`: each `verification_request` can either embed a verifier component (`verifier_component`) or refer to a previously registered component (`verifier_component_id`).
66+
67+
Notes:
68+
- `verifier_component` is base64url (no pad) encoded raw `.wasm` component bytes.
69+
- If both `verifier_component` and `verifier_component_id` are provided, `verifier_component` takes precedence and is deduplicated/registered by hash.
70+
- If neither is provided, the service uses `wasm_verifier.default_component_id` (if configured) or rejects the request.
71+
6072
## Advanced Topics
6173

6274
### Building from Source

attestation-service/src/bin/grpc/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ impl AttestationService for Arc<RwLock<AttestationServer>> {
186186
runtime_data,
187187
runtime_data_hash_algorithm,
188188
init_data,
189+
verifier_component: None,
190+
verifier_component_id: None,
189191
});
190192
}
191193
let policy_ids = match request.policy_ids.is_empty() {

attestation-service/src/bin/restful-as.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tokio::sync::RwLock;
1616
use tracing::{debug, error, info};
1717
use tracing_subscriber::{fmt::Subscriber, EnvFilter};
1818

19-
use crate::restful::{attestation, get_challenge, get_policies, set_policy};
19+
use crate::restful::{attestation, get_challenge, get_policies, register_component, set_policy};
2020

2121
mod restful;
2222

@@ -61,6 +61,9 @@ enum WebApi {
6161

6262
#[strum(serialize = "/challenge")]
6363
Challenge,
64+
65+
#[strum(serialize = "/component")]
66+
Component,
6467
}
6568

6669
#[derive(Error, Debug)]
@@ -165,13 +168,16 @@ loglevel: {env_filter}
165168
let server = HttpServer::new(move || {
166169
App::new()
167170
.wrap(configure_cors(&allowed_origin))
171+
// Wasm verifier components can be a few MB; raise the default JSON payload limit.
172+
.app_data(web::JsonConfig::default().limit(32 * 1024 * 1024))
168173
.service(web::resource(WebApi::Attestation.as_ref()).route(web::post().to(attestation)))
169174
.service(
170175
web::resource(WebApi::Policy.as_ref())
171176
.route(web::post().to(set_policy))
172177
.route(web::get().to(get_policies)),
173178
)
174179
.service(web::resource(WebApi::Challenge.as_ref()).route(web::post().to(get_challenge)))
180+
.service(web::resource(WebApi::Component.as_ref()).route(web::post().to(register_component)))
175181
.app_data(web::Data::clone(&attestation_service))
176182
});
177183

attestation-service/src/bin/restful/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ pub struct AttestationRequest {
5353
pub struct IndividualAttestationRequest {
5454
tee: String,
5555
evidence: String,
56+
/// Base64url (no pad) encoded Wasm component bytes.
57+
verifier_component: Option<String>,
58+
/// Component ID returned by the Component Registration API.
59+
verifier_component_id: Option<String>,
5660
runtime_data: Option<RuntimeData>,
5761
init_data: Option<InitDataInput>,
5862
runtime_data_hash_algorithm: Option<String>,
@@ -150,6 +154,15 @@ pub async fn attestation(
150154
let evidence =
151155
serde_json::from_slice(&evidence).context("failed to parse evidence as JSON")?;
152156

157+
let verifier_component = match attestation_request.verifier_component.as_deref() {
158+
Some(s) => Some(
159+
URL_SAFE_NO_PAD
160+
.decode(s)
161+
.context("base64 decode verifier component")?,
162+
),
163+
None => None,
164+
};
165+
153166
let tee = to_tee(&attestation_request.tee)?;
154167

155168
let runtime_data = attestation_request
@@ -179,6 +192,8 @@ pub async fn attestation(
179192
runtime_data,
180193
runtime_data_hash_algorithm,
181194
init_data,
195+
verifier_component,
196+
verifier_component_id: attestation_request.verifier_component_id,
182197
});
183198
}
184199

@@ -200,6 +215,41 @@ pub async fn attestation(
200215
Ok(HttpResponse::Ok().body(token))
201216
}
202217

218+
#[derive(Debug, Deserialize)]
219+
pub struct RegisterComponentRequest {
220+
/// Base64url (no pad) encoded Wasm component bytes.
221+
verifier_component: String,
222+
}
223+
224+
#[derive(Debug, Serialize)]
225+
pub struct RegisterComponentResponse {
226+
component_id: String,
227+
}
228+
229+
#[instrument(skip_all, fields(request_id = tracing::field::Empty))]
230+
pub async fn register_component(
231+
request: web::Json<RegisterComponentRequest>,
232+
cocoas: web::Data<Arc<RwLock<AttestationService>>>,
233+
) -> Result<HttpResponse> {
234+
let request_id = Uuid::new_v4().to_string();
235+
Span::current().record("request_id", tracing::field::display(&request_id));
236+
info!("Component Registration API called.");
237+
238+
let request = request.into_inner();
239+
let component_bytes = URL_SAFE_NO_PAD
240+
.decode(&request.verifier_component)
241+
.context("base64 decode verifier component")?;
242+
243+
let component_id = cocoas
244+
.read()
245+
.await
246+
.register_component_verifier(&component_bytes)
247+
.await
248+
.context("register component verifier")?;
249+
250+
Ok(HttpResponse::Ok().json(RegisterComponentResponse { component_id }))
251+
}
252+
203253
#[derive(Deserialize, Debug)]
204254
pub struct SetPolicyInput {
205255
policy_id: String,

attestation-service/src/config.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,65 @@ pub struct Config {
2929
/// Optional configuration for verifier modules
3030
#[serde(default)]
3131
pub verifier_config: Option<VerifierConfig>,
32+
33+
/// Configuration for hosting Wasm component verifiers.
34+
#[serde(default)]
35+
pub wasm_verifier: WasmVerifierConfig,
36+
}
37+
38+
#[derive(Clone, Debug, Deserialize, PartialEq)]
39+
pub struct WasmVerifierConfig {
40+
/// Enable loading and invoking Wasm component verifiers.
41+
#[serde(default)]
42+
pub enabled: bool,
43+
44+
/// If `false`, Wasm components must carry a valid `wasmsign2` signature that
45+
/// matches one of the `trusted_public_keys`.
46+
#[serde(default)]
47+
pub allow_unsigned: bool,
48+
49+
/// Trusted public keys used to validate uploaded Wasm components.
50+
/// Keys can be in `wasmsign2` raw format, DER, PEM, or OpenSSH public key format.
51+
#[serde(default)]
52+
pub trusted_public_keys: Vec<PathBuf>,
53+
54+
/// Optional directory for storing registered components. When unset,
55+
/// defaults to `<work_dir>/components`.
56+
#[serde(default)]
57+
pub registry_dir: Option<PathBuf>,
58+
59+
/// Optional default component ID to use when a request does not include
60+
/// `verifier_component` (inline) or `verifier_component_id` (cache reference).
61+
///
62+
/// This enables gRPC/KBS callers (or legacy clients) to keep sending only
63+
/// evidence while the AS fetches the verifier component from its registry.
64+
#[serde(default)]
65+
pub default_component_id: Option<String>,
66+
67+
/// Optional directory pre-opened to Wasm components as `cache/` for
68+
/// collateral caching. When unset, defaults to `<work_dir>/wasm-cache`.
69+
#[serde(default)]
70+
pub wasi_cache_dir: Option<PathBuf>,
71+
72+
/// Optional Wasmtime cache config file path. When unset, defaults to
73+
/// `<work_dir>/wasmtime-cache.toml` (created automatically) and stores cache
74+
/// artifacts under `<work_dir>/wasmtime-cache/`.
75+
#[serde(default)]
76+
pub wasmtime_cache_config: Option<PathBuf>,
77+
}
78+
79+
impl Default for WasmVerifierConfig {
80+
fn default() -> Self {
81+
Self {
82+
enabled: false,
83+
allow_unsigned: false,
84+
trusted_public_keys: vec![],
85+
registry_dir: None,
86+
default_component_id: None,
87+
wasi_cache_dir: None,
88+
wasmtime_cache_config: None,
89+
}
90+
}
3291
}
3392

3493
fn default_work_dir() -> PathBuf {
@@ -55,6 +114,7 @@ impl Default for Config {
55114
rvps_config: RvpsConfig::default(),
56115
attestation_token_broker: EarTokenConfiguration::default(),
57116
verifier_config: None,
117+
wasm_verifier: WasmVerifierConfig::default(),
58118
}
59119
}
60120
}
@@ -115,6 +175,7 @@ mod tests {
115175
profile_name: "tag:github.com,2024:confidential-containers/Trustee".into()
116176
},
117177
verifier_config: None,
178+
wasm_verifier: crate::config::WasmVerifierConfig::default(),
118179
})]
119180
#[case("./tests/configs/example2.json", Config {
120181
work_dir: PathBuf::from("/var/lib/attestation-service/"),
@@ -136,6 +197,7 @@ mod tests {
136197
})
137198
},
138199
verifier_config: None,
200+
wasm_verifier: crate::config::WasmVerifierConfig::default(),
139201
})]
140202
fn read_config(#[case] config: &str, #[case] expected: Config) {
141203
let config = std::fs::read_to_string(config).unwrap();

0 commit comments

Comments
 (0)