aesm_client/
lib.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6//! # Features
7//!
8//! * `sgxs`. Enable the `sgxs` feature to get an implemention of
9//!   `EinittokenProvider` that uses AESM.
10
11#![doc(html_logo_url = "https://edp.fortanix.com/img/docs/edp-logo.svg",
12       html_favicon_url = "https://edp.fortanix.com/favicon.ico",
13       html_root_url = "https://edp.fortanix.com/docs/api/")]
14#![allow(non_local_definitions)] // Required by failure
15/* The latest rust compiler has removed the lint box_pointers. This lint
16 * is used and generated by protobuf which is a dependency in this project.
17 * In order to avoid CI failures, allow the below lint. */
18#![allow(renamed_and_removed_lints)]
19#![deny(warnings)]
20// To fix this error, which appears in generated code:
21// "lint `box_pointers` has been removed: it does not detect other kinds of allocations, and existed only for historical reasons."
22#![allow(renamed_and_removed_lints)]
23// (nightly only) To fix this error appears in generated code:
24// `error: lifetime flowing from input to output with different syntax can be confusing`
25#![allow(unknown_lints)]
26#![allow(mismatched_lifetime_syntaxes)]
27
28extern crate byteorder;
29pub extern crate anyhow;
30pub extern crate thiserror;
31#[macro_use]
32#[cfg(unix)]
33extern crate lazy_static;
34extern crate protobuf;
35#[cfg(feature = "sgxs")]
36extern crate sgxs;
37#[cfg(unix)]
38extern crate unix_socket;
39#[cfg(windows)]
40extern crate winapi;
41extern crate sgx_isa;
42
43use std::convert::TryFrom;
44#[cfg(feature = "sgxs")]
45use std::result::Result as StdResult;
46
47#[cfg(feature = "sgxs")]
48use sgxs::einittoken::{Einittoken, EinittokenProvider};
49#[cfg(all(not(target_env = "sgx"),feature = "sgxs"))]
50use sgx_isa::{Attributes, Sigstruct};
51
52use protobuf::{MessageField, Result as ProtobufResult};
53include!(concat!(env!("OUT_DIR"), "/protos/mod_aesm_proto.rs"));
54mod error;
55use self::aesm_proto::*;
56pub use error::{AesmError, Error, Result};
57#[cfg(windows)]
58#[path = "imp/windows.rs"]
59mod imp;
60#[cfg(unix)]
61#[path = "imp/unix.rs"]
62mod imp;
63#[cfg(target_env = "sgx")]
64#[path = "imp/sgx.rs"]
65mod imp;
66#[cfg(unix)]
67pub mod unix {
68    use std::path::Path;
69    pub trait AesmClientExt {
70        fn with_path<P: AsRef<Path>>(path: P) -> Self;
71    }
72}
73
74#[cfg(target_env = "sgx")]
75pub mod sgx {
76    use std::net::TcpStream;
77    pub trait AesmClientExt {
78        fn new(tcp_stream: TcpStream) -> Self;
79    }
80}
81
82use request::{GetQuoteExRequest, GetQuoteRequest, GetQuoteSizeExRequest, GetLaunchTokenRequest, GetSupportedAttKeyIDNumRequest, GetSupportedAttKeyIDsRequest, InitQuoteExRequest, InitQuoteRequest};
83use response::{GetSupportedAttKeyIDNumResponse, GetSupportedAttKeyIDsResponse, GetLaunchTokenResponse, GetQuoteSizeExResponse, GetQuoteExResponse, GetQuoteResponse, InitQuoteExResponse, InitQuoteResponse};
84
85// From SDK aesm_error.h
86const AESM_SUCCESS: u32 = 0;
87
88// From SDK sgx_quote.h
89#[repr(u32)]
90pub enum QuoteType {
91    Unlinkable = 0,
92    Linkable = 1,
93}
94
95impl Into<u32> for QuoteType {
96    fn into(self: QuoteType) -> u32 {
97        use self::QuoteType::*;
98        match self {
99            Unlinkable => 0,
100            Linkable => 1,
101        }
102    }
103}
104
105impl QuoteType {
106    pub fn from_u32(v: u32) -> Result<Self> {
107        use self::QuoteType::*;
108        Ok(match v {
109            0 => Unlinkable,
110            1 => Linkable,
111            _ => return Err(Error::InvalidQuoteType(v)),
112        })
113    }
114}
115
116#[derive(Debug)]
117pub struct QuoteInfo {
118    target_info: Vec<u8>,
119    pub_key_id: Vec<u8>,
120}
121
122impl QuoteInfo {
123    pub fn target_info(&self) -> &[u8] {
124        &self.target_info
125    }
126
127    /// EPID only: EPID group ID, big-endian byte order
128    pub fn gid(&self) -> Vec<u8> {
129        // AESM gives it to us little-endian, we want big-endian for writing into IAS URL with to_hex()
130        let mut pk = self.pub_key_id.clone();
131        pk.reverse();
132        pk
133    }
134
135    pub fn pub_key_id(&self) -> &[u8] {
136        &self.pub_key_id
137    }
138}
139
140// The value returned here can depend on number of sigrl entries, and
141// possibly other factors. Although why the client needs to pass a length
142// in a protobuf API is beyond me.
143fn quote_buffer_size(sig_rl: &[u8]) -> u32 {
144    // Refer to se_quote_internal.h and sgx_quote.h in the Intel SDK.
145    let quote_length = 436 + 288 + 12 + 4 + 16;
146
147    // Refer to epid/common/types.h in the Intel SDK.
148    // This is the truly correct way to compute sig_length:
149    //let nr_proof_length = 160;
150    //let sig_length = 352 + 4 + 4 + sig_rl_entries * nr_proof_length;
151    // Instead we do something that should be conservative, and doesn't
152    // require interpreting the sig_rl structure to determine the entry
153    // count. An nr_proof is 5 field elements, a sig_rl entry is four.
154    // Add some slop for sig_rl headers.
155    let sig_length = 352 + 4 + 4 + (sig_rl.len() as u32 * 5 / 4) + 128;
156
157    quote_length + sig_length
158}
159
160#[derive(Debug, Clone, Eq, PartialEq)]
161pub struct QuoteResult {
162    /// For Intel attestations, the EPID signature from Intel QE.
163    quote: Vec<u8>,
164
165    /// SGX report (EREPORT) from the Intel quoting enclave for the quote.
166    qe_report: Vec<u8>,
167}
168
169impl QuoteResult {
170    pub fn new<T: Into<Vec<u8>>, U: Into<Vec<u8>>>(quote: T, qe_report: U) -> Self {
171        QuoteResult {
172            quote: quote.into(),
173            qe_report: qe_report.into(),
174        }
175    }
176
177    pub fn quote(&self) -> &[u8] {
178        &self.quote
179    }
180
181    pub fn qe_report(&self) -> &[u8] {
182        &self.qe_report
183    }
184}
185
186#[cfg_attr(not(target_env = "sgx"), derive(Default))]
187#[derive(Debug, Clone)]
188pub struct AesmClient {
189    inner: imp::AesmClient
190}
191
192
193impl AesmClient {
194    #[cfg(not(target_env = "sgx"))]
195    pub fn new() -> Self {
196        AesmClient { inner: imp::AesmClient::new() }
197    }
198
199    /// Test the connection with AESM.
200    ///
201    /// This should only be used for diagnostic purposes. This method returning
202    /// `Ok` is not a guarantee that any of the other methods will function
203    /// correctly.
204    pub fn try_connect(&self) -> Result<()> {
205        self.inner.try_connect()
206    }
207
208    /// Obtain target info from QE.
209    pub fn init_quote(&self) -> Result<QuoteInfo> {
210        self.inner.init_quote()
211    }
212
213    /// Obtain remote attestation quote from QE.
214    pub fn get_quote(
215        &self,
216        report: Vec<u8>,
217        spid: Vec<u8>,
218        sig_rl: Vec<u8>,
219        quote_type: QuoteType,
220        nonce: Vec<u8>,
221    ) -> Result<QuoteResult> {
222        self.inner.get_quote(
223            report,
224            spid,
225            sig_rl,
226            quote_type,
227            nonce,
228        )
229    }
230
231    #[cfg(all(not(target_env = "sgx"), feature = "sgxs"))]
232    pub fn get_launch_token(
233        &self,
234        sigstruct: &Sigstruct,
235        attributes: Attributes,
236    ) -> Result<Vec<u8>> {
237        self.inner.get_launch_token(
238            sigstruct,
239            attributes,
240        )
241    }
242
243    /// Returns all keys supported by AESM service.
244    #[cfg(not(windows))]
245    pub fn get_supported_att_key_ids(&self) -> Result<Vec<Vec<u8>>> {
246        self.inner.get_supported_att_key_ids()
247    }
248
249    /// Obtain target info from QE.
250    ///
251    /// Like `init_quote`, but allows specifying the attestation key id.
252    #[cfg(not(windows))]
253    pub fn init_quote_ex(&self, att_key_id: Vec<u8>) -> Result<QuoteInfo> {
254        self.inner.init_quote_ex(att_key_id)
255    }
256
257    /// Obtain remote attestation quote from QE.
258    ///
259    /// Like `get_quote`, but allows specifying the attestation key id.
260    ///
261    /// If `target_info` is not supplied, it's determined from `report` so that
262    /// the quote may be verified by the enclave it's for.
263    #[cfg(not(windows))]
264    pub fn get_quote_ex(
265        &self,
266        att_key_id: Vec<u8>,
267        report: Vec<u8>,
268        target_info: Option<Vec<u8>>,
269        nonce: Vec<u8>
270    ) -> Result<QuoteResult> {
271        let target_info = target_info.unwrap_or_else( ||
272            AsRef::<[u8]>::as_ref(&sgx_isa::Targetinfo::from(sgx_isa::Report::try_copy_from(&report).unwrap()))
273                .to_owned()
274        );
275        self.inner.get_quote_ex(att_key_id, report, target_info, nonce)
276    }
277}
278
279#[cfg(feature = "sgxs")]
280impl EinittokenProvider for AesmClient {
281    fn token(
282        &mut self,
283        sigstruct: &Sigstruct,
284        attributes: Attributes,
285        _retry: bool,
286    ) -> StdResult<Einittoken, ::anyhow::Error> {
287        let token = self.get_launch_token(
288            sigstruct,
289            attributes,
290        )?;
291        Einittoken::try_copy_from(&token).ok_or(Error::InvalidTokenSize.into())
292    }
293
294    fn can_retry(&self) -> bool {
295        false
296    }
297}
298
299trait AesmRequest: protobuf::Message + Into<Request> {
300    type Response: protobuf::Message + TryFrom<ProtobufResult<Response>, Error = Error>;
301
302    #[cfg(not(target_env = "sgx"))]
303    fn get_timeout(&self) -> Option<u32>;
304}
305
306macro_rules! define_aesm_message {
307    ($request:ident, $req_field:ident, $response:ident, $resp_field:ident) => {
308        impl AesmRequest for $request {
309            type Response = $response;
310
311            #[cfg(not(target_env = "sgx"))]
312            fn get_timeout(&self) -> Option<u32> {
313                if self.has_timeout() {
314                    Some($request::timeout(self))
315                } else {
316                    None
317                }
318            }
319        }
320        impl From<$request> for Request {
321            fn from(r: $request) -> Request {
322                let mut req = Request::new();
323                req.$req_field = Some(r).into();
324                req
325            }
326        }
327        impl TryFrom<ProtobufResult<Response>> for $response {
328            type Error = Error;
329
330            fn try_from(res: ProtobufResult<Response>) -> Result<Self> {
331                if let Ok(Response { $resp_field: MessageField(Some(body)), .. }) = res {
332                    match body.errorCode() {
333                        AESM_SUCCESS => Ok(*body),
334                        code => Err(Error::aesm_code(code)),
335                    }
336                } else {
337                    Err(Error::aesm_bad_response(stringify!($response)))
338                }
339            }
340        }
341    }
342}
343
344define_aesm_message!(GetQuoteRequest, getQuoteReq, GetQuoteResponse, getQuoteRes);
345define_aesm_message!(InitQuoteRequest, initQuoteReq, InitQuoteResponse, initQuoteRes);
346define_aesm_message!(GetLaunchTokenRequest, getLicTokenReq, GetLaunchTokenResponse, getLicTokenRes);
347define_aesm_message!(GetQuoteExRequest, getQuoteExReq, GetQuoteExResponse, getQuoteExRes);
348define_aesm_message!(InitQuoteExRequest, initQuoteExReq, InitQuoteExResponse, initQuoteExRes);
349define_aesm_message!(GetQuoteSizeExRequest, getQuoteSizeExReq, GetQuoteSizeExResponse, getQuoteSizeExRes);
350define_aesm_message!(GetSupportedAttKeyIDNumRequest, getSupportedAttKeyIDNumReq, GetSupportedAttKeyIDNumResponse, getSupportedAttKeyIDNumRes);
351define_aesm_message!(GetSupportedAttKeyIDsRequest, getSupportedAttKeyIDsReq, GetSupportedAttKeyIDsResponse, getSupportedAttKeyIDsRes);
352
353#[cfg(all(test, feature = "test-sgx"))]
354mod tests {
355    // These tests require that aesmd is running and correctly configured.
356    extern crate sgx_isa;
357
358    use self::sgx_isa::{Report, Targetinfo};
359    use super::*;
360
361    const SPID_SIZE: usize = 16;
362    const NONCE_SIZE: usize = 16;
363
364    #[test]
365    fn test_init_quote() {
366        let quote = AesmClient::new().init_quote().unwrap();
367        assert_eq!(
368            quote.target_info().len(),
369            ::std::mem::size_of::<Targetinfo>()
370        );
371        assert!(quote.gid().len() != 0);
372    }
373
374    #[test]
375    fn test_get_quote() {
376        // Doing a meaningful test of this requires creating an enclave, this is
377        // just a simple test that we can send a bogus request and get an error
378        // back. The node attest flow in testsetup.sh exercises the real case.
379        let client = AesmClient::new();
380
381        let _quote_info = client.init_quote().unwrap();
382
383        let quote = client
384            .get_quote(
385                vec![0u8; Report::UNPADDED_SIZE],
386                vec![0u8; SPID_SIZE],
387                vec![],
388                QuoteType::Linkable,
389                vec![0u8; NONCE_SIZE],
390            )
391            .unwrap_err();
392
393        assert!(if let Error::AesmCode(_) = quote {
394            true
395        } else {
396            false
397        });
398    }
399}