use synta::der::decoder::Decoder;
use synta::types::oid::ObjectIdentifier;
use synta::{Decode, Encoding};
pub fn parse_name_attrs(raw: &[u8]) -> Vec<(String, String)> {
parse_name_attrs_inner(raw).unwrap_or_default()
}
fn parse_name_attrs_inner(raw: &[u8]) -> Option<Vec<(String, String)>> {
let mut dec = Decoder::new(raw, Encoding::Der);
dec.read_tag().ok()?;
let len = dec.read_length().ok()?.definite().ok()?;
let content = dec.read_bytes(len).ok()?;
let mut result = Vec::new();
let mut rdn_dec = Decoder::new(content, Encoding::Der);
while !rdn_dec.is_empty() {
rdn_dec.read_tag().ok()?;
let set_len = rdn_dec.read_length().ok()?.definite().ok()?;
let set_content = rdn_dec.read_bytes(set_len).ok()?;
let mut atv_dec = Decoder::new(set_content, Encoding::Der);
while !atv_dec.is_empty() {
atv_dec.read_tag().ok()?;
let seq_len = atv_dec.read_length().ok()?.definite().ok()?;
let seq_content = atv_dec.read_bytes(seq_len).ok()?;
let mut field_dec = Decoder::new(seq_content, Encoding::Der);
let oid = ObjectIdentifier::decode(&mut field_dec).ok()?;
let oid_str = oid.to_string();
let val_tag = field_dec.read_tag().ok()?;
let val_len = field_dec.read_length().ok()?.definite().ok()?;
let val_bytes = field_dec.read_bytes(val_len).ok()?;
let mut value = String::new();
append_string_value(&mut value, val_tag.number(), val_bytes);
result.push((oid_str, value));
}
}
Some(result)
}
pub fn format_dn(raw: &[u8]) -> String {
format_dn_inner(raw, false).unwrap_or_else(|| "<invalid>".to_string())
}
pub fn format_dn_slash(raw: &[u8]) -> String {
format_dn_inner(raw, true).unwrap_or_else(|| "<invalid>".to_string())
}
fn format_dn_inner(raw: &[u8], slash: bool) -> Option<String> {
let mut dec = Decoder::new(raw, Encoding::Der);
dec.read_tag().ok()?;
let len = dec.read_length().ok()?.definite().ok()?;
let content = dec.read_bytes(len).ok()?;
let mut out = String::with_capacity(64);
let mut rdn_dec = Decoder::new(content, Encoding::Der);
while !rdn_dec.is_empty() {
rdn_dec.read_tag().ok()?;
let set_len = rdn_dec.read_length().ok()?.definite().ok()?;
let set_content = rdn_dec.read_bytes(set_len).ok()?;
let mut atv_dec = Decoder::new(set_content, Encoding::Der);
while !atv_dec.is_empty() {
atv_dec.read_tag().ok()?;
let seq_len = atv_dec.read_length().ok()?.definite().ok()?;
let seq_content = atv_dec.read_bytes(seq_len).ok()?;
let mut field_dec = Decoder::new(seq_content, Encoding::Der);
let oid = ObjectIdentifier::decode(&mut field_dec).ok()?;
let val_tag = field_dec.read_tag().ok()?;
let val_len = field_dec.read_length().ok()?.definite().ok()?;
let val_bytes = field_dec.read_bytes(val_len).ok()?;
if slash {
out.push('/');
} else if !out.is_empty() {
out.push_str(", ");
}
append_attr_name(&mut out, oid.components());
out.push('=');
append_string_value(&mut out, val_tag.number(), val_bytes);
}
}
Some(out)
}
#[inline]
fn append_attr_name(out: &mut String, comps: &[u32]) {
use crate::oids::attr;
const ARC0: u32 = attr::COMMON_NAME[0];
const ARC1: u32 = attr::COMMON_NAME[1];
const ARC2: u32 = attr::COMMON_NAME[2];
const CN: u32 = attr::COMMON_NAME[3];
const SN: u32 = attr::SURNAME[3];
const SERNO: u32 = attr::SERIAL_NUMBER[3];
const CTRY: u32 = attr::COUNTRY[3];
const LCL: u32 = attr::LOCALITY[3];
const ST: u32 = attr::STATE[3];
const STR: u32 = attr::STREET[3];
const ORG: u32 = attr::ORGANIZATION[3];
const OU: u32 = attr::ORG_UNIT[3];
const TITL: u32 = attr::TITLE[3];
const GN: u32 = attr::GIVEN_NAME[3];
const INIT: u32 = attr::INITIALS[3];
const OI: u32 = attr::ORG_IDENTIFIER[3];
let name = if let &[a0, a1, a2, sub] = comps {
if a0 == ARC0 && a1 == ARC1 && a2 == ARC2 {
match sub {
CN => "CN",
SN => "SN",
SERNO => "SERIALNUMBER",
CTRY => "C",
LCL => "L",
ST => "ST",
STR => "STREET",
ORG => "O",
OU => "OU",
TITL => "T",
GN => "GN",
INIT => "I",
OI => "OI",
_ => "",
}
} else {
""
}
} else if comps == attr::EMAIL_ADDRESS {
"emailAddress"
} else if comps == attr::USER_ID {
"UID"
} else if comps == attr::DOMAIN_COMPONENT {
"DC"
} else {
""
};
if !name.is_empty() {
out.push_str(name);
} else {
use core::fmt::Write;
for (i, c) in comps.iter().enumerate() {
if i > 0 {
out.push('.');
}
let _ = write!(out, "{}", c);
}
}
}
pub fn decode_string_value(tag_number: u32, bytes: &[u8]) -> String {
let mut out = String::new();
append_string_value(&mut out, tag_number, bytes);
out
}
#[inline]
fn append_string_value(out: &mut String, tag_number: u32, bytes: &[u8]) {
use synta::tag::*;
match tag_number {
TAG_UTF8_STRING | TAG_PRINTABLE_STRING | TAG_IA5_STRING | TAG_VISIBLE_STRING
| TAG_NUMERIC_STRING | TAG_GENERAL_STRING => {
out.push_str(&String::from_utf8_lossy(bytes));
}
TAG_TELETEX_STRING => {
out.extend(bytes.iter().map(|&b| b as char));
}
TAG_BMP_STRING => {
out.extend(
bytes.chunks_exact(2).filter_map(|pair| {
char::from_u32(u16::from_be_bytes([pair[0], pair[1]]) as u32)
}),
);
}
TAG_UNIVERSAL_STRING => {
out.extend(bytes.chunks_exact(4).filter_map(|quad| {
char::from_u32(u32::from_be_bytes([quad[0], quad[1], quad[2], quad[3]]))
}));
}
_ => {
use core::fmt::Write;
out.push('#');
for b in bytes {
let _ = write!(out, "{:02x}", b);
}
}
}
}
pub struct NameBuilder {
attrs: Vec<(Vec<u32>, String)>,
}
impl Default for NameBuilder {
fn default() -> Self {
Self::new()
}
}
impl NameBuilder {
pub fn new() -> Self {
Self { attrs: Vec::new() }
}
pub fn add_attr(mut self, oid_comps: &[u32], value: &str) -> Self {
self.attrs.push((oid_comps.to_vec(), value.to_string()));
self
}
pub fn common_name(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::COMMON_NAME, value)
}
pub fn organization(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::ORGANIZATION, value)
}
pub fn organizational_unit(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::ORG_UNIT, value)
}
pub fn country(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::COUNTRY, value)
}
pub fn state(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::STATE, value)
}
pub fn locality(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::LOCALITY, value)
}
pub fn street(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::STREET, value)
}
pub fn email_address(self, value: &str) -> Self {
self.add_attr(crate::oids::attr::EMAIL_ADDRESS, value)
}
pub fn build(&self) -> Result<Vec<u8>, String> {
use synta::types::oid::ObjectIdentifier;
use synta::types::string::Utf8StringRef;
use synta::{Encoder, Encoding};
fn encode_tlv(tag: u8, content: &[u8]) -> Result<Vec<u8>, String> {
let len = content.len();
if len > 0xFFFF {
return Err(format!("TLV content too large: {len} bytes (max 65535)"));
}
let mut buf = Vec::with_capacity(content.len() + 4);
buf.push(tag);
if len <= 0x7F {
buf.push(len as u8);
} else if len <= 0xFF {
buf.push(0x81);
buf.push(len as u8);
} else {
buf.push(0x82);
buf.push((len >> 8) as u8);
buf.push((len & 0xFF) as u8);
}
buf.extend_from_slice(content);
Ok(buf)
}
let mut name_content: Vec<u8> = Vec::new();
for (oid_comps, value) in &self.attrs {
let oid = ObjectIdentifier::new(oid_comps)
.map_err(|e| format!("NameBuilder: invalid OID component slice: {e:?}"))?;
let mut oid_enc = Encoder::new(Encoding::Der);
oid_enc
.encode(&oid)
.map_err(|e| format!("NameBuilder: failed to encode OID: {e:?}"))?;
let oid_bytes = oid_enc
.finish()
.map_err(|e| format!("NameBuilder: failed to finish OID encoder: {e:?}"))?;
let val = Utf8StringRef::new(value);
let mut val_enc = Encoder::new(Encoding::Der);
val_enc
.encode(&val)
.map_err(|e| format!("NameBuilder: failed to encode value: {e:?}"))?;
let val_bytes = val_enc
.finish()
.map_err(|e| format!("NameBuilder: failed to finish value encoder: {e:?}"))?;
let mut atv_content: Vec<u8> = Vec::with_capacity(oid_bytes.len() + val_bytes.len());
atv_content.extend_from_slice(&oid_bytes);
atv_content.extend_from_slice(&val_bytes);
let atv_buf = encode_tlv(0x30, &atv_content)?;
let rdn_buf = encode_tlv(0x31, &atv_buf)?;
name_content.extend_from_slice(&rdn_buf);
}
encode_tlv(0x30, &name_content)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_name_der(oid_comps: &[u32], value: &str) -> Vec<u8> {
use synta::types::oid::ObjectIdentifier;
use synta::types::string::Utf8StringRef;
use synta::{Encoder, Encoding};
let oid = ObjectIdentifier::new(oid_comps).unwrap();
let mut oid_enc = Encoder::new(Encoding::Der);
oid_enc.encode(&oid).unwrap();
let oid_bytes = oid_enc.finish().unwrap();
let val = Utf8StringRef::new(value);
let mut val_enc = Encoder::new(Encoding::Der);
val_enc.encode(&val).unwrap();
let val_bytes = val_enc.finish().unwrap();
let atv_content: Vec<u8> = [oid_bytes.as_slice(), val_bytes.as_slice()].concat();
assert!(
atv_content.len() <= 127,
"test helper: ATV content too long for short-form DER length"
);
let mut atv_buf = vec![0x30u8, atv_content.len() as u8];
atv_buf.extend_from_slice(&atv_content);
assert!(
atv_buf.len() <= 127,
"test helper: RDN content too long for short-form DER length"
);
let mut rdn_buf = vec![0x31u8, atv_buf.len() as u8];
rdn_buf.extend_from_slice(&atv_buf);
assert!(
rdn_buf.len() <= 127,
"test helper: Name content too long for short-form DER length"
);
let mut name_buf = vec![0x30u8, rdn_buf.len() as u8];
name_buf.extend_from_slice(&rdn_buf);
name_buf
}
#[test]
fn test_format_dn_cn() {
let der = build_name_der(&[2, 5, 4, 3], "example.com");
let result = format_dn(&der);
assert_eq!(result, "CN=example.com");
}
#[test]
fn test_format_dn_country() {
let der = build_name_der(&[2, 5, 4, 6], "US");
let result = format_dn(&der);
assert_eq!(result, "C=US");
}
#[test]
fn test_format_dn_invalid() {
let result = format_dn(&[0x00, 0x01, 0x02]);
assert_eq!(result, "<invalid>");
}
#[test]
fn test_format_dn_empty_sequence() {
let der = vec![0x30u8, 0x00];
let result = format_dn(&der);
assert_eq!(result, "");
}
}