# 8. `asn1parse.rs` — ASN.1 parser utility
[← Example index](index.md) · [asn1parse.rs on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/asn1parse.rs)
Command-line tool that reads PEM or DER files and prints a structured
ASN.1 tree similar to `openssl asn1parse`. Useful for debugging encoded
data. Run as: `cargo run --example asn1parse <file>`.
## Source
```rust,ignore
//! ASN.1 Parser Utility
//!
//! This utility reads PEM or DER files and outputs an ASN.1 tree structure
//! similar to `openssl asn1parse`. It's useful for debugging and validating
//! ASN.1 encoded data.
//!
//! Usage:
//! cargo run --example asn1parse <file>
//! cargo run --example asn1parse <file> --offset <bytes>
//!
//! The output format matches `openssl asn1parse`:
//! offset:d=depth hl=header_len l=content_len cons/prim: TYPE [value]
//!
//! Examples:
//! cargo run --example asn1parse certificate.pem
//! cargo run --example asn1parse certificate.der
//! cargo run --example asn1parse data.der --offset 4
use std::env;
use std::fs;
use std::io::{self, Read};
use synta::{Decoder, Encoding, Tag, TagClass};
/// Parse DER or PEM file
fn read_asn1_file(path: &str) -> io::Result<Vec<u8>> {
let mut file = fs::File::open(path)?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
// Try to interpret as PEM first
if contents.windows(11).any(|w| w == b"-----BEGIN ") {
if let Some(der_data) = synta_certificate::pem_to_der(&contents).into_iter().next() {
return Ok(der_data);
}
}
// Otherwise assume it's DER
Ok(contents)
}
/// Format tag name
fn tag_name(tag: &Tag) -> String {
match tag.class() {
TagClass::Universal => match tag.number() {
1 => "BOOLEAN".to_string(),
2 => "INTEGER".to_string(),
3 => "BIT STRING".to_string(),
4 => "OCTET STRING".to_string(),
5 => "NULL".to_string(),
6 => "OBJECT".to_string(),
12 => "UTF8STRING".to_string(),
16 => "SEQUENCE".to_string(),
17 => "SET".to_string(),
19 => "PrintableString".to_string(),
22 => "IA5String".to_string(),
23 => "UTCTIME".to_string(),
24 => "GeneralizedTime".to_string(),
n => format!("UNIVERSAL[{}]", n),
},
TagClass::Application => format!("appl [ {} ]", tag.number()),
TagClass::ContextSpecific => format!("cont [ {} ]", tag.number()),
TagClass::Private => format!("priv [ {} ]", tag.number()),
}
}
/// Calculate tag encoding length
fn tag_length(tag: &Tag) -> usize {
if tag.number() < 31 {
1
} else {
// High-tag-number form: 1 byte + variable bytes
let mut num = tag.number();
let mut len = 1;
while num > 0 {
len += 1;
num >>= 7;
}
len
}
}
/// Calculate length encoding size
fn length_encoding_size(len: usize) -> usize {
if len < 128 {
1
} else if len < 256 {
2
} else if len < 65536 {
3
} else if len < 16777216 {
4
} else {
5
}
}
/// Parse and print ASN.1 tree
fn parse_tree(data: &[u8], depth: usize, base_offset: usize) -> synta::Result<usize> {
let mut decoder = Decoder::new(data, Encoding::Der);
let start_pos = decoder.position();
// Read tag
let tag = decoder.read_tag()?;
let tag_pos = start_pos;
let tag_len = tag_length(&tag);
// Read length
let length = decoder.read_length()?;
let content_len = length.definite()?;
let length_len = length_encoding_size(content_len);
let header_len = tag_len + length_len;
let cons_prim = if tag.is_constructed() { "cons" } else { "prim" };
let tag_name_str = tag_name(&tag);
// Read content for primitive types
let value_str = if !tag.is_constructed() && content_len > 0 {
let content = decoder.read_bytes(content_len)?;
// Format value based on type
match tag.class() {
TagClass::Universal => match tag.number() {
2 => {
// INTEGER - show hex if small
if content.len() <= 8 {
format!(
":{}",
content
.iter()
.map(|b| format!("{:02X}", b))
.collect::<String>()
)
} else {
String::new()
}
}
3 | 4 => {
// BIT STRING or OCTET STRING
if content.len() <= 8 {
format!(
":{}",
content
.iter()
.map(|b| format!("{:02X}", b))
.collect::<String>()
)
} else {
String::new()
}
}
6 => {
// OBJECT IDENTIFIER - decode it
if let Ok(oid_str) = decode_oid(content) {
format!(":{}", oid_str)
} else {
String::new()
}
}
12 | 19 | 22 => {
// String types
if let Ok(s) = std::str::from_utf8(content) {
format!(":{}", s)
} else {
String::new()
}
}
23 | 24 => {
// Time types
if let Ok(s) = std::str::from_utf8(content) {
format!(":{}", s)
} else {
String::new()
}
}
_ => String::new(),
},
_ => String::new(),
}
} else {
String::new()
};
println!(
"{:5}:d={:2} hl={} l={:4} {}: {}{}",
base_offset + tag_pos,
depth,
header_len,
content_len,
cons_prim,
tag_name_str,
value_str
);
// Recursively parse constructed types
if tag.is_constructed() && content_len > 0 {
let content_start = decoder.position();
let content_data = &data[content_start..content_start + content_len];
let mut parsed = 0;
while parsed < content_len {
let child_len = parse_tree(
&content_data[parsed..],
depth + 1,
base_offset + content_start + parsed,
)?;
parsed += child_len;
}
}
Ok(header_len + content_len)
}
/// Decode OID from bytes
fn decode_oid(data: &[u8]) -> Result<String, ()> {
if data.is_empty() {
return Err(());
}
let mut components = Vec::new();
// First byte encodes first two components
let first = data[0];
components.push((first / 40) as u32);
components.push((first % 40) as u32);
// Remaining bytes encode subsequent components
let mut i = 1;
while i < data.len() {
let mut value = 0u32;
loop {
if i >= data.len() {
break;
}
let byte = data[i];
i += 1;
value = (value << 7) | (byte & 0x7F) as u32;
if byte & 0x80 == 0 {
break;
}
}
components.push(value);
}
Ok(components
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("."))
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <file> [--offset <bytes>]", args[0]);
eprintln!();
eprintln!("Parse ASN.1 DER or PEM file and display structure.");
eprintln!();
eprintln!("Examples:");
eprintln!(" {} certificate.pem", args[0]);
eprintln!(" {} certificate.der", args[0]);
eprintln!(" {} data.der --offset 4", args[0]);
std::process::exit(1);
}
let file_path = &args[1];
// Parse optional offset
let start_offset = if args.len() >= 4 && args[2] == "--offset" {
args[3].parse::<usize>().unwrap_or(0)
} else {
0
};
// Read file
let data = match read_asn1_file(file_path) {
Ok(data) => data,
Err(e) => {
eprintln!("Error reading file: {}", e);
std::process::exit(1);
}
};
// Apply offset if specified
let parse_data = if start_offset > 0 {
if start_offset >= data.len() {
eprintln!("Offset {} exceeds file size {}", start_offset, data.len());
std::process::exit(1);
}
&data[start_offset..]
} else {
&data
};
// Parse ASN.1 tree
match parse_tree(parse_data, 0, start_offset) {
Ok(_) => {}
Err(e) => {
eprintln!("Error parsing ASN.1: {:?}", e);
std::process::exit(1);
}
}
}
```