rust_sgx_util/ias.rs
1use crate::{c, Error, Nonce, Quote, Result};
2#[cfg(feature = "with_serde")]
3use serde::{Deserialize, Serialize};
4use std::ffi::CString;
5use std::ops::Deref;
6use std::path::Path;
7use std::ptr::{self, NonNull};
8use std::str::FromStr;
9use std::{fmt, slice, u32};
10
11const IAS_VERIFY_URL: &str = "https://api.trustedservices.intel.com/sgx/dev/attestation/v3/report";
12const IAS_SIGRL_URL: &str = "https://api.trustedservices.intel.com/sgx/dev/attestation/v3/sigrl";
13
14/// Represents a handle to Intel's Attestation Service. It allows the user
15/// to perform operations such as getting a SigRL for a specified [`GroupId`],
16/// or verifying a specified quote with the IAS.
17///
18/// [`GroupId`]: struct.GroupId.html
19pub struct IasHandle {
20 // We need to store `verify_url` and `sigrl_url` due to a bug in the current
21 // implementation of `sgx_util` lib which does not copy out the buffers
22 // passed in as args to `ias_init` function.
23 #[allow(dead_code)]
24 verify_url: CString,
25 #[allow(dead_code)]
26 sigrl_url: CString,
27 context: NonNull<c::IasContext>,
28}
29
30impl IasHandle {
31 // TODO API key should probably have its own type that does
32 // at the very least some length validation
33 /// Create new instance with the specified `api_key` API key,
34 /// IAS verification URL `verify_url`, and IAS SigRL URL `sigrl_url`.
35 ///
36 /// By default, the following URLs are used:
37 /// * IAS verification - [dev/attestation/v3/report]
38 /// * IAS SigRL - [dev/attestation/v3/sigrl]
39 ///
40 /// [dev/attestation/v3/report]: https://api.trustedservices.intel.com/sgx/dev/attestation/v3/report
41 /// [dev/attestation/v3/sigrl]: https://api.trustedservices.intel.com/sgx/dev/attestation/v3/sigrl
42 ///
43 /// # Errors
44 ///
45 /// This function will fail with [`Error::IasInitNullPtr`] if initialisation
46 /// of the handle is unsuccessful, or if converting input arguments to
47 /// `CString` fails.
48 ///
49 /// [`Error::IasInitNullPtr`]: enum.Error.html#variant.IasInitNullPtr
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// # use rust_sgx_util::*;
55 /// # fn main() -> anyhow::Result<()> {
56 /// let _handle = IasHandle::new("012345abcdef", None, None)?;
57 /// # Ok(())
58 /// # }
59 /// ```
60 pub fn new(api_key: &str, verify_url: Option<&str>, sigrl_url: Option<&str>) -> Result<Self> {
61 let api_key = CString::new(api_key)?;
62 let verify_url = verify_url.unwrap_or(IAS_VERIFY_URL);
63 let verify_url = CString::new(verify_url)?;
64 let sigrl_url = sigrl_url.unwrap_or(IAS_SIGRL_URL);
65 let sigrl_url = CString::new(sigrl_url)?;
66 let raw_context =
67 unsafe { c::ias_init(api_key.as_ptr(), verify_url.as_ptr(), sigrl_url.as_ptr()) };
68 let context = NonNull::new(raw_context).ok_or(Error::IasInitNullPtr)?;
69 Ok(Self {
70 verify_url,
71 sigrl_url,
72 context,
73 })
74 }
75
76 /// Obtain SigRL for the given `group_id`.
77 ///
78 /// # Errors
79 ///
80 /// This function will fail with [`Error::IasGetSigrlNonZero(_)`] if the
81 /// `group_id` is invalid, or the `IasHandle` was created with an
82 /// invalid IAS verification URL.
83 ///
84 /// [`Error::IasGetSigrlNonZero(_)`]: enum.Error.html#variant.IasGetSigrlNonZero
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// # use rust_sgx_util::*;
90 /// use std::str::FromStr;
91 /// # fn main() -> anyhow::Result<()> {
92 /// let handle = IasHandle::new("012345abcdef", None, None)?;
93 /// let group_id = GroupId::from_str("01234567")?;
94 /// let res = handle.get_sigrl(&group_id);
95 /// assert!(res.is_err());
96 /// # Ok(())
97 /// # }
98 /// ```
99 pub fn get_sigrl(&self, group_id: &GroupId) -> Result<Option<Sigrl>> {
100 let mut size: usize = 0;
101 let mut raw = ptr::null_mut();
102 let ret = unsafe {
103 c::ias_get_sigrl(
104 self.context.as_ptr(),
105 group_id.as_ptr(),
106 &mut size,
107 &mut raw,
108 )
109 };
110 if ret == 0 {
111 if size == 0 {
112 // No SigRL for given EPID group id
113 Ok(None)
114 } else {
115 let sigrl = unsafe { Sigrl::new(raw as *const u8, size) };
116 Ok(Some(sigrl))
117 }
118 } else {
119 Err(Error::IasGetSigrlNonZero(ret))
120 }
121 }
122
123 /// Verify provided quote.
124 ///
125 /// # Errors
126 ///
127 /// This function will fail with [`Error::IasVerifyQuoteNonZero(_)`] if the
128 /// provided `quote` is invalid, or the `nonce`, or if the IAS server
129 /// returns a non 200 status code.
130 ///
131 /// [`Error::IasVerifyQuoteNonZero(_)`]: enum.Error.html#variant.IasVerifyQuoteNonZero
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// # use rust_sgx_util::*;
137 /// # fn main() -> anyhow::Result<()> {
138 /// let handle = IasHandle::new("012345abcdef", None, None)?;
139 /// let quote = Quote::from(vec![0u8; 100]);
140 /// let res = handle.verify_quote("e, None, None, None, None, None);
141 /// assert!(res.is_err());
142 /// # Ok(())
143 /// # }
144 /// ```
145 pub fn verify_quote(
146 &self,
147 quote: &Quote,
148 nonce: Option<&Nonce>,
149 report_path: Option<&Path>,
150 sig_path: Option<&Path>,
151 cert_path: Option<&Path>,
152 advisory_path: Option<&Path>,
153 ) -> Result<()> {
154 let nonce = nonce.map(|nonce| CString::new(nonce.deref())).transpose()?;
155 let report_path = report_path.map(|path| path_to_c_string(path)).transpose()?;
156 let sig_path = sig_path.map(|path| path_to_c_string(path)).transpose()?;
157 let cert_path = cert_path.map(|path| path_to_c_string(path)).transpose()?;
158 let advisory_path = advisory_path
159 .map(|path| path_to_c_string(path))
160 .transpose()?;
161 let ret = unsafe {
162 c::ias_verify_quote(
163 self.context.as_ptr(),
164 quote.as_ptr() as *const _,
165 quote.len(),
166 match &nonce {
167 Some(nonce) => nonce.as_ptr(),
168 None => ptr::null(),
169 },
170 match &report_path {
171 Some(path) => path.as_ptr(),
172 None => ptr::null(),
173 },
174 match &sig_path {
175 Some(path) => path.as_ptr(),
176 None => ptr::null(),
177 },
178 match &cert_path {
179 Some(path) => path.as_ptr(),
180 None => ptr::null(),
181 },
182 match &advisory_path {
183 Some(path) => path.as_ptr(),
184 None => ptr::null(),
185 }
186 )
187 };
188 if ret == 0 {
189 Ok(())
190 } else {
191 Err(Error::IasVerifyQuoteNonZero(ret))
192 }
193 }
194}
195
196impl Drop for IasHandle {
197 fn drop(&mut self) {
198 unsafe { c::ias_cleanup(self.context.as_ptr()) }
199 }
200}
201
202#[cfg(unix)]
203fn path_to_c_string(path: &Path) -> Result<CString> {
204 use std::os::unix::ffi::OsStrExt;
205 let s = CString::new(path.as_os_str().as_bytes())?;
206 Ok(s)
207}
208
209#[cfg(windows)]
210fn path_to_c_string(path: &Path) -> Result<CString> {
211 use std::os::windows::ffi::OsStringExt;
212 let utf16: Vec<_> = path.as_os_str().encode_wide().collect();
213 let s = String::from_utf16(utf16)?;
214 let s = CString::new(s.as_bytes())?;
215 Ok(s)
216}
217
218/// A thin wrapper around vector of bytes. Stores the result of
219/// [`IasHandle::get_sigrl`] function call, i.e., the SigRL
220/// for the specified [`GroupId`].
221///
222/// [`IasHandle::get_sigrl`]: struct.IasHandle.html#method.get_sigrl
223/// [`GroupId`]: struct.GroupId.html
224///
225/// # Accessing the underlying bytes buffer
226///
227/// `Sigrl` implements `Deref<Target=[u8]>`, therefore dereferencing it will
228/// yield its inner buffer of bytes.
229///
230/// # Serializing/deserializing
231///
232/// With `with_serde` feature enabled, `Sigrl` can be serialized and deserialized
233/// as base64 `String`.
234#[derive(Debug, Clone)]
235#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
236pub struct Sigrl(#[cfg_attr(feature = "with_serde", serde(with = "crate::ser_de"))] Vec<u8>);
237
238impl Sigrl {
239 unsafe fn new(raw: *const u8, size: usize) -> Self {
240 let slice = slice::from_raw_parts(raw, size);
241 Self(slice.to_vec())
242 }
243}
244
245impl Deref for Sigrl {
246 type Target = [u8];
247
248 fn deref(&self) -> &Self::Target {
249 &self.0
250 }
251}
252
253impl fmt::Display for Sigrl {
254 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255 f.write_str("Sigrl(")?;
256 for b in &self.0 {
257 f.write_fmt(format_args!("{:#b}", b))?;
258 }
259 f.write_str(")")
260 }
261}
262
263/// Represents EPID group ID.
264///
265/// This structure is necessary to invoke [`IasHandle::get_sigrl`] function.
266///
267/// [`IasHandle::get_sigrl`]: struct.IasHandle.html#method.get_sigrl
268///
269/// # Creating `GroupId`
270///
271/// Currently, the only way to create an instance of `GroupId`, is from `&str`
272/// slice via the `std::str::FromStr::from_str` method. Note also that currently
273/// prepending "0x" to the string is invalid, and will result in `Error::ParseInt(_)`
274/// error.
275///
276/// ```
277/// # use rust_sgx_util::GroupId;
278/// use std::str::FromStr;
279/// assert!(GroupId::from_str("01234567").is_ok());
280/// assert!(GroupId::from_str("0x01234567").is_err()); // prepending "0x" is currently invalid
281/// ```
282///
283/// # Accessing the underlying bytes buffer
284///
285/// `GroupId` implements `Deref<Target=[u8]>`, therefore dereferencing it will
286/// yield its inner buffer of bytes.
287#[derive(Debug, Clone)]
288#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
289// TODO cleanup construction and ser/de
290pub struct GroupId([u8; 4]);
291
292impl Deref for GroupId {
293 type Target = [u8];
294
295 fn deref(&self) -> &Self::Target {
296 &self.0
297 }
298}
299
300impl FromStr for GroupId {
301 type Err = Error;
302
303 fn from_str(s: &str) -> Result<Self> {
304 let parsed = u32::from_str_radix(s, 16)?;
305 Ok(GroupId(parsed.to_le_bytes()))
306 }
307}
308
309impl fmt::Display for GroupId {
310 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311 write!(f, "GroupId({:#010x})", u32::from_le_bytes(self.0))
312 }
313}