diff --git a/src/pe/certificate_table.rs b/src/pe/certificate_table.rs
new file mode 100644
index 000000000..d873738be
--- /dev/null
+++ b/src/pe/certificate_table.rs
@@ -0,0 +1,156 @@
+/// Implements parsing of pe32's Attribute Certificate Table
+/// See reference:
+/// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only
+/// https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-win_certificate
+use crate::error;
+use scroll::Pread;
+
+use alloc::string::ToString;
+use alloc::vec::Vec;
+
+#[repr(u16)]
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum AttributeCertificateRevision {
+    /// WIN_CERT_REVISION_1_0
+    Revision1_0 = 0x0100,
+    /// WIN_CERT_REVISION_2_0
+    Revision2_0 = 0x0200,
+}
+
+impl TryFrom<u16> for AttributeCertificateRevision {
+    type Error = error::Error;
+
+    fn try_from(value: u16) -> Result<Self, Self::Error> {
+        Ok(match value {
+            x if x == AttributeCertificateRevision::Revision1_0 as u16 => {
+                AttributeCertificateRevision::Revision1_0
+            }
+            x if x == AttributeCertificateRevision::Revision2_0 as u16 => {
+                AttributeCertificateRevision::Revision2_0
+            }
+            _ => {
+                return Err(error::Error::Malformed(
+                    "Invalid certificate attribute revision".to_string(),
+                ))
+            }
+        })
+    }
+}
+
+#[repr(u16)]
+#[derive(Debug)]
+pub enum AttributeCertificateType {
+    /// WIN_CERT_TYPE_X509
+    X509 = 0x0001,
+    /// WIN_CERT_TYPE_PKCS_SIGNED_DATA
+    PkcsSignedData = 0x0002,
+    /// WIN_CERT_TYPE_RESERVED_1
+    Reserved1 = 0x0003,
+    /// WIN_CERT_TYPE_TS_STACK_SIGNED
+    TsStackSigned = 0x0004,
+}
+
+impl TryFrom<u16> for AttributeCertificateType {
+    type Error = error::Error;
+
+    fn try_from(value: u16) -> Result<Self, Self::Error> {
+        Ok(match value {
+            x if x == AttributeCertificateType::X509 as u16 => AttributeCertificateType::X509,
+            x if x == AttributeCertificateType::PkcsSignedData as u16 => {
+                AttributeCertificateType::PkcsSignedData
+            }
+            x if x == AttributeCertificateType::Reserved1 as u16 => {
+                AttributeCertificateType::Reserved1
+            }
+            x if x == AttributeCertificateType::TsStackSigned as u16 => {
+                AttributeCertificateType::TsStackSigned
+            }
+            _ => {
+                return Err(error::Error::Malformed(
+                    "Invalid attribute certificate type".to_string(),
+                ))
+            }
+        })
+    }
+}
+
+#[derive(Clone, Pread)]
+struct AttributeCertificateHeader {
+    /// dwLength
+    length: u32,
+    revision: u16,
+    certificate_type: u16,
+}
+
+const CERTIFICATE_DATA_OFFSET: u32 = 8;
+#[derive(Debug)]
+pub struct AttributeCertificate<'a> {
+    pub length: u32,
+    pub revision: AttributeCertificateRevision,
+    pub certificate_type: AttributeCertificateType,
+    pub certificate: &'a [u8],
+}
+
+impl<'a> AttributeCertificate<'a> {
+    pub fn parse(
+        bytes: &'a [u8],
+        current_offset: &mut usize,
+    ) -> Result<AttributeCertificate<'a>, error::Error> {
+        // `current_offset` is moved sizeof(AttributeCertificateHeader) = 8 bytes further.
+        let header: AttributeCertificateHeader = bytes.gread_with(current_offset, scroll::LE)?;
+        let cert_size = usize::try_from(header.length.saturating_sub(CERTIFICATE_DATA_OFFSET))
+            .map_err(|_err| {
+                error::Error::Malformed(
+                    "Attribute certificate size do not fit in usize".to_string(),
+                )
+            })?;
+        let attr = Self {
+            length: header.length,
+            revision: header.revision.try_into()?,
+            certificate_type: header.certificate_type.try_into()?,
+            certificate: &bytes[*current_offset..(*current_offset + cert_size)],
+        };
+        // Moving past the certificate data.
+        // Prevent the current_offset to wrap and ensure current_offset is strictly increasing.
+        *current_offset = current_offset.saturating_add(cert_size);
+        // Round to the next 8-bytes.
+        *current_offset = (*current_offset + 7) & !7;
+        Ok(attr)
+    }
+}
+
+pub type CertificateDirectoryTable<'a> = Vec<AttributeCertificate<'a>>;
+pub(crate) fn enumerate_certificates(
+    bytes: &[u8],
+    table_virtual_address: u32,
+    table_size: u32,
+) -> Result<CertificateDirectoryTable, error::Error> {
+    let table_start_offset = usize::try_from(table_virtual_address).map_err(|_err| {
+        error::Error::Malformed("Certificate table RVA do not fit in a usize".to_string())
+    })?;
+    // Here, we do not want wrapping semantics as it means that a too big table size or table start
+    // offset will provide table_end_offset such that table_end_offset < table_start_offset, which
+    // is not desirable at all.
+    let table_end_offset =
+        table_start_offset.saturating_add(usize::try_from(table_size).map_err(|_err| {
+            error::Error::Malformed("Certificate table size do not fit in a usize".to_string())
+        })?);
+    let mut current_offset = table_start_offset;
+    let mut attrs = vec![];
+
+    // End offset cannot be further than the binary we have at hand.
+    if table_end_offset >= bytes.len() {
+        return Err(error::Error::Malformed(
+            "End of attribute certificates table is after the end of the PE binary".to_string(),
+        ));
+    }
+
+    // This is guaranteed to terminate, either by a malformed error being returned
+    // or because current_offset >= table_end_offset by virtue of current_offset being strictly
+    // increasing through `AttributeCertificate::parse`.
+    while current_offset < table_end_offset {
+        attrs.push(AttributeCertificate::parse(bytes, &mut current_offset)?);
+    }
+
+    Ok(attrs)
+}
diff --git a/src/pe/mod.rs b/src/pe/mod.rs
index 72a5c8e53..f0df3c7ef 100644
--- a/src/pe/mod.rs
+++ b/src/pe/mod.rs
@@ -5,6 +5,7 @@
 
 use alloc::vec::Vec;
 
