Skip to main content

quartz_contract_core/handler/execute/
attested.rs

1use ciborium::{from_reader as from_cbor_slice, into_writer as into_cbor, Value as CborValue};
2use cosmwasm_std::{
3    to_json_binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Response, StdResult, WasmQuery,
4};
5use quartz_dcap_verifier_msgs::QueryMsg as DcapVerifierQueryMsg;
6use quartz_tcbinfo_msgs::{GetTcbInfoResponse, QueryMsg as TcbInfoQueryMsg};
7use quartz_tee_ra::{
8    intel_sgx::dcap::{Collateral, TrustedIdentity, TrustedMrEnclaveIdentity},
9    Error as RaVerificationError,
10};
11use serde::{de::DeserializeOwned, Serialize};
12
13use crate::{
14    error::Error,
15    handler::Handler,
16    msg::execute::attested::{
17        Attestation, Attested, DcapAttestation, HasUserData, MockAttestation, Noop, Quote,
18    },
19    state::CONFIG,
20};
21
22fn query_contract<T: DeserializeOwned>(
23    deps: Deps<'_>,
24    contract_addr: String,
25    query_msg: impl Serialize,
26) -> StdResult<T> {
27    let request = QueryRequest::Wasm(WasmQuery::Smart {
28        contract_addr,
29        msg: to_json_binary(&query_msg)?,
30    });
31
32    deps.querier.query(&request)
33}
34
35fn query_tcbinfo(deps: Deps<'_>, fmspc: String) -> Result<GetTcbInfoResponse, Error> {
36    let tcbinfo_addr = {
37        let config = CONFIG.load(deps.storage).map_err(Error::Std)?;
38        config
39            .tcbinfo_contract()
40            .expect("TcbInfo contract address is required for DCAP")
41            .to_string()
42    };
43
44    let fmspc_bytes =
45        hex::decode(&fmspc).map_err(|_| Error::InvalidFmspc("Invalid FMSPC format".to_string()))?;
46    if fmspc_bytes.len() != 6 {
47        return Err(Error::InvalidFmspc("FMSPC must be 6 bytes".to_string()));
48    }
49
50    let query_msg = TcbInfoQueryMsg::GetTcbInfo { fmspc };
51
52    query_contract(deps, tcbinfo_addr, &query_msg)
53        .map_err(|err| Error::TcbInfoQueryError(err.to_string()))
54}
55
56fn to_cbor_vec<T: Serialize>(value: &T) -> Vec<u8> {
57    let mut buffer = Vec::new();
58    into_cbor(&value, &mut buffer).expect("Serialization failed");
59    buffer
60}
61
62fn query_dcap_verifier(
63    deps: Deps<'_>,
64    quote: Quote,
65    mr_enclave: impl Into<TrustedIdentity>,
66    updated_collateral: Collateral,
67) -> Result<(), Error> {
68    let query_msg = DcapVerifierQueryMsg::VerifyDcapAttestation {
69        quote: quote.as_ref().to_vec().into(),
70        collateral: to_cbor_vec(&updated_collateral).into(),
71        identities: Some(to_cbor_vec(&[mr_enclave.into()])),
72    };
73
74    let dcap_verifier_contract = {
75        let config = CONFIG.load(deps.storage).map_err(Error::Std)?;
76        config
77            .dcap_verifier_contract()
78            .expect("verifier_contract address is required for DCAP")
79            .to_string()
80    };
81
82    query_contract(deps, dcap_verifier_contract, &query_msg)
83        .map_err(|err| Error::DcapVerificationQueryError(err.to_string()))
84}
85
86impl Handler for DcapAttestation {
87    fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> {
88        let (quote, collateral) = self.clone().into_tuple();
89        let mr_enclave = TrustedMrEnclaveIdentity::new(
90            self.mr_enclave().into(),
91            [""; 0],
92            ["INTEL-SA-00334", "INTEL-SA-00615"],
93        );
94
95        // Retrieve the FMSPC from the collateral
96        let fmspc_hex = collateral.tcb_info().to_string();
97
98        // Query the tcbinfo contract with the FMSPC retrieved and validated
99        let tcb_info_response = query_tcbinfo(deps.as_ref(), fmspc_hex)?;
100
101        // Serialize the existing collateral
102        let collateral_serialized = to_cbor_vec(&collateral);
103        let mut collateral_value: CborValue = from_cbor_slice(collateral_serialized.as_slice())
104            .map_err(|e| {
105                Error::TcbInfoQueryError(format!("Failed to serialize collateral: {}", e))
106            })?;
107
108        // Update the tcb_info in the serialized data
109        fn try_get_tcb_info(collateral_value: &mut CborValue) -> Option<&mut CborValue> {
110            if let CborValue::Map(map) = collateral_value {
111                return map
112                    .iter_mut()
113                    .find(|(k, _)| k == &CborValue::Text("tcb_info".to_string()))
114                    .map(|(_, v)| v);
115            }
116            None
117        }
118
119        let tcb_info_value = try_get_tcb_info(&mut collateral_value).expect("infallible serde");
120        *tcb_info_value = CborValue::Text(tcb_info_response.tcb_info.to_string());
121
122        // Deserialize back into a Collateral
123        let collateral_serialized = to_cbor_vec(&collateral_value);
124        let updated_collateral: Collateral = from_cbor_slice(collateral_serialized.as_slice())
125            .map_err(|e| {
126                Error::TcbInfoQueryError(format!("Failed to deserialize updated collateral: {}", e))
127            })?;
128
129        query_dcap_verifier(deps.as_ref(), quote, mr_enclave, updated_collateral)
130            .map(|_| Response::default())
131    }
132}
133
134impl Handler for MockAttestation {
135    fn handle(
136        self,
137        _deps: DepsMut<'_>,
138        _env: &Env,
139        _info: &MessageInfo,
140    ) -> Result<Response, Error> {
141        Ok(Response::default())
142    }
143}
144
145impl<M, A> Handler for Attested<M, A>
146where
147    M: Handler + HasUserData,
148    A: Handler + HasUserData + Attestation,
149{
150    fn handle(
151        self,
152        mut deps: DepsMut<'_>,
153        env: &Env,
154        info: &MessageInfo,
155    ) -> Result<Response, Error> {
156        let (msg, attestation) = self.into_tuple();
157        if msg.user_data() != attestation.user_data() {
158            return Err(RaVerificationError::UserDataMismatch.into());
159        }
160
161        if let Some(config) = CONFIG.may_load(deps.storage)? {
162            // if we weren't able to load then the context was from InstantiateMsg so we don't fail
163            // in such cases, the InstantiateMsg handler will verify that the mr_enclave matches
164            if config.mr_enclave() != attestation.mr_enclave() {
165                return Err(RaVerificationError::MrEnclaveMismatch.into());
166            }
167        }
168
169        // handle message first, this has 2 benefits -
170        // 1. we avoid (the more expensive) attestation verification if the message handler fails
171        // 2. we allow the message handler to make changes to the config so that the attestation
172        //    handler can use those changes, e.g. InstantiateMsg
173        // return response from msg handle to include pub_key attribute
174        let res_msg = Handler::handle(msg, deps.branch(), env, info)?;
175        let res_attest = Handler::handle(attestation, deps, env, info)?;
176
177        Ok(res_msg
178            .add_events(res_attest.events)
179            .add_attributes(res_attest.attributes))
180    }
181}
182
183impl<T> Handler for Noop<T> {
184    fn handle(
185        self,
186        _deps: DepsMut<'_>,
187        _env: &Env,
188        _info: &MessageInfo,
189    ) -> Result<Response, Error> {
190        Ok(Response::default())
191    }
192}