use crate::parser::{read_u16, read_u32};
use crate::Error;
pub const DSIG_VERSION: u32 = 0x0000_0001;
pub const DSIG_BLOCK_FORMAT_PKCS7: u32 = 1;
#[derive(Debug, Clone)]
pub struct Signature<'a> {
pub format: u32,
pub length: u32,
pkcs7: Option<&'a [u8]>,
raw_block: &'a [u8],
}
impl<'a> Signature<'a> {
pub fn is_pkcs7(&self) -> bool {
self.format == DSIG_BLOCK_FORMAT_PKCS7
}
pub fn pkcs7_packet(&self) -> Option<&'a [u8]> {
self.pkcs7
}
pub fn raw_block(&self) -> &'a [u8] {
self.raw_block
}
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct DsigTable<'a> {
version: u32,
flags: u16,
signatures: Vec<Signature<'a>>,
}
impl<'a> DsigTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let version = read_u32(bytes, 0)?;
if version != DSIG_VERSION {
return Err(Error::BadStructure("DSIG: unsupported version"));
}
let num_signatures = read_u16(bytes, 4)? as usize;
let flags = read_u16(bytes, 6)?;
let records_end = 8usize
.checked_add(
num_signatures
.checked_mul(12)
.ok_or(Error::BadStructure("DSIG: signature count overflow"))?,
)
.ok_or(Error::BadStructure("DSIG: record array overflow"))?;
if records_end > bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut signatures = Vec::with_capacity(num_signatures);
for i in 0..num_signatures {
let rec = 8 + i * 12;
let format = read_u32(bytes, rec)?;
let length = read_u32(bytes, rec + 4)?;
let offset = read_u32(bytes, rec + 8)? as usize;
let block_end = offset
.checked_add(length as usize)
.ok_or(Error::BadStructure("DSIG: block range overflow"))?;
if offset < 8 || block_end > bytes.len() {
return Err(Error::BadStructure("DSIG: signature block out of bounds"));
}
let raw_block = &bytes[offset..block_end];
let pkcs7 = if format == DSIG_BLOCK_FORMAT_PKCS7 {
if raw_block.len() < 8 {
return Err(Error::BadStructure("DSIG: Format-1 block too short"));
}
let sig_len = read_u32(raw_block, 4)? as usize;
let packet_end = 8usize
.checked_add(sig_len)
.ok_or(Error::BadStructure("DSIG: signatureLength overflow"))?;
if packet_end > raw_block.len() {
return Err(Error::BadStructure("DSIG: signatureLength past block"));
}
Some(&raw_block[8..packet_end])
} else {
None
};
signatures.push(Signature {
format,
length,
pkcs7,
raw_block,
});
}
Ok(Self {
version,
flags,
signatures,
})
}
pub fn version(&self) -> u32 {
self.version
}
pub fn flags(&self) -> u16 {
self.flags
}
pub fn cannot_be_resigned(&self) -> bool {
(self.flags & 0x0001) != 0
}
pub fn signatures(&self) -> &[Signature<'a>] {
&self.signatures
}
pub fn signature_count(&self) -> usize {
self.signatures.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_dsig(flags: u16, payloads: &[&[u8]]) -> Vec<u8> {
let n = payloads.len();
let mut out = Vec::new();
out.extend_from_slice(&DSIG_VERSION.to_be_bytes());
out.extend_from_slice(&(n as u16).to_be_bytes());
out.extend_from_slice(&flags.to_be_bytes());
let records_off = out.len();
out.extend(std::iter::repeat_n(0u8, n * 12));
for (i, payload) in payloads.iter().enumerate() {
let block_off = out.len();
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
out.extend_from_slice(payload);
let block_len = (out.len() - block_off) as u32;
let rec = records_off + i * 12;
out[rec..rec + 4].copy_from_slice(&DSIG_BLOCK_FORMAT_PKCS7.to_be_bytes());
out[rec + 4..rec + 8].copy_from_slice(&block_len.to_be_bytes());
out[rec + 8..rec + 12].copy_from_slice(&(block_off as u32).to_be_bytes());
}
out
}
#[test]
fn decodes_single_pkcs7_signature() {
let payload: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02];
let bytes = build_dsig(0, &[payload]);
let d = DsigTable::parse(&bytes).unwrap();
assert_eq!(d.version(), DSIG_VERSION);
assert_eq!(d.signature_count(), 1);
assert!(!d.cannot_be_resigned());
let sig = &d.signatures()[0];
assert!(sig.is_pkcs7());
assert_eq!(sig.pkcs7_packet(), Some(payload));
assert_eq!(sig.raw_block().len(), 8 + payload.len());
}
#[test]
fn decodes_two_signatures_and_flags() {
let p0: &[u8] = &[1, 2, 3];
let p1: &[u8] = &[9, 8, 7, 6, 5];
let bytes = build_dsig(0x0001, &[p0, p1]);
let d = DsigTable::parse(&bytes).unwrap();
assert_eq!(d.signature_count(), 2);
assert!(d.cannot_be_resigned());
assert_eq!(d.signatures()[0].pkcs7_packet(), Some(p0));
assert_eq!(d.signatures()[1].pkcs7_packet(), Some(p1));
assert_eq!(d.signatures()[1].length, (8 + p1.len()) as u32);
}
#[test]
fn empty_signature_table() {
let bytes = build_dsig(0, &[]);
let d = DsigTable::parse(&bytes).unwrap();
assert_eq!(d.signature_count(), 0);
assert!(d.signatures().is_empty());
}
#[test]
fn rejects_bad_version() {
let mut bytes = build_dsig(0, &[&[1, 2]]);
bytes[0..4].copy_from_slice(&2u32.to_be_bytes());
assert!(matches!(
DsigTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_short_header() {
assert!(matches!(
DsigTable::parse(&[0u8; 4]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_block_out_of_bounds() {
let mut bytes = build_dsig(0, &[&[1, 2, 3]]);
bytes[8 + 8..8 + 12].copy_from_slice(&9999u32.to_be_bytes());
assert!(matches!(
DsigTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_signature_length_past_block() {
let mut bytes = build_dsig(0, &[&[1, 2, 3, 4]]);
let block_off = read_u32(&bytes, 8 + 8).unwrap() as usize;
bytes[block_off + 4..block_off + 8].copy_from_slice(&9999u32.to_be_bytes());
assert!(matches!(
DsigTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn unknown_block_format_surfaces_raw() {
let mut bytes = build_dsig(0, &[&[1, 2, 3, 4]]);
bytes[8..12].copy_from_slice(&7u32.to_be_bytes());
let d = DsigTable::parse(&bytes).unwrap();
let sig = &d.signatures()[0];
assert!(!sig.is_pkcs7());
assert_eq!(sig.format, 7);
assert_eq!(sig.pkcs7_packet(), None);
assert!(!sig.raw_block().is_empty());
}
}