authenticode/
win_cert.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::usize_from_u32;
10use crate::PeTrait;
11use crate::{AuthenticodeSignature, AuthenticodeSignatureParseError};
12use core::fmt::{self, Display, Formatter};
13
14/// Current version of `Win_Certificate` structure.
15pub const WIN_CERT_REVISION_2_0: u16 = 0x0200;
16
17/// Certificate contains a PKCS#7 `SignedData` structure.
18pub const WIN_CERT_TYPE_PKCS_SIGNED_DATA: u16 = 0x0002;
19
20fn align_up_to_8(val: usize) -> Option<usize> {
21    const ALIGN: usize = 8;
22    let r = val % ALIGN;
23    if r == 0 {
24        Some(val)
25    } else {
26        // OK to unwrap: the remainder cannot be larger than `ALIGN`.
27        let sub = ALIGN.checked_sub(r).unwrap();
28        val.checked_add(sub)
29    }
30}
31
32fn check_total_size_valid(remaining_data: &[u8]) -> bool {
33    let mut iter = AttributeCertificateIterator { remaining_data };
34    while iter.next().is_some() {}
35    iter.remaining_data.is_empty()
36}
37
38/// Error type for [`AttributeCertificateIterator`].
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum AttributeCertificateError {
41    /// The certificate table's range is out of bounds.
42    OutOfBounds,
43
44    /// The certificate table's size does not match the sum of the
45    /// certificate entry's aligned sizes.
46    InvalidSize,
47
48    /// The size of an entry in the certificate table is invalid.
49    InvalidCertificateSize {
50        /// Size (in bytes) stored in the certificate entry header.
51        size: u32,
52    },
53}
54
55impl Display for AttributeCertificateError {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        match self {
58            Self::OutOfBounds => {
59                write!(f, "certificate table range is out of bounds")
60            }
61            Self::InvalidSize => {
62                write!(f, "certificate table size does not match the sum of the certificate entry's aligned sizes")
63            }
64            Self::InvalidCertificateSize { size } => {
65                write!(f, "certificate table contains an entry with an invalid size: {size}")
66            }
67        }
68    }
69}
70
71#[cfg(feature = "std")]
72impl std::error::Error for AttributeCertificateError {}
73
74/// Error returned by [`AttributeCertificate::get_authenticode_signature`].
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum AttributeCertificateAuthenticodeError {
77    /// Attribute certificate revision does not match [`WIN_CERT_REVISION_2_0`].
78    InvalidCertificateRevision(u16),
79
80    /// Attribute certificate type does not match [`WIN_CERT_TYPE_PKCS_SIGNED_DATA`].
81    InvalidCertificateType(u16),
82
83    /// Attribute certificate data is not a valid [`AuthenticodeSignature`].
84    InvalidSignature(AuthenticodeSignatureParseError),
85}
86
87impl Display for AttributeCertificateAuthenticodeError {
88    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::InvalidCertificateRevision(rev) => {
91                write!(f, "invalid attribute certificate revision: {rev:02x}")
92            }
93            Self::InvalidCertificateType(ctype) => {
94                write!(f, "invalid attribute certificate type: {ctype:02x}")
95            }
96            Self::InvalidSignature(err) => {
97                write!(f, "invalid signature: {err}")
98            }
99        }
100    }
101}
102
103#[cfg(feature = "std")]
104impl std::error::Error for AttributeCertificateAuthenticodeError {}
105
106/// Raw data for a PE attribute certificate.
107///
108/// Note that PE attribute certificates are not related to X.509
109/// attribute certificates.
110#[derive(Debug)]
111pub struct AttributeCertificate<'a> {
112    /// `WIN_CERTIFICATE` version number.
113    pub revision: u16,
114
115    /// Certificate type.
116    pub certificate_type: u16,
117
118    /// Raw certificate data (not including the header).
119    pub data: &'a [u8],
120}
121
122impl<'a> AttributeCertificate<'a> {
123    /// Get the certificate data as an authenticode signature.
124    pub fn get_authenticode_signature(
125        &self,
126    ) -> Result<AuthenticodeSignature, AttributeCertificateAuthenticodeError>
127    {
128        if self.revision != WIN_CERT_REVISION_2_0 {
129            return Err(AttributeCertificateAuthenticodeError::InvalidCertificateRevision(self.revision));
130        }
131        if self.certificate_type != WIN_CERT_TYPE_PKCS_SIGNED_DATA {
132            return Err(
133                AttributeCertificateAuthenticodeError::InvalidCertificateType(
134                    self.certificate_type,
135                ),
136            );
137        }
138
139        AuthenticodeSignature::from_bytes(self.data)
140            .map_err(AttributeCertificateAuthenticodeError::InvalidSignature)
141    }
142}
143
144/// Iterator over PE attribute certificates.
145#[derive(Debug)]
146pub struct AttributeCertificateIterator<'a> {
147    remaining_data: &'a [u8],
148}
149
150impl<'a> AttributeCertificateIterator<'a> {
151    /// Create a new `AttributeCertificateIterator`.
152    ///
153    /// If there is no attribute certificate table, this returns `Ok(None)`.
154    ///
155    /// # Errors
156    ///
157    /// Returns [`AttributeCertificateError::OutOfBounds`] if the table
158    /// is not within the PE image bounds.
159    ///
160    /// Returns [`AttributeCertificateError::InvalidSize`] if the table
161    /// size does not match the sum of the certificate entry's aligned
162    /// sizes.
163    pub fn new(
164        pe: &'a dyn PeTrait,
165    ) -> Result<Option<Self>, AttributeCertificateError> {
166        match pe.certificate_table_range() {
167            Ok(Some(certificate_table_range)) => {
168                let remaining_data = pe
169                    .data()
170                    .get(certificate_table_range)
171                    .ok_or(AttributeCertificateError::OutOfBounds)?;
172
173                // TODO(nicholasbishop): add unit test for this.
174                if !check_total_size_valid(remaining_data) {
175                    return Err(AttributeCertificateError::InvalidSize);
176                }
177                Ok(Some(Self { remaining_data }))
178            }
179            Ok(None) => Ok(None),
180            Err(_) => Err(AttributeCertificateError::OutOfBounds),
181        }
182    }
183}
184
185impl<'a> Iterator for AttributeCertificateIterator<'a> {
186    type Item = Result<AttributeCertificate<'a>, AttributeCertificateError>;
187
188    fn next(&mut self) -> Option<Self::Item> {
189        let header_size = 8;
190        if self.remaining_data.len() < header_size {
191            return None;
192        }
193
194        // OK to unwrap: we've already verified above that at least 8
195        // bytes are available.
196        let cert_bytes = self.remaining_data;
197        let cert_size =
198            u32::from_le_bytes(cert_bytes[0..4].try_into().unwrap());
199        let cert_size_err = AttributeCertificateError::InvalidCertificateSize {
200            size: cert_size,
201        };
202        let cert_size = usize_from_u32(cert_size);
203        let revision = u16::from_le_bytes(cert_bytes[4..6].try_into().unwrap());
204        let certificate_type =
205            u16::from_le_bytes(cert_bytes[6..8].try_into().unwrap());
206
207        // Get the cert data (excludes the header). Return an error if
208        // the cert data size is less than the header size.
209        let cert_data_size =
210            if let Some(cert_data_size) = cert_size.checked_sub(header_size) {
211                cert_data_size
212            } else {
213                // End iteration after returning the error.
214                self.remaining_data = &[];
215                return Some(Err(cert_size_err));
216            };
217
218        // Get the offset where the cert data ends. Return an error on
219        // overflow.
220        let cert_data_end = if let Some(cert_data_end) =
221            header_size.checked_add(cert_data_size)
222        {
223            cert_data_end
224        } else {
225            // End iteration after returning the error.
226            self.remaining_data = &[];
227            return Some(Err(cert_size_err));
228        };
229
230        // Get the cert data slice. Return an error if the size is
231        // outside the valid range.
232        let cert_data = if let Some(cert_data) =
233            cert_bytes.get(header_size..cert_data_end)
234        {
235            cert_data
236        } else {
237            // End iteration after returning the error.
238            self.remaining_data = &[];
239            return Some(Err(cert_size_err));
240        };
241
242        // Advance to next certificate. Data is 8-byte aligned, so round up.
243        let size_rounded_up = align_up_to_8(cert_size)?;
244        self.remaining_data = cert_bytes.get(size_rounded_up..)?;
245
246        Some(Ok(AttributeCertificate {
247            revision,
248            certificate_type,
249            data: cert_data,
250        }))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_align_up() {
260        assert_eq!(align_up_to_8(0).unwrap(), 0);
261        for i in 1..=8 {
262            assert_eq!(align_up_to_8(i).unwrap(), 8);
263        }
264        for i in 9..=16 {
265            assert_eq!(align_up_to_8(i).unwrap(), 16);
266        }
267    }
268}