use super::der::{self, Tlv};
use super::sha1::sha1;
pub fn name_hash(subject_name: &[u8]) -> u32 {
let canon = canon_name(subject_name).unwrap_or_default();
let d = sha1(&canon);
u32::from_le_bytes([d[0], d[1], d[2], d[3]])
}
pub fn canon_name(subject_name: &[u8]) -> Result<Vec<u8>, der::Error> {
let (name, _) = der::read_tlv(subject_name)?;
if name.tag != der::SEQUENCE {
return Err(der::Error::Unexpected("Name is not a SEQUENCE"));
}
let mut out = Vec::new();
for rdn in der::children(name.content)? {
if rdn.tag != der::SET {
return Err(der::Error::Unexpected("RDN is not a SET"));
}
let mut avas: Vec<Vec<u8>> = der::children(rdn.content)?
.into_iter()
.map(canon_ava)
.collect::<Result<_, _>>()?;
avas.sort();
let mut avas_enc = Vec::new();
for a in avas {
avas_enc.extend(a);
}
out.extend(wrap(der::SET, &avas_enc));
}
Ok(out)
}
fn canon_ava(ava: Tlv<'_>) -> Result<Vec<u8>, der::Error> {
if ava.tag != der::SEQUENCE {
return Err(der::Error::Unexpected("AVA is not a SEQUENCE"));
}
let parts = der::children(ava.content)?;
let oid = parts
.first()
.filter(|t| t.tag == der::OID)
.ok_or(der::Error::Unexpected("AVA missing type OID"))?;
let value = parts
.get(1)
.ok_or(der::Error::Unexpected("AVA missing value"))?;
let value_enc = if is_canon_string(value.tag) {
let utf8 = to_utf8(value.tag, value.content);
let canon = canon_string(&utf8);
wrap(0x0c, &canon) } else {
value.raw.to_vec()
};
let mut inner = oid.raw.to_vec();
inner.extend(value_enc);
Ok(wrap(der::SEQUENCE, &inner))
}
fn is_canon_string(tag: u8) -> bool {
matches!(
tag,
0x0c | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x1a | 0x1b | 0x1c | 0x1e )
}
fn to_utf8(tag: u8, content: &[u8]) -> Vec<u8> {
match tag {
0x1e => {
let mut out = Vec::new();
for pair in content.chunks_exact(2) {
let cp = u16::from_be_bytes([pair[0], pair[1]]);
push_utf8(&mut out, cp as u32);
}
out
}
0x1c => {
let mut out = Vec::new();
for quad in content.chunks_exact(4) {
let cp = u32::from_be_bytes([quad[0], quad[1], quad[2], quad[3]]);
push_utf8(&mut out, cp);
}
out
}
0x14 | 0x15 => {
let mut out = Vec::new();
for &b in content {
push_utf8(&mut out, b as u32);
}
out
}
_ => content.to_vec(),
}
}
fn push_utf8(out: &mut Vec<u8>, cp: u32) {
match char::from_u32(cp) {
Some(c) => {
let mut b = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
}
None => out.push(b'?'),
}
}
fn canon_string(utf8: &[u8]) -> Vec<u8> {
let start = utf8.iter().position(|&b| b != b' ').unwrap_or(utf8.len());
let end = utf8
.iter()
.rposition(|&b| b != b' ')
.map(|p| p + 1)
.unwrap_or(start);
let trimmed = &utf8[start..end];
let mut out = Vec::with_capacity(trimmed.len());
let mut i = 0;
while i < trimmed.len() {
let b = trimmed[i];
if b >= 0x80 {
out.push(b);
i += 1;
} else if b == b' ' {
out.push(b' ');
while i < trimmed.len() && trimmed[i] == b' ' {
i += 1;
}
} else {
out.push(b.to_ascii_lowercase());
i += 1;
}
}
out
}
fn wrap(tag: u8, content: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(content.len() + 4);
out.push(tag);
let len = content.len();
if len < 0x80 {
out.push(len as u8);
} else {
let bytes = len.to_be_bytes();
let first = bytes.iter().position(|&b| b != 0).unwrap();
let sig = &bytes[first..];
out.push(0x80 | sig.len() as u8);
out.extend_from_slice(sig);
}
out.extend_from_slice(content);
out
}
#[cfg(test)]
mod tests {
use super::canon_string;
#[test]
fn collapses_and_lowercases() {
assert_eq!(canon_string(b" Hello World "), b"hello world");
assert_eq!(canon_string(b"ACME"), b"acme");
assert_eq!(canon_string(b" "), b"");
}
}