use crate::imp::core::merkle::MerkleTree;
use crate::imp::core::MerkleProof;
pub fn emit_merkle_proof(tree: &MerkleTree, leaf_index: usize) -> MerkleProof {
tree.prove(leaf_index).unwrap_or_else(|| MerkleProof {
leaf: tree.root(),
path: alloc::vec::Vec::new(),
root: tree.root(),
})
}
use crate::imp::core::codec::Decode;
use crate::imp::core::serving::concat_output;
use crate::imp::core::{Bytes32, ContentResponse};
use crate::imp::guest::datasection::{DataSection, SectionId};
use crate::imp::guest::decoy::decoy_content_response;
use crate::imp::guest::host::DigHost;
use crate::imp::guest::oblivious::build_access_plan;
use crate::imp::guest::request::ContentRequest;
use crate::imp::guest::temporal::within_window;
use alloc::vec::Vec;
pub struct GateConfig {
pub require_attestation: bool,
pub require_jwt: bool,
pub expected_iss: Option<alloc::string::String>,
pub expected_aud: Option<alloc::string::String>,
}
impl GateConfig {
pub fn from_embedded(ds: &DataSection) -> GateConfig {
let require_jwt = embedded_auth_info(ds)
.map(|i| i.requires_jwt)
.unwrap_or(false);
GateConfig {
require_attestation: false,
require_jwt,
expected_iss: None,
expected_aud: None,
}
}
}
pub enum ContentOutcome {
Real(ContentResponse),
Decoy(ContentResponse),
}
fn read_chunk(ds: &DataSection, index: u32) -> Option<Vec<u8>> {
let pool = ds.section(SectionId::ChunkPool)?;
if pool.len() < 4 {
return None;
}
let count = u32::from_be_bytes([pool[0], pool[1], pool[2], pool[3]]);
if index >= count {
return None;
}
let mut p = 4usize;
for i in 0..count {
if p + 4 > pool.len() {
return None;
}
let len = u32::from_be_bytes([pool[p], pool[p + 1], pool[p + 2], pool[p + 3]]) as usize;
p += 4;
if p + len > pool.len() {
return None;
}
if i == index {
return Some(pool[p..p + len].to_vec());
}
p += len;
}
None
}
fn embedded_trusted_set(ds: &DataSection) -> crate::imp::guest::attestation::TrustedSet {
use crate::imp::core::codec::Decoder;
let body = match ds.section(SectionId::TrustedKeys) {
Some(b) => b,
None => return crate::imp::guest::attestation::TrustedSet::from_pubkeys(&[]),
};
let mut dec = Decoder::new(body);
let count = match u32::decode(&mut dec) {
Ok(c) => c,
Err(_) => return crate::imp::guest::attestation::TrustedSet::from_pubkeys(&[]),
};
let mut keys: Vec<[u8; 48]> = Vec::new();
for _ in 0..count {
let pk = match <[u8; 48]>::decode(&mut dec) {
Ok(p) => p,
Err(_) => break,
};
if alloc::string::String::decode(&mut dec).is_err() {
break;
}
keys.push(pk);
}
crate::imp::guest::attestation::TrustedSet::from_pubkeys(&keys)
}
fn gate<H: DigHost + ?Sized>(
host: &H,
ds: &DataSection,
req: &ContentRequest,
cfg: &GateConfig,
) -> Result<(), ()> {
if !crate::imp::guest::obfuscation_hooks::opaque_true() {
return Err(());
}
if !within_window(&req.window, host.current_time()) {
return Err(());
}
if cfg.require_attestation {
let nonce = host.random_bytes(32).map_err(|_| ())?;
if nonce.len() < 32 {
return Err(());
}
let mut nonce32 = [0u8; 32];
nonce32.copy_from_slice(&nonce[..32]);
let signed_time = host.current_time();
let challenge =
crate::imp::guest::attestation::build_challenge(nonce32, ds.store_id().0, signed_time);
let resp_bytes = host.create_attestation(&challenge).map_err(|_| ())?;
let resp =
crate::imp::core::AttestationResponse::from_bytes(&resp_bytes).map_err(|_| ())?;
let now = host.current_time();
let trusted = embedded_trusted_set(ds);
crate::imp::guest::attestation::verify_attestation(
&trusted,
&challenge,
&resp.host_public_key,
&resp.signature,
signed_time,
now,
)
.map_err(|_| ())?;
}
if cfg.require_jwt {
if !host.verify_session() {
return Err(());
}
let jwt = req.jwt.as_ref().ok_or(())?;
let policy = crate::imp::guest::jwt::ClaimPolicy {
now: host.current_time(),
expected_iss: cfg.expected_iss.as_deref(),
expected_aud: cfg.expected_aud.as_deref(),
};
let jwks_url = embedded_jwks_url(ds).ok_or(())?;
if verify_request_jwt(host, jwks_url.as_bytes(), jwt, &policy).is_err() {
return Err(());
}
}
Ok(())
}
enum GatheredOutcome {
Real {
gathered: Vec<Vec<u8>>,
real_positions: Vec<usize>,
merkle_proof: MerkleProof,
root: Bytes32,
},
Decoy(ContentResponse),
}
fn gather_content<H: DigHost + ?Sized>(
host: &H,
ds: &DataSection,
req: &ContentRequest,
cfg: &GateConfig,
) -> GatheredOutcome {
let root = req.root_hash.unwrap_or_else(|| ds.current_root());
if gate(host, ds, req, cfg).is_err() {
return GatheredOutcome::Decoy(decoy_content_response(&req.retrieval_key, &root));
}
let entry = match ds.lookup_key(&req.retrieval_key) {
Some(e) => e,
None => return GatheredOutcome::Decoy(decoy_content_response(&req.retrieval_key, &root)),
};
let pool = ds.section(SectionId::ChunkPool).unwrap_or(&[]);
let pool_size = if pool.len() >= 4 {
u32::from_be_bytes([pool[0], pool[1], pool[2], pool[3]])
} else {
0
};
let plan = build_access_plan(&entry.chunk_indices, pool_size, |c| {
host.random_bytes(c)
.unwrap_or_else(|_| alloc::vec![0u8; c as usize])
});
let mut gathered: Vec<Vec<u8>> = Vec::with_capacity(plan.order.len());
for idx in &plan.order {
gathered.push(read_chunk(ds, *idx).unwrap_or_default());
}
let merkle_proof = build_real_proof(ds, &entry, &root);
GatheredOutcome::Real {
gathered,
real_positions: plan.real_positions,
merkle_proof,
root,
}
}
pub fn serve_content<H: DigHost + ?Sized>(
host: &H,
ds: &DataSection,
req: &ContentRequest,
cfg: &GateConfig,
) -> ContentOutcome {
match gather_content(host, ds, req, cfg) {
GatheredOutcome::Decoy(resp) => ContentOutcome::Decoy(resp),
GatheredOutcome::Real {
gathered,
real_positions,
merkle_proof,
root,
} => {
let real_slices: Vec<&[u8]> = real_positions
.iter()
.map(|pos| gathered[*pos].as_slice())
.collect();
let chunk_lens: Vec<u32> = real_slices.iter().map(|s| s.len() as u32).collect();
let ciphertext = concat_output(&real_slices);
ContentOutcome::Real(ContentResponse {
ciphertext,
merkle_proof,
roothash: root,
chunk_lens,
})
}
}
}
pub fn serve_content_wire<H: DigHost + ?Sized>(
host: &H,
ds: &DataSection,
req: &ContentRequest,
cfg: &GateConfig,
) -> Vec<u8> {
use crate::imp::core::codec::{Encode, Encoder};
match gather_content(host, ds, req, cfg) {
GatheredOutcome::Decoy(resp) => {
let mut enc = Encoder::new();
resp.encode(&mut enc);
enc.finish()
}
GatheredOutcome::Real {
gathered,
real_positions,
merkle_proof,
root,
} => {
let mut tail = Encoder::new();
merkle_proof.encode(&mut tail);
root.encode(&mut tail);
let chunk_lens: Vec<u32> = real_positions
.iter()
.map(|p| gathered[*p].len() as u32)
.collect();
chunk_lens.encode(&mut tail);
let tail = tail.finish();
let cipher_len: usize = real_positions.iter().map(|p| gathered[*p].len()).sum();
let mut enc = Encoder::with_capacity(4 + cipher_len + tail.len());
(cipher_len as u32).encode(&mut enc); for pos in &real_positions {
enc.write_bytes(gathered[*pos].as_slice()); }
enc.write_bytes(&tail);
enc.finish()
}
}
}
fn build_real_proof(
ds: &DataSection,
entry: &crate::imp::core::KeyTableEntry,
root: &Bytes32,
) -> MerkleProof {
use crate::imp::core::datasection::decode_merkle_leaves;
let leaves = ds
.section(SectionId::MerkleNodes)
.and_then(|body| decode_merkle_leaves(body).ok());
match leaves {
Some(leaves) if !leaves.is_empty() => {
let leaf_index = resource_leaf_index(ds, &entry.static_key);
let tree = MerkleTree::from_leaves(leaves);
tree.prove(leaf_index).unwrap_or_else(|| MerkleProof {
leaf: tree.root(),
path: alloc::vec::Vec::new(),
root: tree.root(),
})
}
_ => {
let leaf = *root;
MerkleProof {
leaf,
path: alloc::vec::Vec::new(),
root: *root,
}
}
}
}
fn resource_leaf_index(ds: &DataSection, served_key: &Bytes32) -> usize {
let body = match ds.section(SectionId::KeyTable) {
Some(b) => b,
None => return 0,
};
let mut dec = crate::imp::core::codec::Decoder::new(body);
let count = match u32::decode(&mut dec) {
Ok(c) => c,
Err(_) => return 0,
};
let mut rank = 0usize;
for _ in 0..count {
match crate::imp::core::KeyTableEntry::decode(&mut dec) {
Ok(e) => {
if e.static_key.0 < served_key.0 {
rank += 1;
}
}
Err(_) => break,
}
}
rank
}
fn embedded_auth_info(ds: &DataSection) -> Option<crate::imp::core::AuthenticationInfo> {
use crate::imp::core::codec::Decoder;
let body = ds.section(SectionId::AuthInfo)?;
let mut dec = Decoder::new(body);
crate::imp::core::AuthenticationInfo::decode(&mut dec).ok()
}
fn embedded_jwks_url(ds: &DataSection) -> Option<alloc::string::String> {
embedded_auth_info(ds).and_then(|i| i.jwks_url)
}
pub fn verify_request_jwt<H: DigHost + ?Sized>(
host: &H,
jwks_url: &[u8],
jwt: &[u8],
policy: &crate::imp::guest::jwt::ClaimPolicy,
) -> Result<(), crate::imp::guest::jwt::JwtError> {
let parts = crate::imp::guest::jwt::decode_unverified(jwt)?;
crate::imp::guest::jwt::check_claims(&parts.claims, policy)?;
let jwks_bytes = crate::imp::guest::session::gated_jwks_fetch(host, jwks_url)
.map_err(|_| crate::imp::guest::jwt::JwtError::UnknownKey)?;
let jwks = crate::imp::guest::jwt::parse_jwks(&jwks_bytes)?;
let jwk = match parts.kid.as_deref() {
Some(kid) => jwks
.iter()
.find(|k| k.kid == kid)
.ok_or(crate::imp::guest::jwt::JwtError::UnknownKey)?,
None => match jwks.as_slice() {
[only] => only,
_ => return Err(crate::imp::guest::jwt::JwtError::UnknownKey),
},
};
crate::imp::guest::jwt::verify_signature(
&parts.alg,
jwk,
&parts.signing_input,
&parts.signature,
)
}