From d30000a1c3ae6cb8f6dc60c295e037614605b36e Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 5 Jul 2022 16:24:32 +0900 Subject: [PATCH 1/4] algorithms: Add conversion between our hash algorithms and OpenSSL's Signed-off-by: Daiki Ueno --- src/algorithms.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/algorithms.rs b/src/algorithms.rs index 414ef100..019f9360 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2021 Keylime Authors +use openssl::hash::MessageDigest; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; @@ -74,6 +75,18 @@ impl From for HashingAlgorithm { } } +impl From for MessageDigest { + fn from(hash_algorithm: HashAlgorithm) -> Self { + match hash_algorithm { + HashAlgorithm::Sha1 => MessageDigest::sha1(), + HashAlgorithm::Sha256 => MessageDigest::sha256(), + HashAlgorithm::Sha384 => MessageDigest::sha384(), + HashAlgorithm::Sha512 => MessageDigest::sha512(), + HashAlgorithm::Sm3_256 => MessageDigest::sm3(), + } + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum EncryptionAlgorithm { Rsa, From a90aa5b0852d587114aca7a8e79306987c562624 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 5 Jul 2022 15:43:42 +0900 Subject: [PATCH 2/4] ima_entry: add IMA entry parser ported from Python Keylime Signed-off-by: Daiki Ueno --- src/ima_entry.rs | 516 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 src/ima_entry.rs diff --git a/src/ima_entry.rs b/src/ima_entry.rs new file mode 100644 index 00000000..7bb4a970 --- /dev/null +++ b/src/ima_entry.rs @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Keylime Authors + +// Parser for IMA ASCII entries. +// +// Implements the templates (modes) and types as defined in: +// https://elixir.bootlin.com/linux/latest/source/security/integrity/ima/ima_template.c +// https://www.kernel.org/doc/html/v5.12/security/IMA-templates.html + +use crate::algorithms::HashAlgorithm; +use openssl::hash::MessageDigest; +use std::convert::{TryFrom, TryInto}; +use std::io::{Error, ErrorKind, Result, Write}; + +pub trait Encode { + fn encode(&self, writer: &mut dyn Write) -> Result<()>; +} + +pub trait EncodeLegacy { + fn encode_legacy(&self, writer: &mut dyn Write) -> Result<()>; +} + +#[derive(PartialEq)] +pub struct Digest { + pub algorithm: HashAlgorithm, + value: Vec, +} + +impl Digest { + pub fn new(algorithm: HashAlgorithm, value: &[u8]) -> Result { + let digest: MessageDigest = algorithm.into(); + if value.len() != digest.size() { + return Err(Error::new( + ErrorKind::InvalidInput, + "invalid digest value", + )); + } + let mut v = Vec::with_capacity(digest.size()); + v.extend_from_slice(value); + Ok(Self { + algorithm, + value: v, + }) + } + + pub fn value(&self) -> &[u8] { + &self.value + } + + pub fn start(algorithm: HashAlgorithm) -> Self { + let digest: MessageDigest = algorithm.into(); + Self { + algorithm, + value: vec![0x00u8; digest.size()], + } + } + + pub fn ff(algorithm: HashAlgorithm) -> Self { + let digest: MessageDigest = algorithm.into(); + Self { + algorithm, + value: vec![0xffu8; digest.size()], + } + } +} + +impl TryFrom<&str> for Digest { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let tokens: Vec<&str> = value.splitn(2, ':').collect(); + if tokens.len() == 1 { + Ok(Digest { + algorithm: HashAlgorithm::Sha1, + value: hex::decode(&tokens[0]).map_err(|_| { + Error::new( + ErrorKind::InvalidInput, + "invalid hex encoding", + ) + })?, + }) + } else { + Ok(Digest { + algorithm: tokens[0].try_into().map_err(|_| { + Error::new(ErrorKind::InvalidInput, "invalid algorithm") + })?, + value: hex::decode(&tokens[1]).map_err(|_| { + Error::new( + ErrorKind::InvalidInput, + "invalid hex encoding", + ) + })?, + }) + } + } +} + +impl Encode for Digest { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + if self.algorithm == HashAlgorithm::Sha1 { + writer.write_all(&(self.value.len() as u32).to_le_bytes())?; + writer.write_all(&self.value)?; + } else { + let algorithm = format!("{}", self.algorithm); + let total_len = algorithm.len() + 2 + self.value.len(); + writer.write_all(&(total_len as u32).to_le_bytes())?; + writer.write_all(algorithm.as_bytes())?; + writer.write_all(&[58u8, 0u8])?; + writer.write_all(&self.value)?; + } + Ok(()) + } +} + +impl EncodeLegacy for Digest { + fn encode_legacy(&self, writer: &mut dyn Write) -> Result<()> { + writer.write_all(&self.value)?; + Ok(()) + } +} + +struct Name { + name: String, +} + +impl TryFrom<&str> for Name { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + Ok(Self { + name: value.to_string(), + }) + } +} + +const TCG_EVENT_NAME_LEN_MAX: usize = 255; + +impl Encode for Name { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + let bytes = self.name.as_bytes(); + writer.write_all(&((bytes.len() + 1) as u32).to_le_bytes())?; + writer.write_all(bytes)?; + writer.write_all(&[0u8])?; // NUL + Ok(()) + } +} + +impl EncodeLegacy for Name { + fn encode_legacy(&self, writer: &mut dyn Write) -> Result<()> { + let bytes = self.name.as_bytes(); + writer.write_all(bytes)?; + writer.write_all(&vec![0u8; TCG_EVENT_NAME_LEN_MAX - bytes.len()])?; + Ok(()) + } +} + +struct Signature { + value: Vec, +} + +impl TryFrom<&str> for Signature { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let value = hex::decode(value).map_err(|_| { + Error::new(ErrorKind::InvalidInput, "invalid hex encoding") + })?; + // basic checks on signature + if value.len() < 9 { + return Err(Error::new( + ErrorKind::InvalidInput, + "invalid signature", + )); + } + let sig_size = u16::from_be_bytes(value[7..9].try_into().unwrap()); //#[allow_ci] + if (sig_size as usize) + 9 != value.len() { + return Err(Error::new( + ErrorKind::InvalidInput, + "invalid signature", + )); + } + Ok(Self { value }) + } +} + +impl Encode for Signature { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + writer.write_all(&(self.value.len() as u32).to_le_bytes())?; + writer.write_all(&self.value)?; + Ok(()) + } +} + +struct Buffer { + value: Vec, +} + +impl TryFrom<&str> for Buffer { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + Ok(Self { + value: hex::decode(value).map_err(|_| { + Error::new(ErrorKind::InvalidInput, "invalid hex encoding") + })?, + }) + } +} + +impl Encode for Buffer { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + writer.write_all(&(self.value.len() as u32).to_le_bytes())?; + writer.write_all(&self.value)?; + Ok(()) + } +} + +pub trait EventData: Encode { + fn path(&self) -> &str; +} + +struct Ima { + digest: Digest, + path: Name, +} + +impl TryFrom<&str> for Ima { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let tokens: Vec<&str> = value.splitn(2, ' ').collect(); + if tokens.len() != 2 { + return Err(Error::new(ErrorKind::InvalidInput, value)); + } + Ok(Self { + digest: Digest::try_from(tokens[0])?, + path: Name::try_from(tokens[1])?, + }) + } +} + +impl EventData for Ima { + fn path(&self) -> &str { + &self.path.name + } +} + +impl Encode for Ima { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + self.digest.encode_legacy(writer)?; + self.path.encode_legacy(writer)?; + Ok(()) + } +} + +struct ImaNg { + digest: Digest, + path: Name, +} + +impl TryFrom<&str> for ImaNg { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let tokens: Vec<&str> = value.splitn(2, ' ').collect(); + if tokens.len() != 2 { + return Err(Error::new(ErrorKind::InvalidInput, value)); + } + + Ok(Self { + digest: Digest::try_from(tokens[0])?, + path: Name::try_from(tokens[1])?, + }) + } +} + +impl EventData for ImaNg { + fn path(&self) -> &str { + &self.path.name + } +} + +impl Encode for ImaNg { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + self.digest.encode(writer)?; + self.path.encode(writer)?; + Ok(()) + } +} + +struct ImaSig { + digest: Digest, + path: Name, + signature: Option, +} + +impl EventData for ImaSig { + fn path(&self) -> &str { + &self.path.name + } +} + +impl TryFrom<&str> for ImaSig { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + // extract signature first + let (value, signature) = value + .rsplit_once(' ') + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, value))?; + + // parse d-ng|n-ng as in ima-ng + let tokens: Vec<&str> = value.splitn(2, ' ').collect(); + if tokens.len() != 2 { + return Err(Error::new(ErrorKind::InvalidInput, value)); + } + + let digest = Digest::try_from(tokens[0])?; + let path = Name::try_from(tokens[1])?; + let signature = if !signature.is_empty() { + Some(Signature::try_from(signature)?) + } else { + None + }; + + Ok(Self { + digest, + path, + signature, + }) + } +} + +impl Encode for ImaSig { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + self.digest.encode(writer)?; + self.path.encode(writer)?; + if let Some(signature) = &self.signature { + signature.encode(writer)?; + } else { + writer.write_all(&0u32.to_le_bytes())?; + } + Ok(()) + } +} + +struct ImaBuf { + digest: Digest, + name: Name, + data: Buffer, +} + +impl TryFrom<&str> for ImaBuf { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let tokens: Vec<&str> = value.splitn(3, ' ').collect(); + if tokens.len() != 3 { + return Err(Error::new(ErrorKind::InvalidInput, value)); + } + Ok(Self { + digest: Digest::try_from(tokens[0])?, + name: Name::try_from(tokens[1])?, + data: Buffer::try_from(tokens[2])?, + }) + } +} + +impl EventData for ImaBuf { + fn path(&self) -> &str { + &self.name.name + } +} + +impl Encode for ImaBuf { + fn encode(&self, writer: &mut dyn Write) -> Result<()> { + self.digest.encode(writer)?; + self.name.encode(writer)?; + self.data.encode(writer)?; + Ok(()) + } +} + +pub struct Entry { + pub template_hash: Digest, + pub event_data: Box, +} + +impl TryFrom<&str> for Entry { + type Error = std::io::Error; + + fn try_from(value: &str) -> std::result::Result { + let tokens: Vec<&str> = value.splitn(4, ' ').collect(); + if tokens.len() != 4 { + return Err(Error::new(ErrorKind::InvalidInput, value)); + } + + let template_hash = Digest { + algorithm: HashAlgorithm::Sha1, + value: hex::decode(&tokens[1]).map_err(|_| { + Error::new(ErrorKind::InvalidInput, "invalid hex encoding") + })?, + }; + let mode = tokens[2]; + let event = tokens[3]; + + match mode { + "ima" => Ok(Self { + template_hash, + event_data: Box::new(Ima::try_from(event)?), + }), + "ima-ng" => Ok(Self { + template_hash, + event_data: Box::new(ImaNg::try_from(event)?), + }), + "ima-sig" => Ok(Self { + template_hash, + event_data: Box::new(ImaSig::try_from(event)?), + }), + "ima-buf" => Ok(Self { + template_hash, + event_data: Box::new(ImaBuf::try_from(event)?), + }), + template => Err(Error::new( + ErrorKind::Other, + format!("unrecognized template \"{}\"", template,), + )), + } + } +} + +// Unit Testing +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_ima() { + let entry: Entry = "10 d7026dc672344d3ee372217bdbc7395947788671 ima 6f66d1d8e2fffcc12dfcb78c04b81fe5b8bbae4e /usr/bin/kmod" + .try_into().expect("unable to parse ima template"); + assert_eq!(entry.event_data.path(), "/usr/bin/kmod"); + let mut buf = vec![]; + entry + .event_data + .encode(&mut buf) + .expect("unable to encode event data"); + assert_eq!( + &buf, + &hex::decode("6f66d1d8e2fffcc12dfcb78c04b81fe5b8bbae4e2f7573722f62696e2f6b6d6f640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), //#[allow_ci] + ); + } + + #[test] + fn test_parse_ima_ng() { + let entry: Entry = "10 7936eb315fb4e74b99e7d461bc5c96049e1ee092 ima-ng sha1:bc026ae66d81713e4e852465e980784dc96651f8 /usr/lib/systemd/systemd" + .try_into().expect("unable to parse ima-ng template"); + assert_eq!(entry.event_data.path(), "/usr/lib/systemd/systemd"); + let mut buf = vec![]; + entry + .event_data + .encode(&mut buf) + .expect("unable to encode event data"); + assert_eq!( + &buf, + &hex::decode("14000000bc026ae66d81713e4e852465e980784dc96651f8190000002f7573722f6c69622f73797374656d642f73797374656d6400").unwrap(), //#[allow_ci] + ); + } + + #[test] + fn test_parse_ima_sig() { + let entry: Entry = "10 06e804489a77ddab51b9ef27e17053c0e5d503bd ima-sig sha1:1cb84b12db45d7da8de58ba6744187db84082f0e /usr/bin/zmore 030202531f402500483046022100bff9c02dc7b270c83cc94bfec10eecd42831de2cdcb04f024369a14623bc3a91022100cc4d015ae932fb98d6846645ed7d1bb1afd4621ec9089bc087126f191886dd31" + .try_into().expect("unable to parse ima-sig template"); + assert_eq!(entry.event_data.path(), "/usr/bin/zmore"); + let mut buf = vec![]; + entry + .event_data + .encode(&mut buf) + .expect("unable to encode event data"); + assert_eq!( + &buf, + &hex::decode("140000001cb84b12db45d7da8de58ba6744187db84082f0e0f0000002f7573722f62696e2f7a6d6f72650051000000030202531f402500483046022100bff9c02dc7b270c83cc94bfec10eecd42831de2cdcb04f024369a14623bc3a91022100cc4d015ae932fb98d6846645ed7d1bb1afd4621ec9089bc087126f191886dd31").unwrap(), //#[allow_ci] + ); + } + + #[test] + fn test_parse_ima_sig_missing() { + let entry: Entry = "10 5426cf3031a43f5bfca183d79950698a95a728f6 ima-sig sha256:f1125b940480d20ad841d26d5ea253edc0704b5ec1548c891edf212cb1a9365e /lib/modules/5.4.48-openpower1/kernel/drivers/usb/common/usb-common.ko " + .try_into().expect("unable to parse ima-sig template without signature"); + assert_eq!(entry.event_data.path(), "/lib/modules/5.4.48-openpower1/kernel/drivers/usb/common/usb-common.ko"); + let mut buf = vec![]; + entry + .event_data + .encode(&mut buf) + .expect("unable to encode event data"); + assert_eq!( + &buf, + &hex::decode("280000007368613235363a00f1125b940480d20ad841d26d5ea253edc0704b5ec1548c891edf212cb1a9365e470000002f6c69622f6d6f64756c65732f352e342e34382d6f70656e706f776572312f6b65726e656c2f647269766572732f7573622f636f6d6d6f6e2f7573622d636f6d6d6f6e2e6b6f0000000000").unwrap(), //#[allow_ci] + ); + } + + #[test] + fn test_parse_ima_buf() { + let entry: Entry = "10 b7862dbbf1383ac6c7cca7f02d981a081aacb1f1 ima-buf sha1:6e0e6fc8a188ef4f059638949adca4d221946906 device_resume 6e616d653d544553543b757569643d43525950542d5645524954592d39656633326535623635623034343234613561386562343436636630653731332d544553543b63617061636974793d303b6d616a6f723d3235333b6d696e6f723d303b6d696e6f725f636f756e743d313b6e756d5f746172676574733d313b6163746976655f7461626c655f686173683d346565383065333365353635643336333430356634303238393436653837623365396563306335383661666639656630656436663561653762656237326431333b" + .try_into().expect("unable to parse ima-buf template"); + assert_eq!(entry.event_data.path(), "device_resume"); + let mut buf = vec![]; + entry + .event_data + .encode(&mut buf) + .expect("unable to encode event data"); + assert_eq!( + &buf, + &hex::decode("140000006e0e6fc8a188ef4f059638949adca4d2219469060e0000006465766963655f726573756d6500ce0000006e616d653d544553543b757569643d43525950542d5645524954592d39656633326535623635623034343234613561386562343436636630653731332d544553543b63617061636974793d303b6d616a6f723d3235333b6d696e6f723d303b6d696e6f725f636f756e743d313b6e756d5f746172676574733d313b6163746976655f7461626c655f686173683d346565383065333365353635643336333430356634303238393436653837623365396563306335383661666639656630656436663561653762656237326431333b").unwrap(), //#[allow_ci] + ); + } +} From 7decf3b0985235e28f36c1b445def535d615c8df Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Sun, 15 May 2022 17:46:36 +0200 Subject: [PATCH 3/4] ima_emulator: Support PCR hash algorithms other than SHA-1 This ports the recent changes to keylime_ima_emulator in the Python stack, which allows PCR hash algorithms other than SHA-1 through the command-line options. Signed-off-by: Daiki Ueno --- src/algorithms.rs | 1 - src/ima_emulator.rs | 176 ++++++++++++++++++++++++++++++-------------- 2 files changed, 122 insertions(+), 55 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 019f9360..3386abd7 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -10,7 +10,6 @@ use tss_esapi::{ AsymmetricAlgorithm, HashingAlgorithm, SignatureSchemeAlgorithm, }, structures::{HashScheme, SignatureScheme}, - tss2_esys::TPMT_SIG_SCHEME, }; // This error needs to be public because we implement TryFrom for public types diff --git a/src/ima_emulator.rs b/src/ima_emulator.rs index ba590bbc..c09e41b6 100644 --- a/src/ima_emulator.rs +++ b/src/ima_emulator.rs @@ -1,28 +1,32 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2021 Keylime Authors +mod algorithms; +use crate::algorithms::HashAlgorithm; +mod ima_entry; +use openssl::hash::{hash, MessageDigest}; + use log::*; -use std::convert::TryFrom; +use clap::Parser; +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; +use std::path::{Path, PathBuf}; use thiserror::Error; use tss_esapi::{ abstraction::pcr, handles::PcrHandle, - interface_types::algorithm::HashingAlgorithm, structures::{Digest, DigestValues, PcrSelectionListBuilder, PcrSlot}, Context, Tcti, }; const IMA_ML: &str = "/sys/kernel/security/ima/ascii_runtime_measurements"; -const START_HASH: &[u8; 20] = &[0u8; 20]; -const FF_HASH: &[u8; 20] = &[0xffu8; 20]; - #[derive(Error, Debug)] enum ImaEmulatorError { #[error("Invalid envvar")] @@ -31,8 +35,14 @@ enum ImaEmulatorError { TssEsapiError(#[from] tss_esapi::Error), #[error("Decoding error")] FromHexError(#[from] hex::FromHexError), + #[error("Algorithm error")] + AlgorithmError(#[from] algorithms::AlgorithmError), + #[error("OpenSSL error")] + OpenSSLError(#[from] openssl::error::ErrorStack), #[error("I/O error")] IoError(#[from] std::io::Error), + #[error("Integer parsing error")] + ParseInt(#[from] std::num::ParseIntError), #[error("{0}")] Other(String), } @@ -41,57 +51,75 @@ type Result = std::result::Result; fn ml_extend( context: &mut Context, - ml: &str, + ml: &Path, mut position: usize, + ima_hash_alg: HashAlgorithm, + pcr_hash_alg: HashAlgorithm, search_hash: Option<&Digest>, ) -> Result { let f = File::open(ml)?; let mut reader = BufReader::new(f); - let mut running_hash: Vec = Vec::from(&START_HASH[..]); + let ima_digest: MessageDigest = ima_hash_alg.into(); + let ima_start_hash = ima_entry::Digest::start(ima_hash_alg); + let pcr_digest: MessageDigest = pcr_hash_alg.into(); + let mut running_hash = ima_entry::Digest::start(pcr_hash_alg); + let ff_hash = ima_entry::Digest::ff(pcr_hash_alg); for line in reader.by_ref().lines().skip(position) { let line = line?; if line.is_empty() { continue; } - let tokens: Vec<&str> = line.splitn(5, ' ').collect(); - if tokens.len() < 5 { - error!("invalid measurement list file line: -{}-", line); - } + + let entry: ima_entry::Entry = line.as_str().try_into()?; + position += 1; - let path = tokens[4]; - let template_hash = hex::decode(tokens[1])?; - let template_hash = if template_hash == START_HASH { - Digest::try_from(&FF_HASH[..]) + // Set correct hash for time of measure, time of use (ToMToU) errors + // and if a file is already opened for write. + // https://elixir.bootlin.com/linux/v5.12.12/source/security/integrity/ima/ima_main.c#L101 + let pcr_template_hash = if entry.template_hash == ima_start_hash { + Digest::try_from(ff_hash.value()) } else { - Digest::try_from(template_hash) + let mut event_data = vec![]; + entry.event_data.encode(&mut event_data)?; + let pcr_event_hash = hash(pcr_digest, &event_data)?; + let ima_event_hash = hash(ima_digest, &event_data)?; + if ima_event_hash.as_ref() != entry.template_hash.value() { + return Err(ImaEmulatorError::Other( + "IMA template hash doesn't match".to_string(), + )); + } + Digest::try_from(pcr_event_hash.as_ref()) }?; match search_hash { None => { println!( "extending hash {} for {}", - hex::encode(template_hash.value()), - &path + hex::encode(pcr_template_hash.value()), + entry.event_data.path(), ); let mut vals = DigestValues::new(); - vals.set(HashingAlgorithm::Sha1, template_hash); - // TODO: Add support for other hash algorithms + vals.set(pcr_hash_alg.into(), pcr_template_hash); context.execute_with_nullauth_session(|ctx| { ctx.pcr_extend(PcrHandle::Pcr10, vals) })?; } Some(search_hash) => { - let mut hasher = openssl::sha::Sha1::new(); - hasher.update(&running_hash); - hasher.update(&template_hash); - running_hash = hasher.finish().into(); - let digest = Digest::try_from(running_hash.as_slice())?; + let mut hasher = openssl::hash::Hasher::new(pcr_digest)?; + hasher.update(running_hash.value())?; + hasher.update(&pcr_template_hash)?; + running_hash = + ima_entry::Digest::new(pcr_hash_alg, &hasher.finish()?)?; + let digest = Digest::try_from(running_hash.value())?; let mut vals = DigestValues::new(); - vals.set(HashingAlgorithm::Sha1, digest.clone()); + vals.set(pcr_hash_alg.into(), digest.clone()); if digest == *search_hash { - println!("Located last IMA file updated: {}", path); + println!( + "Located last IMA file updated: {}", + entry.event_data.path() + ); return Ok(position); } } @@ -106,7 +134,20 @@ fn ml_extend( Ok(position) } +#[derive(Parser)] +#[clap(about)] +struct Args { + #[clap(long = "hash_algs", short = 'a', default_value = "sha1")] + hash_algs: Vec, + #[clap(long, short = 'i', default_value = "sha1")] + ima_hash_alg: String, + #[clap(long, short = 'f', default_value = IMA_ML)] + ima_log: PathBuf, +} + fn main() -> std::result::Result<(), ImaEmulatorError> { + let args = Args::parse(); + let tcti = match Tcti::from_environment_variable() { Ok(tcti) => tcti, @@ -124,37 +165,64 @@ fn main() -> std::result::Result<(), ImaEmulatorError> { )); } - // check if pcr is clean - let pcr_list = PcrSelectionListBuilder::new() - .with_selection(HashingAlgorithm::Sha1, &[PcrSlot::Slot10]) - .build()?; - let pcr_data = context - .execute_without_session(|ctx| pcr::read_all(ctx, pcr_list))?; - let digest = pcr_data - .pcr_bank(HashingAlgorithm::Sha1) - .ok_or_else(|| { - ImaEmulatorError::Other( - "IMA slot does not have SHA-1 bank".to_string(), - ) - })? - .get_digest(PcrSlot::Slot10) - .ok_or_else(|| { - ImaEmulatorError::Other( - "could not read value from IMA PCR".to_string(), - ) - })?; - - let mut pos = 0; - - if digest.value() != START_HASH { - log::warn!("IMA PCR is not empty, trying to find the last updated file in the measurement list..."); - pos = ml_extend(&mut context, IMA_ML, 0, Some(digest))?; + let ima_hash_alg: HashAlgorithm = + args.ima_hash_alg.as_str().try_into()?; + let mut positions = HashMap::new(); + for pcr_hash_alg in args.hash_algs { + let pcr_hash_alg: HashAlgorithm = pcr_hash_alg.as_str().try_into()?; + positions.insert(pcr_hash_alg, 0usize); + } + + for (pcr_hash_alg, position) in positions.iter_mut() { + // check if pcr is clean + let pcr_list = PcrSelectionListBuilder::new() + .with_selection((*pcr_hash_alg).into(), &[PcrSlot::Slot10]) + .build()?; + let pcr_data = context + .execute_without_session(|ctx| pcr::read_all(ctx, pcr_list))?; + let digest = pcr_data + .pcr_bank((*pcr_hash_alg).into()) + .ok_or_else(|| { + ImaEmulatorError::Other(format!( + "IMA slot does not have {} bank", + *pcr_hash_alg, + )) + })? + .get_digest(PcrSlot::Slot10) + .ok_or_else(|| { + ImaEmulatorError::Other( + "could not read value from IMA PCR".to_string(), + ) + })?; + + let pcr_digest: MessageDigest = (*pcr_hash_alg).into(); + let pcr_start_hash = vec![0x00u8; pcr_digest.size()]; + if digest.value() != pcr_start_hash { + log::warn!("IMA PCR is not empty, trying to find the last updated file in the measurement list..."); + *position = ml_extend( + &mut context, + &args.ima_log, + *position, + ima_hash_alg, + *pcr_hash_alg, + Some(digest), + )?; + } } - println!("Monitoring {}", IMA_ML); + println!("Monitoring {}", args.ima_log.display()); loop { - pos = ml_extend(&mut context, IMA_ML, pos, None)?; + for (pcr_hash_alg, position) in positions.iter_mut() { + *position = ml_extend( + &mut context, + &args.ima_log, + *position, + ima_hash_alg, + *pcr_hash_alg, + None, + )?; + } // FIXME: We could poll IMA_ML as in the python implementation, though // the file is not pollable: From d0e2dd7dc9f24df1eb5ff3f8cde95e5e561992d6 Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Wed, 6 Jul 2022 22:02:53 +0200 Subject: [PATCH 4/4] Enable usage of Rust IMA emulator in E2E tests. Instalation of Rust IMA emulator has been implemented in https://github.com/RedHat-SP-Security/keylime-tests/pull/141 Signed-off-by: Karel Srot --- packit-ci.fmf | 1 + 1 file changed, 1 insertion(+) diff --git a/packit-ci.fmf b/packit-ci.fmf index 9a9f9581..064bc0ae 100644 --- a/packit-ci.fmf +++ b/packit-ci.fmf @@ -4,6 +4,7 @@ environment: TPM_BINARY_MEASUREMENTS: /var/tmp/binary_bios_measurements + RUST_IMA_EMULATOR: 1 prepare: how: shell