+pub mod certificate_table;
 pub mod characteristic;
 pub mod data_directories;
 pub mod debug;
@@ -58,6 +59,8 @@ pub struct PE<'a> {
     pub debug_data: Option<debug::DebugData<'a>>,
     /// Exception handling and stack unwind information, if any, contained in the PE header
     pub exception_data: Option<exception::ExceptionData<'a>>,
+    /// Certificates present, if any, described by the Certificate Table
+    pub certificates: certificate_table::CertificateDirectoryTable<'a>,
 }
 
 impl<'a> PE<'a> {
@@ -86,6 +89,7 @@ impl<'a> PE<'a> {
         let mut libraries = vec![];
         let mut debug_data = None;
         let mut exception_data = None;
+        let mut certificates = Default::default();
         let mut is_64 = false;
         if let Some(optional_header) = header.optional_header {
             entry = optional_header.standard_fields.address_of_entry_point as usize;
@@ -177,6 +181,16 @@ impl<'a> PE<'a> {
                     )?);
                 }
             }
+
+            if let Some(certificate_table) =
+                *optional_header.data_directories.get_certificate_table()
+            {
+                certificates = certificate_table::enumerate_certificates(
+                    bytes,
+                    certificate_table.virtual_address,
+                    certificate_table.size,
+                )?;
+            }
         }
         Ok(PE {
             header,
@@ -194,6 +208,7 @@ impl<'a> PE<'a> {
             libraries,
             debug_data,
             exception_data,
+            certificates,
         })
     }
 }