// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![deny(warnings)] pub mod eif_reader; pub mod identity; use crate::defs::eif_hasher::EifHasher; use crate::defs::{ EifHeader, EifIdentityInfo, EifSectionHeader, EifSectionType, PcrInfo, PcrSignature, EIF_MAGIC, MAX_NUM_SECTIONS, }; use aws_nitro_enclaves_cose::{crypto::Openssl, header_map::HeaderMap, CoseSign1}; use crc::{crc32, Hasher32}; use openssl::asn1::Asn1Time; use openssl::pkey::PKey; use serde::{Deserialize, Serialize}; use serde_cbor::{from_slice, to_vec}; use sha2::Digest; use std::cmp::Ordering; use std::collections::BTreeMap; /// Contains code for EifBuilder a simple library used for building an EifFile /// from a: /// - kernel_file /// - cmdline string /// - ramdisks files. /// TODO: /// - Unittests. /// - Add support to write default_mem & default_cpus, flags. /// - Various validity checks: E.g: kernel is a bzImage. use std::ffi::CString; use std::fmt::Debug; use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::path::Path; const DEFAULT_SECTIONS_COUNT: u16 = 3; #[derive(Clone, Debug)] pub struct SignEnclaveInfo { pub signing_certificate: Vec, pub private_key: Vec, } impl SignEnclaveInfo { pub fn new(cert_path: &str, key_path: &str) -> Result { let mut certificate_file = File::open(cert_path) .map_err(|err| format!("Could not open the certificate file: {:?}", err))?; let mut signing_certificate = Vec::new(); certificate_file .read_to_end(&mut signing_certificate) .map_err(|err| format!("Could not read the certificate file: {:?}", err))?; let mut key_file = File::open(key_path) .map_err(|err| format!("Could not open the key file: {:?}", err))?; let mut private_key = Vec::new(); key_file .read_to_end(&mut private_key) .map_err(|err| format!("Could not read the key file: {:?}", err))?; Ok(SignEnclaveInfo { signing_certificate, private_key, }) } } /// Utility function to calculate PCRs, used at build and describe. pub fn get_pcrs( image_hasher: &mut EifHasher, bootstrap_hasher: &mut EifHasher, app_hasher: &mut EifHasher, cert_hasher: &mut EifHasher, hasher: T, is_signed: bool, ) -> Result, String> { let mut measurements = BTreeMap::new(); let image_hasher = hex::encode( image_hasher .tpm_extend_finalize_reset() .map_err(|e| format!("Could not get result for image_hasher: {:?}", e))?, ); let bootstrap_hasher = hex::encode( bootstrap_hasher .tpm_extend_finalize_reset() .map_err(|e| format!("Could not get result for bootstrap_hasher: {:?}", e))?, ); let app_hash = hex::encode( app_hasher .tpm_extend_finalize_reset() .map_err(|e| format!("Could not get result for app_hasher: {:?}", e))?, ); // Hash certificate only if signing key is set, otherwise related PCR will be zero let cert_hash = if is_signed { Some(hex::encode( cert_hasher .tpm_extend_finalize_reset() .map_err(|e| format!("Could not get result for cert_hash: {:?}", e))?, )) } else { None }; measurements.insert("HashAlgorithm".to_string(), format!("{:?}", hasher)); measurements.insert("PCR0".to_string(), image_hasher); measurements.insert("PCR1".to_string(), bootstrap_hasher); measurements.insert("PCR2".to_string(), app_hash); if let Some(cert_hash) = cert_hash { measurements.insert("PCR8".to_string(), cert_hash); } Ok(measurements) } pub struct EifBuilder { kernel: File, cmdline: Vec, ramdisks: Vec, sign_info: Option, signature: Option>, signature_size: u64, metadata: Vec, eif_hdr_flags: u16, default_mem: u64, default_cpus: u64, /// Hash of the whole EifImage. pub image_hasher: EifHasher, /// Hash of the EifSections provided by Amazon /// Kernel + cmdline + First Ramdisk pub bootstrap_hasher: EifHasher, /// Hash of the remaining ramdisks. pub customer_app_hasher: EifHasher, /// Hash the signing certificate pub certificate_hasher: EifHasher, hasher_template: T, eif_crc: crc32::Digest, } impl EifBuilder { pub fn new( kernel_path: &Path, cmdline: String, sign_info: Option, hasher: T, flags: u16, eif_info: EifIdentityInfo, ) -> Self { let kernel_file = File::open(kernel_path).expect("Invalid kernel path"); let cmdline = CString::new(cmdline).expect("Invalid cmdline"); let metadata = serde_json::to_vec(&eif_info).expect("Could not serialize metadata: {}"); EifBuilder { kernel: kernel_file, cmdline: cmdline.into_bytes(), ramdisks: Vec::new(), sign_info, signature: None, signature_size: 0, metadata, eif_hdr_flags: flags, default_mem: 1024 * 1024 * 1024, default_cpus: 2, image_hasher: EifHasher::new_without_cache(hasher.clone()) .expect("Could not create image_hasher"), bootstrap_hasher: EifHasher::new_without_cache(hasher.clone()) .expect("Could not create bootstrap_hasher"), customer_app_hasher: EifHasher::new_without_cache(hasher.clone()) .expect("Could not create customer app hasher"), certificate_hasher: EifHasher::new_without_cache(hasher.clone()) .expect("Could not create certificate hasher"), hasher_template: hasher, eif_crc: crc32::Digest::new_with_initial(crc32::IEEE, 0), } } pub fn is_signed(&mut self) -> bool { self.sign_info.is_some() } pub fn add_ramdisk(&mut self, ramdisk_path: &Path) { let ramdisk_file = File::open(ramdisk_path).expect("Invalid ramdisk path"); self.ramdisks.push(ramdisk_file); } /// The first two sections are the kernel and the cmdline and the last is metadata. fn num_sections(&self) -> u16 { DEFAULT_SECTIONS_COUNT + self.ramdisks.len() as u16 + self.sign_info.iter().count() as u16 } fn sections_offsets(&self) -> [u64; MAX_NUM_SECTIONS] { let mut result = [0; MAX_NUM_SECTIONS]; result[0] = self.kernel_offset(); result[1] = self.cmdline_offset(); result[2] = self.metadata_offset(); for i in 0..self.ramdisks.len() { result[i + DEFAULT_SECTIONS_COUNT as usize] = self.ramdisk_offset(i); } if self.sign_info.is_some() { result[DEFAULT_SECTIONS_COUNT as usize + self.ramdisks.len()] = self.signature_offset(); } result } fn sections_sizes(&self) -> [u64; MAX_NUM_SECTIONS] { let mut result = [0; MAX_NUM_SECTIONS]; result[0] = self.kernel_size(); result[1] = self.cmdline_size(); result[2] = self.metadata_size(); for i in 0..self.ramdisks.len() { result[i + DEFAULT_SECTIONS_COUNT as usize] = self.ramdisk_size(&self.ramdisks[i]); } if self.sign_info.is_some() { result[DEFAULT_SECTIONS_COUNT as usize + self.ramdisks.len()] = self.signature_size(); } result } fn eif_header_offset(&self) -> u64 { 0 } fn kernel_offset(&self) -> u64 { self.eif_header_offset() + EifHeader::size() as u64 } fn kernel_size(&self) -> u64 { self.kernel.metadata().unwrap().len() as u64 } fn cmdline_offset(&self) -> u64 { self.kernel_offset() + EifSectionHeader::size() as u64 + self.kernel_size() } fn cmdline_size(&self) -> u64 { self.cmdline.len() as u64 } fn ramdisk_offset(&self, index: usize) -> u64 { self.metadata_offset() + self.metadata_size() + EifSectionHeader::size() as u64 + self.ramdisks[0..index] .iter() .fold(0, |mut total_len, file| { total_len += file.metadata().expect("Invalid ramdisk metadata").len() + EifSectionHeader::size() as u64; total_len }) } fn ramdisk_size(&self, ramdisk: &File) -> u64 { ramdisk.metadata().unwrap().len() as u64 } fn signature_offset(&self) -> u64 { let index = self.ramdisks.len() - 1; self.ramdisk_offset(index) + EifSectionHeader::size() as u64 + self.ramdisk_size(&self.ramdisks[index]) } fn signature_size(&self) -> u64 { self.signature_size } fn metadata_offset(&self) -> u64 { self.cmdline_offset() + EifSectionHeader::size() as u64 + self.cmdline_size() } fn metadata_size(&self) -> u64 { self.metadata.len() as u64 } /// Generate the signature of a certain PCR. fn generate_pcr_signature( &mut self, register_index: i32, register_value: Vec, ) -> PcrSignature { let sign_info = self.sign_info.as_ref().unwrap(); let signing_certificate = sign_info.signing_certificate.clone(); let pcr_info = PcrInfo::new(register_index, register_value); let payload = to_vec(&pcr_info).expect("Could not serialize PCR info"); let private_key = PKey::private_key_from_pem(&sign_info.private_key) .expect("Could not deserialize the PEM-formatted private key"); let signature = CoseSign1::new::(&payload, &HeaderMap::new(), private_key.as_ref()) .unwrap() .as_bytes(false) .unwrap(); PcrSignature { signing_certificate, signature, } } /// Generate the signature of the EIF. /// eif_signature = [pcr0_signature] fn generate_eif_signature(&mut self, measurements: &BTreeMap) { let pcr0_index = 0; let pcr0_value = hex::decode(measurements.get("PCR0").unwrap()).unwrap(); let pcr0_signature = self.generate_pcr_signature(pcr0_index, pcr0_value); let eif_signature = vec![pcr0_signature]; let serialized_signature = to_vec(&eif_signature).expect("Could not serialize the signature"); self.signature_size = serialized_signature.len() as u64; self.signature = Some(serialized_signature) } pub fn header(&mut self) -> EifHeader { EifHeader { magic: EIF_MAGIC, version: crate::defs::CURRENT_VERSION, flags: self.eif_hdr_flags, default_mem: self.default_mem, default_cpus: self.default_cpus, reserved: 0, num_sections: self.num_sections(), section_offsets: self.sections_offsets(), section_sizes: self.sections_sizes(), unused: 0, eif_crc32: self.eif_crc.sum32(), } } /// Compute the crc for the whole enclave image, excluding the /// eif_crc32 field from the EIF header. pub fn compute_crc(&mut self) { let eif_header = self.header(); let eif_buffer = eif_header.to_be_bytes(); // The last field of the EifHeader is the CRC itself, so we need // to exclude it from contributing to the CRC. let len_without_crc = eif_buffer.len() - size_of::(); self.eif_crc.write(&eif_buffer[..len_without_crc]); let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionKernel, flags: 0, section_size: self.kernel_size(), }; let eif_buffer = eif_section.to_be_bytes(); self.eif_crc.write(&eif_buffer[..]); let mut kernel_file = &self.kernel; kernel_file .seek(SeekFrom::Start(0)) .expect("Could not seek kernel to beginning"); let mut buffer = Vec::new(); kernel_file .read_to_end(&mut buffer) .expect("Failed to read kernel content"); self.eif_crc.write(&buffer[..]); let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionCmdline, flags: 0, section_size: self.cmdline_size(), }; let eif_buffer = eif_section.to_be_bytes(); self.eif_crc.write(&eif_buffer[..]); self.eif_crc.write(&self.cmdline[..]); let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionMetadata, flags: 0, section_size: self.metadata_size(), }; let eif_buffer = eif_section.to_be_bytes(); self.eif_crc.write(&eif_buffer[..]); self.eif_crc.write(&self.metadata[..]); for mut ramdisk in &self.ramdisks { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionRamdisk, flags: 0, section_size: self.ramdisk_size(ramdisk), }; let eif_buffer = eif_section.to_be_bytes(); self.eif_crc.write(&eif_buffer[..]); ramdisk .seek(SeekFrom::Start(0)) .expect("Could not seek kernel to begining"); let mut buffer = Vec::new(); ramdisk .read_to_end(&mut buffer) .expect("Failed to read kernel content"); self.eif_crc.write(&buffer[..]); } if let Some(signature) = &self.signature { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionSignature, flags: 0, section_size: self.signature_size(), }; let eif_buffer = eif_section.to_be_bytes(); self.eif_crc.write(&eif_buffer[..]); self.eif_crc.write(&signature[..]); } } pub fn write_header(&mut self, file: &mut File) { let eif_header = self.header(); file.seek(SeekFrom::Start(self.eif_header_offset())).expect( "Could not seek while writing eif \ header", ); let eif_buffer = eif_header.to_be_bytes(); file.write_all(&eif_buffer[..]) .expect("Failed to write eif header"); } pub fn write_kernel(&mut self, eif_file: &mut File) { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionKernel, flags: 0, section_size: self.kernel_size(), }; eif_file .seek(SeekFrom::Start(self.kernel_offset())) .expect("Could not seek while writing kernel section"); let eif_buffer = eif_section.to_be_bytes(); eif_file .write_all(&eif_buffer[..]) .expect("Failed to write kernel header"); let mut kernel_file = &self.kernel; kernel_file .seek(SeekFrom::Start(0)) .expect("Could not seek kernel to begining"); let mut buffer = Vec::new(); kernel_file .read_to_end(&mut buffer) .expect("Failed to read kernel content"); eif_file .write_all(&buffer[..]) .expect("Failed to write kernel data"); } pub fn write_cmdline(&mut self, eif_file: &mut File) { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionCmdline, flags: 0, section_size: self.cmdline_size(), }; eif_file .seek(SeekFrom::Start(self.cmdline_offset())) .expect( "Could not seek while writing cmdline section", ); let eif_buffer = eif_section.to_be_bytes(); eif_file .write_all(&eif_buffer[..]) .expect("Failed to write cmdline header"); eif_file .write_all(&self.cmdline[..]) .expect("Failed write cmdline header"); } pub fn write_metadata(&mut self, eif_file: &mut File) { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionMetadata, flags: 0, section_size: self.metadata_size(), }; eif_file .seek(SeekFrom::Start(self.metadata_offset())) .expect("Could not seek while writing metadata section"); let eif_buffer = eif_section.to_be_bytes(); eif_file .write_all(&eif_buffer[..]) .expect("Failed to write metadata header"); eif_file .write_all(&self.metadata) .expect("Failed to write metadata content"); } pub fn write_ramdisks(&mut self, eif_file: &mut File) { for (index, mut ramdisk) in self.ramdisks.iter().enumerate() { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionRamdisk, flags: 0, section_size: self.ramdisk_size(ramdisk), }; eif_file .seek(SeekFrom::Start(self.ramdisk_offset(index))) .expect( "Could not seek while writing kernel section", ); let eif_buffer = eif_section.to_be_bytes(); eif_file .write_all(&eif_buffer[..]) .expect("Failed to write section header"); ramdisk .seek(SeekFrom::Start(0)) .expect("Could not seek ramdisk to beginning"); let mut buffer = Vec::new(); ramdisk .read_to_end(&mut buffer) .expect("Failed to read ramdisk content"); eif_file .write_all(&buffer[..]) .expect("Failed to write ramdisk data"); } } pub fn write_signature(&mut self, eif_file: &mut File) { if let Some(signature) = &self.signature { let eif_section = EifSectionHeader { section_type: EifSectionType::EifSectionSignature, flags: 0, section_size: self.signature_size(), }; eif_file .seek(SeekFrom::Start(self.signature_offset())) .expect("Could not seek while writing signature section"); let eif_buffer = eif_section.to_be_bytes(); eif_file .write_all(&eif_buffer[..]) .expect("Failed to write signature header"); eif_file .write_all(&signature[..]) .expect("Failed write signature header"); } } pub fn write_to(&mut self, output_file: &mut File) -> BTreeMap { self.measure(); let measurements = get_pcrs( &mut self.image_hasher, &mut self.bootstrap_hasher, &mut self.customer_app_hasher, &mut self.certificate_hasher, self.hasher_template.clone(), self.sign_info.is_some(), ) .expect("Failed to get measurements"); if self.sign_info.is_some() { self.generate_eif_signature(&measurements); } self.compute_crc(); self.write_header(output_file); self.write_kernel(output_file); self.write_cmdline(output_file); self.write_metadata(output_file); self.write_ramdisks(output_file); self.write_signature(output_file); measurements } pub fn measure(&mut self) { let mut kernel_file = &self.kernel; kernel_file .seek(SeekFrom::Start(0)) .expect("Could not seek kernel to beginning"); let mut buffer = Vec::new(); kernel_file .read_to_end(&mut buffer) .expect("Failed to read kernel content"); self.image_hasher.write_all(&buffer[..]).unwrap(); self.bootstrap_hasher.write_all(&buffer[..]).unwrap(); self.image_hasher.write_all(&self.cmdline[..]).unwrap(); self.bootstrap_hasher.write_all(&self.cmdline[..]).unwrap(); for (index, mut ramdisk) in self.ramdisks.iter().enumerate() { ramdisk .seek(SeekFrom::Start(0)) .expect("Could not seek kernel to beginning"); let mut buffer = Vec::new(); ramdisk .read_to_end(&mut buffer) .expect("Failed to read kernel content"); self.image_hasher.write_all(&buffer[..]).unwrap(); // The first ramdisk is provided by amazon and it contains the // code to bootstrap the docker container. if index == 0 { self.bootstrap_hasher.write_all(&buffer[..]).unwrap(); } else { self.customer_app_hasher.write_all(&buffer[..]).unwrap(); } } if let Some(sign_info) = self.sign_info.as_ref() { let cert = openssl::x509::X509::from_pem(&sign_info.signing_certificate[..]).unwrap(); let cert_der = cert.to_der().unwrap(); // This is equivalent to extend(cert.digest(sha384)), since hasher is going to // hash the DER certificate (cert.digest()) and then tpm_extend_finalize_reset // will do the extend. self.certificate_hasher.write_all(&cert_der).unwrap(); } } } /// PCR Signature verifier that checks the validity of /// the certificate used to sign the enclave #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PcrSignatureChecker { signing_certificate: Vec, signature: Vec, } impl PcrSignatureChecker { pub fn new(pcr_signature: &PcrSignature) -> Self { PcrSignatureChecker { signing_certificate: pcr_signature.signing_certificate.clone(), signature: pcr_signature.signature.clone(), } } /// Reads EIF section headers and looks for a signature. /// Seek to the signature section, if present, and save the certificate and signature pub fn from_eif(eif_path: &str) -> Result { let mut signing_certificate = Vec::new(); let mut signature = Vec::new(); let mut curr_seek = 0; let mut eif_file = File::open(eif_path).map_err(|e| format!("Failed to open the EIF file: {:?}", e))?; // Skip header let mut header_buf = vec![0u8; EifHeader::size()]; eif_file .read_exact(&mut header_buf) .map_err(|e| format!("Error while reading EIF header: {:?}", e))?; curr_seek += EifHeader::size(); eif_file .seek(SeekFrom::Start(curr_seek as u64)) .map_err(|e| format!("Failed to seek file from start: {:?}", e))?; let mut section_buf = vec![0u8; EifSectionHeader::size()]; // Read all section headers and skip if different from signature section while eif_file.read_exact(&mut section_buf).is_ok() { let section = EifSectionHeader::from_be_bytes(§ion_buf) .map_err(|e| format!("Error extracting EIF section header: {:?}", e))?; curr_seek += EifSectionHeader::size(); if section.section_type == EifSectionType::EifSectionSignature { let mut buf = vec![0u8; section.section_size as usize]; eif_file .seek(SeekFrom::Start(curr_seek as u64)) .map_err(|e| format!("Failed to seek after EIF section header: {:?}", e))?; eif_file.read_exact(&mut buf).map_err(|e| { format!("Error while reading signature section from EIF: {:?}", e) })?; // Deserialize PCR signature structure and save certificate and signature let des_sign: Vec = from_slice(&buf[..]) .map_err(|e| format!("Error deserializing certificate: {:?}", e))?; signing_certificate = des_sign[0].signing_certificate.clone(); signature = des_sign[0].signature.clone(); } curr_seek += section.section_size as usize; eif_file .seek(SeekFrom::Start(curr_seek as u64)) .map_err(|e| format!("Failed to seek after EIF section: {:?}", e))?; } Ok(Self { signing_certificate, signature, }) } pub fn is_empty(&self) -> bool { self.signing_certificate.len() == 0 && self.signature.len() == 0 } /// Verifies the validity of the signing certificate pub fn verify(&mut self) -> Result<(), String> { let signature = CoseSign1::from_bytes(&self.signature[..]) .map_err(|err| format!("Could not deserialize the signature: {:?}", err))?; let cert = openssl::x509::X509::from_pem(&self.signing_certificate[..]) .map_err(|_| "Could not deserialize the signing certificate".to_string())?; let public_key = cert .public_key() .map_err(|_| "Could not get the public key from the signing certificate".to_string())?; // Verify the signature let result = signature .verify_signature::(public_key.as_ref()) .map_err(|err| format!("Could not verify EIF signature: {:?}", err))?; if !result { return Err("The EIF signature is not valid".to_string()); } // Verify that the signing certificate is not expired let current_time = Asn1Time::days_from_now(0).map_err(|err| err.to_string())?; if current_time .compare(cert.not_after()) .map_err(|err| err.to_string())? == Ordering::Greater || current_time .compare(cert.not_before()) .map_err(|err| err.to_string())? == Ordering::Less { return Err("The signing certificate is expired".to_string()); } Ok(()) } }