diff --git a/keylime-agent-rust.spec b/keylime-agent-rust.spec index 9424185..5a5d4e1 100644 --- a/keylime-agent-rust.spec +++ b/keylime-agent-rust.spec @@ -15,7 +15,7 @@ Name: keylime-agent-rust Version: %{crate_version}~%{commitdate}git%{shortcommit} -Release: 2%{?dist} +Release: 3%{?dist} Summary: Rust agent for Keylime # Upstream license specification: Apache-2.0 @@ -60,6 +60,8 @@ Patch4: rust-keylime-set-supplementary-groups.patch Patch5: rust-keylime-adjust-features.patch # Load configuration file only once during startup Patch6: rust-keylime-load-config-once.patch +# Add support for PCR hash algorithms other than SHA-1 to the IMA emulator +Patch7: rust-keylime-ima-emulator-hash-algorithms.patch ExclusiveArch: %{rust_arches} @@ -379,6 +381,9 @@ install -Dpm 644 ./dist/systemd/system/var-lib-keylime-secure.mount \ %endif %changelog +* Fri Jul 08 2022 Anderson Toshiyuki Sasaki - 0.1.0~20220603gitaed51c7-3 +- Add support for hash algorithms other than SHA-1 to the IMA emulator + * Mon Jul 04 2022 Anderson Toshiyuki Sasaki - 0.1.0~20220603gitaed51c7-2 - Use classic release instead of autorelease macro - Use classic changelog instead of autochangelog macro diff --git a/rust-keylime-ima-emulator-hash-algorithms.patch b/rust-keylime-ima-emulator-hash-algorithms.patch new file mode 100644 index 0000000..66d21f7 --- /dev/null +++ b/rust-keylime-ima-emulator-hash-algorithms.patch @@ -0,0 +1,899 @@ +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