Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
airdrop
src
ber.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
ber.rs
Blame
History
Raw
//! BER to DER, as far as CMS needs it. //! //! Apple's validation records are BER: the outer structures use indefinite //! lengths and the signed content is a constructed OCTET STRING (as OpenSSL //! streams a `SignedData`). The `der` crate, rightly, only reads DER, so the //! record is normalised first. Nothing signed depends on the framing: a CMS //! signature covers the content octets and the DER of the signed attributes, //! which are recomputed from the parsed values. use thiserror::Error; /// Deeper than any CMS structure. const MAX_DEPTH: usize = 32; /// Larger than any record; stops a hostile length from allocating. const MAX_BYTES: usize = 1 << 20; #[derive(Debug, Error)] pub enum BerError { #[error("truncated")] Truncated, #[error("unsupported or malformed encoding")] Malformed, #[error("nested too deeply")] Deep, #[error("too large")] Large, } /// Re-encode `input` (one BER value) as DER. pub fn to_der(input: &[u8]) -> Result<Vec<u8>, BerError> { if input.len() > MAX_BYTES { return Err(BerError::Large); } let (out, rest) = value(input, 0)?; // Trailing zero padding is tolerated, anything else is not a record. if rest.iter().any(|&b| b != 0) { return Err(BerError::Malformed); } Ok(out) } struct Header { tag: u8, constructed: bool, /// `None`: indefinite. len: Option<usize>, } fn header(input: &[u8]) -> Result<(Header, &[u8]), BerError> { let (&tag, rest) = input.split_first().ok_or(BerError::Truncated)?; if tag & 0x1f == 0x1f { return Err(BerError::Malformed); // multi-byte tags do not occur in CMS } let (&first, mut rest) = rest.split_first().ok_or(BerError::Truncated)?; let len = match first { 0x80 => None, n if n < 0x80 => Some(usize::from(n)), n => { let count = usize::from(n & 0x7f); if count > 4 || rest.len() < count { return Err(BerError::Malformed); } let len = rest[..count] .iter() .fold(0usize, |acc, &b| (acc << 8) | usize::from(b)); rest = &rest[count..]; Some(len) } }; let constructed = tag & 0x20 != 0; if len.is_none() && !constructed { return Err(BerError::Malformed); } Ok(( Header { tag, constructed, len, }, rest, )) } /// One value: its DER encoding and what follows it. fn value(input: &[u8], depth: usize) -> Result<(Vec<u8>, &[u8]), BerError> { if depth > MAX_DEPTH { return Err(BerError::Deep); } let (h, rest) = header(input)?; if !h.constructed { let len = h.len.ok_or(BerError::Malformed)?; if rest.len() < len { return Err(BerError::Truncated); } return Ok((encode(h.tag, &rest[..len]), &rest[len..])); } let (children, after) = children(rest, h.len, depth)?; // A constructed OCTET STRING is a sequence of OCTET STRINGs: join them. if h.tag == 0x24 { let mut joined = Vec::new(); for child in &children { joined.extend_from_slice(&octets(child)?); } return Ok((encode(0x04, &joined), after)); } Ok((encode(h.tag, &children.concat()), after)) } /// The DER children of a constructed value, and what follows it. fn children( mut rest: &[u8], len: Option<usize>, depth: usize, ) -> Result<(Vec<Vec<u8>>, &[u8]), BerError> { let mut out = Vec::new(); match len { Some(len) => { if rest.len() < len { return Err(BerError::Truncated); } let (mut body, after) = rest.split_at(len); while !body.is_empty() { let (child, tail) = value(body, depth + 1)?; out.push(child); body = tail; } Ok((out, after)) } None => loop { if rest.starts_with(&[0, 0]) { return Ok((out, &rest[2..])); } let (child, tail) = value(rest, depth + 1)?; out.push(child); rest = tail; }, } } /// The content of an already-normalised OCTET STRING. fn octets(der: &[u8]) -> Result<Vec<u8>, BerError> { let (h, rest) = header(der)?; match (h.tag, h.len) { (0x04, Some(len)) if rest.len() == len => Ok(rest.to_vec()), _ => Err(BerError::Malformed), } } fn encode(tag: u8, content: &[u8]) -> Vec<u8> { let mut out = vec![tag]; match content.len() { n if n < 0x80 => out.push(n as u8), n => { let bytes = n.to_be_bytes(); let skip = bytes.iter().take_while(|&&b| b == 0).count(); out.push(0x80 | (bytes.len() - skip) as u8); out.extend_from_slice(&bytes[skip..]); } } out.extend_from_slice(content); out } #[cfg(test)] mod tests { use super::*; #[test] fn definite_der_passes_through() { let der = [0x30, 0x06, 0x04, 0x01, 0xaa, 0x02, 0x01, 0x05]; assert_eq!(to_der(&der).unwrap(), der); } #[test] fn indefinite_lengths_become_definite() { let ber = [0x30, 0x80, 0x02, 0x01, 0x05, 0x00, 0x00]; assert_eq!(to_der(&ber).unwrap(), [0x30, 0x03, 0x02, 0x01, 0x05]); } #[test] fn constructed_octet_strings_are_joined() { // [0] { 0x24 80 { 04 02 aa bb, 04 01 cc } } let ber = [ 0xa0, 0x80, 0x24, 0x80, 0x04, 0x02, 0xaa, 0xbb, 0x04, 0x01, 0xcc, 0x00, 0x00, 0x00, 0x00, ]; assert_eq!( to_der(&ber).unwrap(), [0xa0, 0x05, 0x04, 0x03, 0xaa, 0xbb, 0xcc] ); } #[test] fn long_lengths_use_the_shortest_form() { let content = vec![7u8; 300]; let mut ber = vec![0x04, 0x82, 0x01, 0x2c]; ber.extend(&content); let der = to_der(&ber).unwrap(); assert_eq!(&der[..4], &[0x04, 0x82, 0x01, 0x2c]); // Over-long length forms are normalised. let mut padded = vec![0x04, 0x84, 0x00, 0x00, 0x01, 0x2c]; padded.extend(&content); assert_eq!(to_der(&padded).unwrap(), der); } #[test] fn hostile_input_is_refused() { assert!(to_der(&[0x30, 0x80]).is_err()); // never ends assert!(to_der(&[0x04, 0x80, 0x00, 0x00]).is_err()); // primitive, indefinite assert!(to_der(&[0x04, 0x05, 0x01]).is_err()); // truncated let deep: Vec<u8> = std::iter::repeat_n([0x30u8, 0x80], 100) .flatten() .collect(); assert!(to_der(&deep).is_err()); } }