From d8386304334e40d0745b9988db11885e21239754 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Thu, 21 Mar 2024 17:04:38 +0100 Subject: [PATCH] keylime: Implement a simple IP parser to remove brackets The goal of the parser is to support IPv6 addresses to be used in the configuration file with or without brackets. The provided IP addresses are validated using the standard implementation of the IP parser. Signed-off-by: Anderson Toshiyuki Sasaki --- keylime/src/ip.pest | 11 ++++ keylime/src/ip_parser.rs | 123 +++++++++++++++++++++++++++++++++++++++ keylime/src/lib.rs | 1 + 3 files changed, 135 insertions(+) create mode 100644 keylime/src/ip.pest create mode 100644 keylime/src/ip_parser.rs diff --git a/keylime/src/ip.pest b/keylime/src/ip.pest new file mode 100644 index 00000000..a6a8fd5f --- /dev/null +++ b/keylime/src/ip.pest @@ -0,0 +1,11 @@ +WHITESPACE = _{ " " | NEWLINE | "\t" } + +ipv4 = { (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT)+ } + +ipv6 = { (ASCII_HEX_DIGIT* ~ ":")? ~ (ASCII_HEX_DIGIT+ ~ ":")* ~ +(ASCII_HEX_DIGIT+)? ~ (":" ~ ASCII_HEX_DIGIT+)* ~ (":" ~ ASCII_HEX_DIGIT*)?} + +unbracketed = { ipv4 | ipv6 } +bracketed = { "[" ~ unbracketed ~ "]" } + +ip = { SOI ~ (bracketed | unbracketed) ~ EOI } diff --git a/keylime/src/ip_parser.rs b/keylime/src/ip_parser.rs new file mode 100644 index 00000000..94b3aa7d --- /dev/null +++ b/keylime/src/ip_parser.rs @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Keylime Authors + +use pest::{iterators::Pair, Parser}; +use pest_derive::Parser; +use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr}; +use thiserror::Error; + +#[derive(Parser)] +#[grammar = "ip.pest"] +pub struct IpParser; + +#[derive(Error, Debug)] +pub enum IpParsingError { + #[error("Invalid input {0}")] + InvalidInput(String), + + #[error("Invalid IP")] + InvalidIP(#[from] AddrParseError), + + #[error("failed to parse the input {input}")] + ParseError { + input: String, + source: Box>, + }, + + #[error("Unexpected end of input")] + UnexpectedEOI, +} + +fn get_inner_ip(pair: Pair) -> Result<&str, IpParsingError> { + let Some(item) = pair.into_inner().next() else { + unreachable!() + }; + + match item.as_rule() { + Rule::ip | Rule::bracketed | Rule::unbracketed => get_inner_ip(item), + Rule::ipv4 => { + // Validate the IP using the standard parser + let _parsed_ipv4 = item.as_str().parse::()?; + Ok(item.as_str()) + } + Rule::ipv6 => { + // Validate the IP using the standard parser + let _parsed_ipv6 = item.as_str().parse::()?; + Ok(item.as_str()) + } + Rule::EOI => Err(IpParsingError::UnexpectedEOI), + _ => { + unreachable!() + } + } +} + +/// Parses an ip address from a string slice removing eventual brackets. +/// This is mostly to remove brackets when using IPv6 +/// +/// Both IPv4 and IPv6 are supported: +/// +/// * The IPv4 and IPv6 can be inside square brackets ("[]") or not +/// +/// # Arguments +/// +/// * `ip` the string to be parsed +/// +/// # Returns +/// +/// The obtained ip as a &str without brackets, if they were present +/// +/// # Examples +/// +/// Valid input lists, and respective result: +/// +/// * `127.0.0.1` => `127.0.0.1` +/// * `::1` => `::1` +/// * `[127.0.0.1]` => `127.0.0.1 +/// * `[::1]` => `::1` +pub fn parse_ip(ip: &str) -> Result<&str, IpParsingError> { + let Some(pair) = IpParser::parse(Rule::ip, ip) + .map_err(|e| IpParsingError::ParseError { + input: ip.to_string(), + source: Box::new(e), + })? + .next() + else { + return Err(IpParsingError::InvalidInput(ip.to_string())); + }; + return get_inner_ip(pair); +} + +// Unit Testing +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_ip() { + // Sanity: most common case + assert_eq!(parse_ip("127.0.0.1").unwrap(), "127.0.0.1"); //#[allow_ci] + assert_eq!(parse_ip("[127.0.0.1]").unwrap(), "127.0.0.1"); //#[allow_ci] + assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci] + assert_eq!(parse_ip("[::1]").unwrap(), "::1"); //#[allow_ci] + + // More advanced cases + assert_eq!(parse_ip("::").unwrap(), "::"); //#[allow_ci] + assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci] + assert_eq!(parse_ip("1::").unwrap(), "1::"); //#[allow_ci] + assert_eq!(parse_ip("1::1").unwrap(), "1::1"); //#[allow_ci] + assert_eq!(parse_ip("[1::1]").unwrap(), "1::1"); //#[allow_ci] + assert_eq!(parse_ip("1::2:3:4").unwrap(), "1::2:3:4"); //#[allow_ci] + assert_eq!(parse_ip("1:2::3:4").unwrap(), "1:2::3:4"); //#[allow_ci] + assert_eq!(parse_ip("1:2:3::4").unwrap(), "1:2:3::4"); //#[allow_ci] + assert_eq!(parse_ip("1:2:3:4:5:6:7:8").unwrap(), "1:2:3:4:5:6:7:8"); //#[allow_ci] + + // Invalid input + assert!(parse_ip("1").is_err()); + assert!(parse_ip("1.2").is_err()); + assert!(parse_ip("1.2.3.4.5").is_err()); + assert!(parse_ip("1:2:3").is_err()); + assert!(parse_ip("1::2::3").is_err()); + assert!(parse_ip("1:2::3:4::5:6").is_err()); + } +} diff --git a/keylime/src/lib.rs b/keylime/src/lib.rs index 17fff909..3f0213eb 100644 --- a/keylime/src/lib.rs +++ b/keylime/src/lib.rs @@ -1,6 +1,7 @@ pub mod algorithms; pub mod crypto; pub mod ima; +pub mod ip_parser; pub mod list_parser; pub mod tpm;