Skip to main content

fortanix_vme_eif/
lib.rs

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
24/// A builder to create a gzipped cpio archive of an initramfs suitable to create an AWS Nitro
25/// Enclave from.
26pub 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        /*
65         *  Eif files are stored as:
66         *      +--------------------+ <- 0
67         *      |      EifHeader     |
68         *     /+--------------------+ <- EifHeader::size()
69         *     ||  EifSectionHeader  |
70         *  +--|+--------------------+ <- EifHeader::size() + EifSectionHeader::size()
71         *  |  ||      <section>     |
72         *  |  \+--------------------+ <- EifHeader::size() + EifSectionHeader::size() + section.section_size
73         *  |
74         *  +-> Any number of sections
75         */
76        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    /// Parses an eif file and returns an iterator of its parts
154    /// The AWS image format crate doesn't provide a way to extract these sections easily. This
155    /// code should be upstreamed.
156    /// https://github.com/aws/aws-nitro-enclaves-image-format/blob/main/src/utils/eif_reader.rs#L85-L209
157    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        // Unfortunately `aws_nitro_enclaves_image_format::EifBuilder` forces us to have data in
264        // files.
265        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        // Unfortunately it's unclear if this information is required. Using defaults found in
283        // https://github.com/aws/aws-nitro-enclaves-image-format/blob/d0d224b8b626db5fcc2d7b685bdd229991bbf0a7/examples/eif_build.rs#L171-L184
284        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        // Create eif
334        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        // Parse eif
350        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}