Add support for hash algorithms other than SHA-1 to the IMA emulator

Related: rhbz#2084552

Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
This commit is contained in:
Anderson Toshiyuki Sasaki 2022-07-08 16:36:43 +02:00
parent e1b08139e2
commit ba6d6e548f
2 changed files with 905 additions and 1 deletions

View File

@ -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 <ansasaki@redhat.com> - 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 <ansasaki@redhat.com> - 0.1.0~20220603gitaed51c7-2
- Use classic release instead of autorelease macro
- Use classic changelog instead of autochangelog macro

View File

@ -0,0 +1,899 @@
From d30000a1c3ae6cb8f6dc60c295e037614605b36e Mon Sep 17 00:00:00 2001
From: Daiki Ueno <dueno@redhat.com>
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 <dueno@redhat.com>
---
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<HashAlgorithm> for HashingAlgorithm {
}
}
+impl From<HashAlgorithm> 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 <dueno@redhat.com>
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 <dueno@redhat.com>
---
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<u8>,
+}
+
+impl Digest {
+ pub fn new(algorithm: HashAlgorithm, value: &[u8]) -> Result<Self> {
+ 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<Self, Self::Error> {
+ 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<Self, Self::Error> {
+ 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<u8>,
+}
+
+impl TryFrom<&str> for Signature {
+ type Error = std::io::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ 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<u8>,
+}
+
+impl TryFrom<&str> for Buffer {
+ type Error = std::io::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ 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<Self, Self::Error> {
+ 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<Self, Self::Error> {
+ 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<Signature>,
+}
+
+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<Self, Self::Error> {
+ // 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<Self, Self::Error> {
+ 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<dyn EventData>,
+}
+
+impl TryFrom<&str> for Entry {
+ type Error = std::io::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ 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 <dueno@redhat.com>
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 <dueno@redhat.com>
---
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<T> = std::result::Result<T, ImaEmulatorError>;
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<usize> {
let f = File::open(ml)?;
let mut reader = BufReader::new(f);
- let mut running_hash: Vec<u8> = 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<String>,
+ #[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 <ksrot@redhat.com>
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 <ksrot@redhat.com>
---
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