1#![deny(warnings)]
2use aws_nitro_enclaves_image_format::defs::{EifHeader, EifIdentityInfo, EifSectionHeader};
3use aws_nitro_enclaves_image_format::generate_build_info;
4use aws_nitro_enclaves_image_format::utils::EifBuilder;
5use serde_json::json;
6use sha2::{Digest, Sha512};
7use std::io::{self, Cursor, ErrorKind, Read, Seek, Write};
8use std::ops::Deref;
9use std::path::Path;
10use std::rc::Rc;
11use tempfile::{self, NamedTempFile};
12
13mod error;
14mod initramfs;
15
16pub mod eif_types {
17 pub use aws_nitro_enclaves_image_format::defs::{EifHeader, EifIdentityInfo, EifSectionHeader};
18}
19pub use aws_nitro_enclaves_image_format::defs::EifSectionType;
20pub use error::Error;
21
22use fortanix_vme_initramfs::Initramfs;
23
24pub struct Builder<
27 R: Read + Seek + 'static,
28 S: Read + Seek + 'static,
29 T: Read + Seek + 'static,
30 U: Read + Seek + 'static,
31 V: Read + Seek + 'static,
32> {
33 name: String,
34 application: R,
35 init: S,
36 nsm: T,
37 kernel: U,
38 kernel_config: V,
39 cmdline: String,
40}
41
42#[derive(Debug, Clone)]
43enum EifPart {
44 Header(Rc<EifHeader>),
45 SectionHeader(Rc<EifSectionHeader>),
46 SectionData(Rc<Vec<u8>>),
47}
48
49struct EifPartIterator<T: Read> {
50 reader: T,
51 part: Option<EifPart>,
52}
53
54impl<T: Read> EifPartIterator<T> {
55 fn new(reader: T) -> EifPartIterator<T> {
56 EifPartIterator { reader, part: None }
57 }
58}
59
60impl<T: Read> Iterator for EifPartIterator<T> {
61 type Item = EifPart;
62
63 fn next(&mut self) -> Option<EifPart> {
64 fn header<T: Read>(reader: &mut T) -> Result<EifHeader, Error> {
77 let mut buff = [0; EifHeader::size()];
78 reader.read_exact(&mut buff).map_err(Error::EifReadError)?;
79 EifHeader::from_be_bytes(&buff).map_err(Error::EifParseError)
80 }
81
82 fn section_header<T: Read>(reader: &mut T) -> Result<Option<EifSectionHeader>, Error> {
83 let mut buff = [0; EifSectionHeader::size()];
84 if let Err(e) = reader.read_exact(&mut buff) {
85 if e.kind() == ErrorKind::UnexpectedEof {
86 return Ok(None);
87 } else {
88 return Err(Error::EifReadError(e));
89 }
90 }
91 let header = EifSectionHeader::from_be_bytes(&buff).map_err(Error::EifParseError)?;
92 Ok(Some(header))
93 }
94
95 fn section_content<T: Read>(
96 reader: &mut T,
97 section: &EifSectionHeader,
98 ) -> Result<Vec<u8>, Error> {
99 let mut buff = vec![0u8; section.section_size as usize];
100 reader.read_exact(&mut buff).map_err(Error::EifReadError)?;
101 Ok(buff)
102 }
103
104 match &self.part {
105 None => {
106 let h = header(&mut self.reader).ok()?;
107 self.part = Some(EifPart::Header(Rc::new(h)));
108 }
109 Some(EifPart::Header(_)) | Some(EifPart::SectionData(_)) => {
110 let s = section_header(&mut self.reader).ok()??;
111 self.part = Some(EifPart::SectionHeader(Rc::new(s)));
112 }
113 Some(EifPart::SectionHeader(h)) => {
114 let data = section_content(&mut self.reader, h).ok()?;
115 self.part = Some(EifPart::SectionData(Rc::new(data)));
116 }
117 }
118 self.part.clone()
119 }
120}
121
122pub struct SectionIterator<T: Read>(EifPartIterator<T>);
123
124impl<T: Read> Iterator for SectionIterator<T> {
125 type Item = (Rc<EifSectionHeader>, Rc<Vec<u8>>);
126
127 fn next(&mut self) -> Option<(Rc<EifSectionHeader>, Rc<Vec<u8>>)> {
128 let header = self.0.next()?;
129 let data = self.0.next()?;
130 match (header, data) {
131 (EifPart::SectionHeader(h), EifPart::SectionData(d)) => Some((h, d)),
132 _ => None,
133 }
134 }
135}
136
137pub struct FtxEif<T> {
138 eif: T,
139}
140
141impl<T> FtxEif<T> {
142 pub fn new(eif: T) -> Self {
143 FtxEif { eif }
144 }
145
146 pub fn into_inner(self) -> T {
147 let FtxEif { eif } = self;
148 eif
149 }
150}
151
152impl<T: Read + Seek> FtxEif<T> {
153 fn iter(&mut self) -> Result<EifPartIterator<&mut T>, Error> {
158 self.eif.rewind().map_err(Error::EifReadError)?;
159 Ok(EifPartIterator::new(&mut self.eif))
160 }
161
162 fn eif_header_ex(&mut self) -> Result<(Rc<EifHeader>, EifPartIterator<&mut T>), Error> {
163 let mut it = self.iter()?;
164 let header = it
165 .next()
166 .map(|h| {
167 if let EifPart::Header(header) = h {
168 Ok(header.clone())
169 } else {
170 Err(Error::EifParseError(String::from(
171 "Malformed eif file: Expected EifHeader",
172 )))
173 }
174 })
175 .ok_or(Error::EifParseError(String::from(
176 "Failed to parse eif header",
177 )))??;
178 Ok((header, it))
179 }
180
181 pub fn eif_header(&mut self) -> Result<Rc<EifHeader>, Error> {
182 self.eif_header_ex().map(|(header, _)| header)
183 }
184
185 pub fn sections(&mut self) -> Result<SectionIterator<&mut T>, Error> {
186 let it = self.eif_header_ex()?.1;
187 Ok(SectionIterator(it))
188 }
189
190 pub fn application(&mut self) -> Result<Vec<u8>, Error> {
191 let initramfs = self
192 .sections()?
193 .find_map(|(hdr, cnt)| {
194 if hdr.section_type == EifSectionType::EifSectionRamdisk {
195 Some(cnt)
196 } else {
197 None
198 }
199 })
200 .ok_or(Error::EifParseError(String::from("No ramdisks found")))?;
201
202 let initramfs = Initramfs::from(Cursor::new(initramfs.deref()));
203 let app = initramfs.read_entry_by_path(initramfs::APP_PATH)?;
204 Ok(app)
205 }
206
207 pub fn metadata(&mut self) -> Result<EifIdentityInfo, Error> {
208 let metadata = self
209 .sections()?
210 .find_map(|(header, data)| {
211 if header.deref().section_type == EifSectionType::EifSectionMetadata {
212 Some(data)
213 } else {
214 None
215 }
216 })
217 .ok_or(Error::EifParseError(String::from(
218 "No metadata section found in EIF file",
219 )))?;
220 serde_json::from_slice(metadata.deref().as_slice()).map_err(Error::MetadataParseError)
221 }
222}
223
224impl<
225 R: Read + Seek + 'static,
226 S: Read + Seek + 'static,
227 T: Read + Seek + 'static,
228 U: Read + Seek + 'static,
229 V: Read + Seek + 'static,
230 > Builder<R, S, T, U, V>
231{
232 pub fn new(
233 name: String,
234 application: R,
235 init: S,
236 nsm: T,
237 kernel: U,
238 kernel_config: V,
239 cmdline: &str,
240 ) -> Self {
241 Builder {
242 name,
243 application,
244 init,
245 nsm,
246 kernel,
247 kernel_config,
248 cmdline: cmdline.trim().to_string(),
249 }
250 }
251
252 pub fn build<F: Write + Seek>(self, mut output: F) -> Result<FtxEif<F>, Error> {
253 let Builder {
254 name,
255 application,
256 init,
257 nsm,
258 kernel: mut image,
259 kernel_config: mut image_config,
260 cmdline,
261 } = self;
262
263 let initramfs = NamedTempFile::new().map_err(Error::EifWriteError)?;
266 let initramfs = initramfs::build(application, init, nsm, initramfs)?;
267
268 let mut kernel = NamedTempFile::new().map_err(Error::KernelWriteError)?;
269 io::copy(&mut image, &mut kernel).map_err(Error::KernelWriteError)?;
270
271 let mut kernel_config = NamedTempFile::new().map_err(Error::KernelConfigWriteError)?;
272 io::copy(&mut image_config, &mut kernel_config).map_err(Error::KernelConfigWriteError)?;
273 let kernel_config_path = kernel_config
274 .path()
275 .as_os_str()
276 .to_str()
277 .ok_or(Error::eif_identity_info(String::from(
278 "Failed to retrieve path to kernel config",
279 )))?
280 .to_string();
281
282 let metadata = EifIdentityInfo {
285 img_name: name,
286 img_version: String::from("0.1"),
287 build_info: generate_build_info!(&kernel_config_path)
288 .map_err(Error::eif_identity_info)?,
289 docker_info: json!(null),
290 custom_info: json!(null),
291 };
292 let sign_info = None;
293 let hasher = Sha512::new();
294 let flags = 0;
295
296 let mut eifbuilder =
297 EifBuilder::new(kernel.path(), cmdline, sign_info, hasher, flags, metadata);
298 eifbuilder.add_ramdisk(initramfs.path());
299 let mut tmp = NamedTempFile::new().map_err(Error::EifWriteError)?;
300 eifbuilder.write_to(tmp.as_file_mut());
301 tmp.rewind().map_err(Error::EifWriteError)?;
302 io::copy(&mut tmp, &mut output).map_err(Error::EifWriteError)?;
303 Ok(FtxEif::new(output))
304 }
305}
306
307pub struct ReadEifResult<T> {
308 pub eif: FtxEif<T>,
309 pub metadata: EifIdentityInfo,
310}
311
312pub fn read_eif_with_metadata<P: AsRef<Path>>(
313 enclave_file_path: P,
314) -> Result<ReadEifResult<impl Read + Seek>, Error> {
315 let f = std::fs::File::open(enclave_file_path).map_err(Error::EifWriteError)?;
316 let mut eif = FtxEif::new(io::BufReader::new(f));
317 let metadata = eif.metadata()?;
318 Ok(ReadEifResult { eif, metadata })
319}
320
321#[cfg(test)]
322mod tests {
323 use super::{initramfs, Builder, FtxEif};
324 use aws_nitro_blobs::{CMDLINE, INIT, KERNEL, KERNEL_CONFIG, NSM};
325 use aws_nitro_enclaves_image_format::defs::EifSectionType;
326 use fortanix_vme_initramfs::Initramfs;
327 use std::io::{Cursor, Seek};
328 use std::ops::Deref;
329 use test_resources::HELLO_WORLD;
330
331 #[test]
332 fn eif_creation() {
333 let name = String::from("enclave");
335 let eif = Builder::new(
336 name.clone(),
337 Cursor::new(HELLO_WORLD),
338 Cursor::new(INIT),
339 Cursor::new(NSM),
340 Cursor::new(KERNEL),
341 Cursor::new(KERNEL_CONFIG),
342 CMDLINE,
343 )
344 .build(Cursor::new(Vec::new()))
345 .unwrap()
346 .into_inner()
347 .into_inner();
348
349 let mut eif_reader = FtxEif::new(Cursor::new(&eif));
351 let mut initramfs = None;
352 let mut sig = None;
353 let mut meta = None;
354 let mut kernel = None;
355 let mut cmdline = None;
356 for (section, content) in eif_reader.sections().unwrap() {
357 match section.section_type {
358 EifSectionType::EifSectionInvalid => panic!("Invalid section"),
359 EifSectionType::EifSectionKernel => {
360 assert_eq!(KERNEL[..], content[..]);
361 assert_eq!(None, kernel.replace(content));
362 }
363 EifSectionType::EifSectionCmdline => {
364 assert_eq!(
365 CMDLINE.trim(),
366 String::from_utf8(content.deref().clone()).unwrap()
367 );
368 assert_eq!(None, cmdline.replace(content));
369 }
370 EifSectionType::EifSectionRamdisk => {
371 let expected_initramfs = initramfs::build(
372 Cursor::new(HELLO_WORLD),
373 Cursor::new(INIT),
374 Cursor::new(NSM),
375 Cursor::new(Vec::new()),
376 )
377 .unwrap()
378 .into_inner();
379 assert_eq!(expected_initramfs, *content);
380 assert_eq!(None, initramfs.replace(content));
381 }
382 EifSectionType::EifSectionSignature => {
383 assert_eq!(None, sig.replace(content));
384 }
385 EifSectionType::EifSectionMetadata => {
386 assert_eq!(None, meta.replace(content));
387 }
388 }
389 }
390 assert_eq!(eif_reader.metadata().unwrap().img_name, name);
391 }
392
393 #[test]
394 fn eif_creation_and_extraction() {
395 let name = String::from("TestEnclave");
396 let hello_world = Cursor::new(HELLO_WORLD);
397 let init = Cursor::new(INIT);
398 let nsm = Cursor::new(NSM);
399 let kernel = Cursor::new(KERNEL);
400 let kernel_config = Cursor::new(KERNEL_CONFIG);
401 let eif = Builder::new(name, hello_world, init, nsm, kernel, kernel_config, CMDLINE)
402 .build(Cursor::new(Vec::new()))
403 .unwrap()
404 .into_inner()
405 .into_inner();
406 let mut eif = FtxEif::new(Cursor::new(eif));
407 assert_eq!(eif.application().unwrap(), HELLO_WORLD);
408 }
409
410 #[test]
411 fn initramfs_verification() {
412 let output = Cursor::new(Vec::new());
413 let initramfs = initramfs::build(
414 Cursor::new(HELLO_WORLD),
415 Cursor::new(INIT),
416 Cursor::new(NSM),
417 output,
418 )
419 .unwrap()
420 .into_inner();
421 let initramfs = Initramfs::from(Cursor::new(initramfs));
422 let fs_tree = initramfs::build_fs_tree(
423 Cursor::new(HELLO_WORLD),
424 Cursor::new(INIT),
425 Cursor::new(NSM),
426 );
427 let initramfs_blob = initramfs.into_inner().into_inner();
428 let mut cursor = Cursor::new(initramfs_blob);
429 Initramfs::from(&mut cursor).verify(fs_tree).unwrap();
430 cursor.rewind().unwrap();
431 Initramfs::from(&mut cursor)
432 .read_entry_by_path(initramfs::APP_PATH)
433 .unwrap();
434 }
435}