From a1547f9d147339f1a1bb2c54d0d8979160011c57 Mon Sep 17 00:00:00 2001 From: Takuma IMAMURA <209989118+hyperfinitism@users.noreply.github.com> Date: Thu, 30 Apr 2026 00:43:52 +0900 Subject: [PATCH] feat!: add typed Template identifier and support all templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the stringly-typed template name with a typed Template enum and add full parse/encode/decode coverage for every template documented in the IMA spec. Drops the IMA_*_TEMPLATE_NAME string constants in favor of Template::parse / Template::as_str. New types: - Template — enum with variants for ima, ima-ng, ima-sig, ima-buf, ima-modsig, ima-ngv2, ima-sigv2, evm-sig, plus Other(String) for forward compatibility. - DigestType (Ima / Verity / Other) and DigestV2 — model the ::\0 shape used by d-ngv2. - TemplateData::{ImaModsig, ImaNgV2, ImaSigV2, EvmSig} variants and matching ImaModsigEntry / ImaNgV2Entry / ImaSigV2Entry / EvmSigEntry payload structs. Parsers / encoders: - Binary parser dispatches on Template, decoding the four new payload shapes (decode_ima_modsig/_ngv2/_sigv2/_evm_sig) plus the d-ngv2 field decoder and u16/u32 LE field helpers. - ASCII parser learns the same templates, including digest-type-prefixed d-ngv2 columns and the evm-sig column layout. - template_hash framing covers the new TemplateData variants so recomputed template hashes still match the wire bytes. Tests: - Expanded src/log/parser.rs unit tests from 4 to 64 cases. New coverage: Endianness conversion, ParseOptions builders/accessors, decode_d_ng / decode_d_ngv2 / decode_n_ng success and error paths, parse_u32_le / parse_u16_le, split_fields edge cases, decode_legacy_ima error paths, per-template field-count rejection, end-to-end roundtrips through EventLogParser for every built-in template plus the Unknown fallback, and iterator semantics (empty input, mid-record EOF, fused-after-error, big-endian length decoding, max_field_len cap). - Updated tests/integration.rs and tests/testdata.rs for the Event::template_name → Event::template rename. Public API: Event::template_name (String) → Event::template (Template). Examples and README adjusted accordingly. Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 Signed-off-by: Takuma IMAMURA <209989118+hyperfinitism@users.noreply.github.com> --- README.md | 4 +- examples/parse_ascii_log.rs | 11 +- examples/parse_binary_log.rs | 11 +- src/lib.rs | 4 +- src/log.rs | 13 +- src/log/ascii.rs | 151 +++++- src/log/event.rs | 6 +- src/log/parser.rs | 906 +++++++++++++++++++++++++++++++++-- src/log/template.rs | 209 ++++++++ src/log/template_hash.rs | 81 +++- tests/integration.rs | 4 +- tests/testdata.rs | 14 +- 12 files changed, 1321 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index c3d34fd..458ed6a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ let opts = ParseOptions::default() .with_template_hash_algorithm(HashAlgorithm::Sha1); for event in EventLogParser::new(bytes.as_slice(), opts) { let event = event?; - println!("PCR {} {}", event.pcr_index, event.template_name); + println!("PCR {} {}", event.pcr_index, event.template); } ``` @@ -67,7 +67,7 @@ use ima_parser::log::parse_ascii_log; let line = "10 91f34b5c671d73504b274a919661cf80dab1e127 ima-ng sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 boot_aggregate\n"; let events = parse_ascii_log(line).unwrap(); assert_eq!(events.len(), 1); -assert_eq!(events[0].template_name, "ima-ng"); +assert_eq!(events[0].template.as_str(), "ima-ng"); ``` #### Recomputing a template hash diff --git a/examples/parse_ascii_log.rs b/examples/parse_ascii_log.rs index 9ebe641..fa7db01 100644 --- a/examples/parse_ascii_log.rs +++ b/examples/parse_ascii_log.rs @@ -96,6 +96,7 @@ fn main() -> ExitCode { } TemplateData::ImaBuf(e) => format!("{} [{}] buf={}B", e.name, e.digest, e.buf.len()), TemplateData::Unknown(fields) => format!("{} unknown-field(s)", fields.len()), + _ => "other built-in template".to_owned(), }; if args.verify { #[cfg(feature = "hash")] @@ -108,10 +109,16 @@ fn main() -> ExitCode { let ok = "no-hash-feature"; println!( "PCR={:>2} {:<8} hash-ok={} {hint}", - ev.pcr_index, ev.template_name, ok + ev.pcr_index, + ev.template.as_str(), + ok ); } else { - println!("PCR={:>2} {:<8} {hint}", ev.pcr_index, ev.template_name); + println!( + "PCR={:>2} {:<8} {hint}", + ev.pcr_index, + ev.template.as_str() + ); } } ExitCode::SUCCESS diff --git a/examples/parse_binary_log.rs b/examples/parse_binary_log.rs index bfbafc0..7a9f2a7 100644 --- a/examples/parse_binary_log.rs +++ b/examples/parse_binary_log.rs @@ -107,6 +107,7 @@ fn main() -> ExitCode { } TemplateData::ImaBuf(e) => format!("{} [{}] buf={}B", e.name, e.digest, e.buf.len()), TemplateData::Unknown(fields) => format!("{} unknown-field(s)", fields.len()), + _ => "other built-in template".to_owned(), }; if args.verify { #[cfg(feature = "hash")] @@ -119,10 +120,16 @@ fn main() -> ExitCode { let ok = "no-hash-feature"; println!( "PCR={:>2} {:<8} hash-ok={} {hint}", - ev.pcr_index, ev.template_name, ok + ev.pcr_index, + ev.template.as_str(), + ok ); } else { - println!("PCR={:>2} {:<8} {hint}", ev.pcr_index, ev.template_name); + println!( + "PCR={:>2} {:<8} {hint}", + ev.pcr_index, + ev.template.as_str() + ); } } ExitCode::SUCCESS diff --git a/src/lib.rs b/src/lib.rs index fcf3204..4a4bae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ //! .with_template_hash_algorithm(HashAlgorithm::Sha1); //! for event in EventLogParser::new(bytes.as_slice(), opts) { //! let event = event?; -//! println!("PCR {} {}", event.pcr_index, event.template_name); +//! println!("PCR {} {}", event.pcr_index, event.template); //! } //! # Ok::<(), Box>(()) //! ``` @@ -62,7 +62,7 @@ //! let line = "10 91f34b5c671d73504b274a919661cf80dab1e127 ima-ng sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 boot_aggregate\n"; //! let events = parse_ascii_log(line).unwrap(); //! assert_eq!(events.len(), 1); -//! assert_eq!(events[0].template_name, "ima-ng"); +//! assert_eq!(events[0].template.as_str(), "ima-ng"); //! ``` //! //! ### Recomputing a template hash diff --git a/src/log.rs b/src/log.rs index a7eb505..2144df1 100644 --- a/src/log.rs +++ b/src/log.rs @@ -26,7 +26,9 @@ pub use self::ascii::{parse_ascii_line, parse_ascii_log}; pub use self::event::Event; pub use self::parser::{Endianness, EventLogParser, ParseOptions}; pub use self::template::{ - Digest, ImaBufEntry, ImaEntry, ImaNgEntry, ImaSigEntry, TemplateData, TemplateField, + Digest, DigestType, DigestV2, EvmSigEntry, ImaBufEntry, ImaEntry, ImaModsigEntry, ImaNgEntry, + ImaNgV2Entry, ImaSigEntry, ImaSigV2Entry, Template, TemplateData, TemplateField, + TemplateFieldId, }; /// Default PCR index used by IMA (`CONFIG_IMA_MEASURE_PCR_IDX`). @@ -36,12 +38,3 @@ pub const DEFAULT_IMA_PCR: u32 = 10; /// The legacy `ima` template pads the `n` field to this size plus one when /// computing the template hash. pub const IMA_EVENT_NAME_LEN_MAX: usize = 255; - -/// Template name of the legacy fixed-format template. -pub const IMA_TEMPLATE_NAME: &str = "ima"; -/// Template name of the `ima-ng` (next-generation) template. -pub const IMA_NG_TEMPLATE_NAME: &str = "ima-ng"; -/// Template name of the `ima-sig` template. -pub const IMA_SIG_TEMPLATE_NAME: &str = "ima-sig"; -/// Template name of the `ima-buf` template. -pub const IMA_BUF_TEMPLATE_NAME: &str = "ima-buf"; diff --git a/src/log/ascii.rs b/src/log/ascii.rs index 1faaf5e..b3f62d5 100644 --- a/src/log/ascii.rs +++ b/src/log/ascii.rs @@ -5,7 +5,7 @@ //! The format of `/sys/kernel/security/ima/ascii_runtime_measurements_*` is: //! //! ```text -//! +//!