#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use core::fmt::Write as _;
use crate::types::constructed::Element;
use crate::Encoding;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Asn1FormatMode {
Hex,
Text,
Openssl,
}
pub fn format_asn1_bytes(bytes: &[u8], mode: Asn1FormatMode) -> String {
match mode {
Asn1FormatMode::Hex => {
let mut out = String::with_capacity(bytes.len() * 3);
push_space_hex(&mut out, bytes);
out
}
Asn1FormatMode::Text => {
use crate::traits::Decode;
let mut decoder = crate::der::decoder::Decoder::new(bytes, Encoding::Der);
match Element::decode(&mut decoder) {
Ok(element) => {
let mut out = String::with_capacity(bytes.len() * 4);
format_element(&element, 0, &mut out);
out
}
Err(e) => {
let mut out = String::new();
let _ = write!(out, "<decode error: {}> raw: ", e);
push_space_hex(&mut out, bytes);
out
}
}
}
Asn1FormatMode::Openssl => format_openssl(bytes),
}
}
const HEX_UPPER: &[u8; 16] = b"0123456789ABCDEF";
#[inline]
fn push_hex_byte(out: &mut String, b: u8) {
out.push(HEX_UPPER[(b >> 4) as usize] as char);
out.push(HEX_UPPER[(b & 0xf) as usize] as char);
}
fn push_space_hex(out: &mut String, bytes: &[u8]) {
for (i, &b) in bytes.iter().enumerate() {
if i > 0 {
out.push(' ');
}
push_hex_byte(out, b);
}
}
fn push_colon_hex(out: &mut String, bytes: &[u8]) {
for (i, &b) in bytes.iter().enumerate() {
if i > 0 {
out.push(':');
}
push_hex_byte(out, b);
}
}
const INDENT: &str =
" ";
#[inline]
fn push_indent(out: &mut String, depth: usize) {
let n = depth * 2;
out.push_str(&INDENT[..n.min(INDENT.len())]);
}
#[inline]
fn push_u32(out: &mut String, mut n: u32) {
let mut buf = [0u8; 10]; let mut i = 10usize;
loop {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
if n == 0 {
break;
}
}
out.push_str(unsafe { core::str::from_utf8_unchecked(&buf[i..]) });
}
#[inline]
fn push_i64(out: &mut String, n: i64) {
if n < 0 {
out.push('-');
if n == i64::MIN {
out.push_str("9223372036854775808");
return;
}
push_u64(out, (-n) as u64);
} else {
push_u64(out, n as u64);
}
}
fn push_u64(out: &mut String, mut n: u64) {
let mut buf = [0u8; 20]; let mut i = 20usize;
loop {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
if n == 0 {
break;
}
}
out.push_str(unsafe { core::str::from_utf8_unchecked(&buf[i..]) });
}
fn push_quoted(out: &mut String, s: &str) {
out.push('"');
if s.bytes().all(|b| b != b'"' && b != b'\\') {
out.push_str(s);
} else {
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
c => out.push(c),
}
}
}
out.push('"');
}
fn format_element(el: &Element<'_>, depth: usize, out: &mut String) {
match el {
Element::Boolean(b) => {
push_indent(out, depth);
out.push_str(if b.value() {
"BOOLEAN TRUE"
} else {
"BOOLEAN FALSE"
});
}
Element::Integer(i) => {
push_indent(out, depth);
out.push_str("INTEGER ");
match i.as_i64() {
Ok(n) => push_i64(out, n),
Err(_) => {
out.push_str("0x");
push_colon_hex(out, i.as_bytes());
}
}
}
Element::BitString(bs) => {
push_indent(out, depth);
out.push_str("BIT STRING ");
push_colon_hex(out, bs.as_bytes());
}
Element::OctetString(os) => {
let bytes = os.as_bytes();
push_indent(out, depth);
out.push_str("OCTET STRING ");
match core::str::from_utf8(bytes) {
Ok(text) if !text.chars().any(|c| c.is_control()) => {
push_quoted(out, text);
out.push_str(" [");
push_colon_hex(out, bytes);
out.push(']');
}
_ => push_colon_hex(out, bytes),
}
}
Element::Null(_) => {
push_indent(out, depth);
out.push_str("NULL");
}
Element::Real(r) => {
push_indent(out, depth);
out.push_str("REAL ");
let _ = write!(out, "{}", r.0);
}
Element::ObjectIdentifier(oid) => {
push_indent(out, depth);
out.push_str("OID ");
for (i, &c) in oid.components().iter().enumerate() {
if i > 0 {
out.push('.');
}
push_u32(out, c);
}
}
Element::Utf8String(s) => {
push_indent(out, depth);
out.push_str("UTF8String ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::PrintableString(s) => {
push_indent(out, depth);
out.push_str("PrintableString ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::IA5String(s) => {
push_indent(out, depth);
out.push_str("IA5String ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::NumericString(s) => {
push_indent(out, depth);
out.push_str("NumericString ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::VisibleString(s) => {
push_indent(out, depth);
out.push_str("VisibleString ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::TeletexString(s) => {
let bytes = s.as_bytes();
push_indent(out, depth);
out.push_str("TeletexString ");
match core::str::from_utf8(bytes) {
Ok(text) => {
push_quoted(out, text);
out.push_str(" [");
push_colon_hex(out, bytes);
out.push(']');
}
Err(_) => {
out.push('[');
push_colon_hex(out, bytes);
out.push(']');
}
}
}
Element::GeneralString(s) => {
let bytes = s.as_bytes();
push_indent(out, depth);
out.push_str("GeneralString ");
match core::str::from_utf8(bytes) {
Ok(text) => {
push_quoted(out, text);
out.push_str(" [");
push_colon_hex(out, bytes);
out.push(']');
}
Err(_) => {
out.push('[');
push_colon_hex(out, bytes);
out.push(']');
}
}
}
Element::UniversalString(s) => {
push_indent(out, depth);
out.push_str("UniversalString ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::BmpString(s) => {
push_indent(out, depth);
out.push_str("BMPString ");
push_quoted(out, s.as_str());
out.push_str(" [");
push_colon_hex(out, s.as_str().as_bytes());
out.push(']');
}
Element::UtcTime(t) => {
push_indent(out, depth);
let _ = write!(
out,
"UTCTime {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
t.year, t.month, t.day, t.hour, t.minute, t.second
);
}
Element::GeneralizedTime(t) => {
push_indent(out, depth);
let _ = write!(
out,
"GeneralizedTime {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
t.year, t.month, t.day, t.hour, t.minute, t.second
);
}
Element::Sequence(seq) => format_constructed("SEQUENCE", seq.iter(), depth, out),
Element::Set(set) => format_constructed("SET", set.iter(), depth, out),
Element::Tagged(tag, inner) => {
let class_prefix = match tag.class() {
crate::TagClass::ContextSpecific => "",
crate::TagClass::Application => "APPLICATION ",
crate::TagClass::Private => "PRIVATE ",
crate::TagClass::Universal => "UNIVERSAL ",
};
match inner.as_ref() {
Element::Sequence(_) | Element::Set(_) => {
push_indent(out, depth);
let _ = writeln!(out, "[{}{}] {{", class_prefix, tag.number());
format_element(inner, depth + 1, out);
out.push('\n');
push_indent(out, depth);
out.push('}');
}
other => {
push_indent(out, depth);
let _ = write!(out, "[{}{}] ", class_prefix, tag.number());
format_element(other, 0, out);
}
}
}
Element::Raw(tag, bytes) => {
let class_prefix = match tag.class() {
crate::TagClass::ContextSpecific => "",
crate::TagClass::Application => "APPLICATION ",
crate::TagClass::Private => "PRIVATE ",
crate::TagClass::Universal => "UNIVERSAL ",
};
push_indent(out, depth);
let _ = write!(out, "[{}{}]", class_prefix, tag.number());
if tag.is_constructed() {
out.push_str(" CONSTRUCTED");
}
out.push(' ');
push_colon_hex(out, bytes);
}
}
}
fn format_constructed<'a, I>(label: &str, iter: I, depth: usize, out: &mut String)
where
I: Iterator<Item = crate::Result<Element<'a>>>,
{
push_indent(out, depth);
out.push_str(label);
let block_start = out.len();
out.push_str(" {\n");
let mut has_children = false;
for result in iter {
if has_children {
out.push('\n');
}
match result {
Ok(child) => format_element(&child, depth + 1, out),
Err(e) => {
push_indent(out, depth + 1);
let _ = write!(out, "<decode error: {}>", e);
}
}
has_children = true;
}
if has_children {
out.push('\n');
push_indent(out, depth);
out.push('}');
} else {
out.truncate(block_start);
out.push_str(" {}");
}
}
fn format_openssl(bytes: &[u8]) -> String {
let mut out = String::new();
walk_openssl(bytes, 0, 0, &mut out);
if out.ends_with('\n') {
out.pop();
}
out
}
fn walk_openssl(data: &[u8], base_offset: usize, depth: usize, out: &mut String) -> usize {
let mut decoder = crate::der::decoder::Decoder::new(data, crate::Encoding::Der);
let tag = match decoder.read_tag() {
Ok(t) => t,
Err(_) => return 0,
};
let length = match decoder.read_length() {
Ok(l) => l,
Err(_) => return 0,
};
let content_len = match length.definite() {
Ok(n) => n,
Err(_) => return 0,
};
let header_len = decoder.position();
let cons_prim = if tag.is_constructed() { "cons" } else { "prim" };
let _ = write!(
out,
"{:5}:d={:2} hl={} l={:4} {}: ",
base_offset, depth, header_len, content_len, cons_prim,
);
push_openssl_tag_name(out, &tag);
if !tag.is_constructed() && content_len > 0 {
match decoder.read_bytes(content_len) {
Ok(content) => push_openssl_value(out, &tag, content),
Err(_) => return 0,
}
}
out.push('\n');
if tag.is_constructed() && content_len > 0 {
let end = header_len + content_len;
if end <= data.len() {
let content_data = &data[header_len..end];
let mut parsed = 0;
while parsed < content_len {
let consumed = walk_openssl(
&content_data[parsed..],
base_offset + header_len + parsed,
depth + 1,
out,
);
if consumed == 0 {
break;
}
parsed += consumed;
}
}
}
header_len + content_len
}
fn push_openssl_tag_name(out: &mut String, tag: &crate::Tag) {
match tag.class() {
crate::TagClass::Universal => out.push_str(match tag.number() {
1 => "BOOLEAN",
2 => "INTEGER",
3 => "BIT STRING",
4 => "OCTET STRING",
5 => "NULL",
6 => "OBJECT",
12 => "UTF8STRING",
16 => "SEQUENCE",
17 => "SET",
19 => "PrintableString",
22 => "IA5String",
23 => "UTCTIME",
24 => "GeneralizedTime",
_ => {
let _ = write!(out, "UNIVERSAL[{}]", tag.number());
return;
}
}),
crate::TagClass::Application => {
let _ = write!(out, "appl [ {} ]", tag.number());
}
crate::TagClass::ContextSpecific => {
let _ = write!(out, "cont [ {} ]", tag.number());
}
crate::TagClass::Private => {
let _ = write!(out, "priv [ {} ]", tag.number());
}
}
}
fn push_openssl_value(out: &mut String, tag: &crate::Tag, content: &[u8]) {
if tag.class() == crate::TagClass::Universal {
match tag.number() {
2..=4 if content.len() <= 8 => {
out.push(':');
for &b in content {
push_hex_byte(out, b);
}
}
6 => push_openssl_oid(out, content),
12 | 19 | 22 | 23 | 24 => {
if let Ok(s) = core::str::from_utf8(content) {
out.push(':');
out.push_str(s);
}
}
_ => {}
}
}
}
fn push_openssl_oid(out: &mut String, data: &[u8]) {
if data.is_empty() {
return;
}
let first = data[0];
let mut i = 1;
let mut components = [0u32; 64];
let mut n = 0usize;
if n < components.len() {
components[n] = (first / 40) as u32;
n += 1;
}
if n < components.len() {
components[n] = (first % 40) as u32;
n += 1;
}
while i < data.len() && n < components.len() {
let mut value: u32 = 0;
loop {
if i >= data.len() {
return;
}
let byte = data[i];
i += 1;
value = match value
.checked_shl(7)
.and_then(|v| v.checked_add((byte & 0x7F) as u32))
{
Some(v) => v,
None => return,
};
if byte & 0x80 == 0 {
break;
}
}
components[n] = value;
n += 1;
}
out.push(':');
for (j, &c) in components[..n].iter().enumerate() {
if j > 0 {
out.push('.');
}
let _ = write!(out, "{}", c);
}
}