diff --git a/Cargo.toml b/Cargo.toml index b55423f..7ef69e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,4 @@ asn1_derive = { path = "asn1_derive/", version = "0.17.0" } libc = "0.2.11" [workspace] -members = ["asn1_derive"] +members = ["asn1_derive", "asn1parse"] diff --git a/asn1parse/Cargo.toml b/asn1parse/Cargo.toml new file mode 100644 index 0000000..366ab5f --- /dev/null +++ b/asn1parse/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "asn1parse" +version = "0.1.0" +edition = "2021" + +[dependencies] +asn1 = { version = "0.17", path = ".." } +clap = { version = "4.5.15", features = ["derive"] } +hex = "0.4.3" +pem = { version = "3.0.4", default-features = false, features = ["std"] } diff --git a/asn1parse/src/lib.rs b/asn1parse/src/lib.rs new file mode 100644 index 0000000..5b3dfe2 --- /dev/null +++ b/asn1parse/src/lib.rs @@ -0,0 +1,156 @@ +#[derive(asn1::Asn1Read)] +pub enum Value<'a> { + SequenceOf(asn1::SequenceOf<'a, Value<'a>>), + SetOf(asn1::SetOf<'a, Value<'a>>), + Integer(asn1::BigInt<'a>), + Boolean(bool), + ObjectIdentifier(asn1::ObjectIdentifier), + Null(asn1::Null), + PrintableString(asn1::PrintableString<'a>), + IA5String(asn1::IA5String<'a>), + Utf8String(asn1::Utf8String<'a>), + OctetString(&'a [u8]), + BitString(asn1::BitString<'a>), + UtcTime(asn1::UtcTime), + + Fallback(asn1::Tlv<'a>), +} + +impl Value<'_> { + pub fn render( + &self, + out: &mut dyn std::io::Write, + indent: usize, + ) -> Result<(), Box> { + match self { + Value::SequenceOf(v) => { + writeln!(out, "{}SEQUENCE", " ".repeat(indent))?; + for el in v.clone() { + el.render(out, indent + 2)?; + } + } + Value::SetOf(v) => { + writeln!(out, "{}SET", " ".repeat(indent))?; + for el in v.clone() { + el.render(out, indent + 2)?; + } + } + Value::Integer(v) => { + let data = hex::encode(v.as_bytes()); + let mut output = data.trim_start_matches('0'); + if output.is_empty() { + output = "0"; + } + + writeln!( + out, + "{}INTEGER: {}0x{}", + " ".repeat(indent), + if v.is_negative() { "-" } else { "" }, + output, + )?; + } + Value::Boolean(v) => { + writeln!(out, "{}BOOLEAN: {:?}", " ".repeat(indent), v)?; + } + Value::ObjectIdentifier(v) => { + writeln!(out, "{}OBJECT IDENTIFIER: {}", " ".repeat(indent), v)?; + } + Value::PrintableString(v) => { + writeln!( + out, + "{}PRINTABLE STRING: {:?}", + " ".repeat(indent), + v.as_str() + )?; + } + Value::IA5String(v) => { + writeln!(out, "{}IA5 STRING: {:?}", " ".repeat(indent), v.as_str())?; + } + Value::Utf8String(v) => { + writeln!(out, "{}UTF8 STRING: {:?}", " ".repeat(indent), v.as_str())?; + } + Value::OctetString(v) => { + if let Ok(v) = asn1::parse_single::>(v) { + writeln!(out, "{}OCTET STRING", " ".repeat(indent))?; + v.render(out, indent + 2)?; + } else { + writeln!(out, "{}OCTET STRING: {:?}", " ".repeat(indent), v)?; + } + } + Value::BitString(v) => { + writeln!( + out, + "{}BIT STRING: {:?}", + " ".repeat(indent), + hex::encode(v.as_bytes()) + )?; + } + Value::UtcTime(v) => { + let dt = v.as_datetime(); + writeln!( + out, + "{}UTC TIME: {}-{:02}-{:02} {:02}:{:02}:{:02}", + " ".repeat(indent), + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + )?; + } + Value::Null(_) => { + writeln!(out, "{}NULL", " ".repeat(indent))?; + } + Value::Fallback(tlv) => { + let tag = tlv.tag(); + if tag.is_constructed() { + writeln!( + out, + "{}[{:?} {}]", + " ".repeat(indent), + tag.class(), + tag.value() + )?; + asn1::parse_single::>(tlv.data())?.render(out, indent + 2)?; + } else { + writeln!( + out, + "{}[{:?} {} PRIMITIVE]: {:?}", + " ".repeat(indent), + tag.class(), + tag.value(), + tlv.data() + )?; + } + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::Value; + + #[test] + fn test_render() { + for (der, expected) in [ + (b"\x01\x01\xff" as &[u8], "BOOLEAN: true\n"), + (b"\x17\x0d910506234540Z", "UTC TIME: 1991-05-06 23:45:40\n"), + (b"\x13\x03abc", "PRINTABLE STRING: \"abc\"\n"), + (b"\x16\x03abc", "IA5 STRING: \"abc\"\n"), + (b"\x03\x03\x04\x81\xf0", "BIT STRING: \"81f0\"\n"), + (b"\x02\x01\x00", "INTEGER: 0x0\n"), + (b"\x02\x01\x02", "INTEGER: 0x2\n"), + (b"\x02\x01\x80", "INTEGER: -0x80\n"), + ] { + let v = asn1::parse_single::>(der).unwrap(); + let mut output = vec![]; + v.render(&mut output, 0).unwrap(); + assert_eq!(std::str::from_utf8(&output).unwrap(), expected); + } + } +} diff --git a/asn1parse/src/main.rs b/asn1parse/src/main.rs new file mode 100644 index 0000000..19bdffd --- /dev/null +++ b/asn1parse/src/main.rs @@ -0,0 +1,33 @@ +use clap::Parser; +use std::io::Read; +use std::path::PathBuf; + +#[derive(clap::Parser)] +struct Args { + #[clap(long)] + pem: bool, + + #[clap()] + path: Option, +} + +fn main() -> Result<(), Box> { + let args = Args::parse(); + + let mut data = if let Some(path) = args.path { + std::fs::read(path)? + } else { + let mut buf = vec![]; + std::io::stdin().read_to_end(&mut buf)?; + buf + }; + + if args.pem { + data = pem::parse(data)?.into_contents(); + } + + let v = asn1::parse_single::>(&data)?; + v.render(&mut std::io::stdout().lock(), 0)?; + + Ok(()) +}