Skip to content

Commit 35d700e

Browse files
nicklaslclaude
andcommitted
feat(wasm): add encrypted CDN state decryption support
Add AES-256-GCM decryption to the WASM guest layer so providers can consume encrypted CDN state blobs. - New proto message `SetEncryptedResolverStateRequest` - New guest export `wasm_msg_guest_set_encrypted_resolver_state` - `aes-gcm` crate (~42K WASM binary increase) - Encrypted test fixture for e2e tests - Go WASM binary synced Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e2ecc6e commit 35d700e

8 files changed

Lines changed: 150 additions & 1 deletion

File tree

Cargo.lock

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

data/encryption_key_test.hex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
342b666ceb14fb94ed1d7da0a85acb529ca2a244c3193c6b02251044a454f4a2

data/resolver_state_encrypted.pb

240 KB
Binary file not shown.
Binary file not shown.

openfeature-provider/proto/confidence/wasm/messages.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ message SetResolverStateRequest {
1717
confidence.flags.resolver.v1.Sdk sdk = 3;
1818
}
1919

20+
message SetEncryptedResolverStateRequest {
21+
bytes encrypted_state = 1;
22+
bytes encryption_key = 2;
23+
confidence.flags.resolver.v1.Sdk sdk = 3;
24+
}
25+
2026
message Request {
2127
bytes data = 1;
2228
}

wasm/proto/messages.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ message SetResolverStateRequest {
2121
Sdk sdk = 3;
2222
}
2323

24+
message SetEncryptedResolverStateRequest {
25+
bytes encrypted_state = 1;
26+
bytes encryption_key = 2;
27+
Sdk sdk = 3;
28+
}
29+
2430
message ResolveSimpleRequest {
2531
string client_secret = 1;
2632
google.protobuf.Struct evaluation_context = 2;

wasm/rust-guest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ prost-types = { version = "0.12", default-features = false }
1515
# TODO re-export Bytes
1616
bytes = { version = "1.4.0", default-features = false }
1717
arc-swap = "1.7.1"
18+
aes-gcm = { version = "0.10", default-features = false, features = ["aes", "alloc"] }
1819

1920
[build-dependencies]
2021
prost-build = "0.12"

wasm/rust-guest/src/lib.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use wasm_msg::WasmResult;
2121
pub mod proto {
2222
include!(concat!(env!("OUT_DIR"), "/rust_guest.rs"));
2323
}
24-
use crate::proto::SetResolverStateRequest;
24+
use crate::proto::{SetEncryptedResolverStateRequest, SetResolverStateRequest};
2525
use confidence_resolver::{
2626
proto::{
2727
confidence::flags::admin::v1::ResolverState as ResolverStatePb,
@@ -140,6 +140,39 @@ wasm_msg_guest! {
140140
Ok(VOID)
141141
}
142142

143+
fn set_encrypted_resolver_state(request: SetEncryptedResolverStateRequest) -> WasmResult<Void> {
144+
use aes_gcm::{Aes256Gcm, Nonce, KeyInit};
145+
use aes_gcm::aead::Aead;
146+
147+
if request.encryption_key.len() != 32 {
148+
return Err(format!(
149+
"Encryption key must be 32 bytes (AES-256), got {}",
150+
request.encryption_key.len()
151+
));
152+
}
153+
154+
let cipher = Aes256Gcm::new_from_slice(&request.encryption_key)
155+
.map_err(|e| format!("Invalid encryption key: {}", e))?;
156+
157+
if request.encrypted_state.len() < 12 {
158+
return Err("Encrypted state too short (missing nonce)".to_string());
159+
}
160+
161+
let nonce = Nonce::from_slice(&request.encrypted_state[..12]);
162+
let plaintext = cipher
163+
.decrypt(nonce, &request.encrypted_state[12..])
164+
.map_err(|_| "Failed to decrypt resolver state: invalid key or corrupted data".to_string())?;
165+
166+
let inner = SetResolverStateRequest::decode(plaintext.as_slice())
167+
.map_err(|e| format!("Failed to decode decrypted resolver state: {}", e))?;
168+
169+
let state_pb = ResolverStatePb::decode(inner.state.as_slice())
170+
.map_err(|e| format!("Failed to decode resolver state: {}", e))?;
171+
let new_state = ResolverState::from_proto(state_pb, inner.account_id.as_str(), request.sdk)?;
172+
RESOLVER_STATE.store(Some(Arc::new(new_state)));
173+
Ok(VOID)
174+
}
175+
143176
fn resolve_flags(request: ResolveProcessRequest) -> WasmResult<ResolveProcessResponse> {
144177
let resolver_state = get_resolver_state()?;
145178

0 commit comments

Comments
 (0)