Nelson Baloian

166 papers A 1B 10C 26Misc 1Journal 44Unranked 75
YearRankTypeTitle / Venue / Authors
2025 J jnl
J. Univers. Comput. Sci.
Aram Adamyan, Hovhannes Hovhannisyan, Daniel Radrigan, Nelson Baloian, Ashot N. Harutyunyan
2025 J jnl
J. Univers. Comput. Sci.
Sergio Peñafiel, Esteban Ramírez, Nelson Baloian, Isabel Saffie, Paulo Luz, Inti Paredes
2025 C conf
CSCWD
Felipe Escarate, Nelson Baloian, José A. Pino
2024 conf
CollabTech
Gustavo Zurita, Joaquín Uribe, Nelson Baloian, Valentina Aravena
2024 conf
UCAmI
Valentina Aravena, Nelson Baloian, Gustavo Zurita
2024 J jnl
Future Internet
Ashot N. Harutyunyan, Arnak V. Poghosyan, Tigran Bunarjyan, Andranik Haroyan, Marine Harutyunyan, Lilit Harutyunyan, Nelson Baloian
2024 B conf
NOMS
Ashot N. Harutyunyan, Arnak V. Poghosyan, Tigran Bunarjyan, Naira Grigoryan, Artur Grigoryan, Vahan Tadevosyan, Nelson Baloian
2023 conf
UCAmI (2)
Jonathan Frez, Nelson Baloian
2023 J jnl
Sensors
Wolfram Luther, Nelson Baloian, Daniel Biella, Daniel Sacher
2023 conf
ICCV (Workshops)
Cristián Llull, Nelson Baloian, Benjamin Bustos, Kornelius Kupczik, Ivan Sipiran, Andres Baloian
2023 conf
AICCC
Arnak V. Poghosyan, Ashot N. Harutyunyan, Tigran Bunarjyan, Nelson Baloian
2023 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, Jonathan Frez, José A. Pino, Cristóbal Fuenzalida, Sergio Peñafiel, Belisario Panay, Gustavo Zurita, Horacio Sanson
2023 conf
UCAmI (1)
Eric Kirchgessner, Matías Sothers, Valentina Aravena, Nelson Baloian, Gustavo Zurita
2022 J jnl
Pers. Ubiquitous Comput.
Claudio Alvarez, Gustavo Zurita, Nelson Baloian
2022 ed.
CollabTech
Lung-Hsiang Wong, Yugo Hayashi, César A. Collazos, Claudio Alvarez, Gustavo Zurita, Nelson Baloian
2022 J jnl
Pers. Ubiquitous Comput.
Claudio Alvarez, Gustavo Zurita, Nelson Baloian
2022 conf
UCAmI
Maximiliano Edwards, Nelson Baloian, José A. Pino
2022 conf
CollabTech
Nelson Baloian, Gustavo Zurita, José A. Pino, Rodrigo Llull
2021 conf
CollabTech
Claudio Alvarez, Gustavo Zurita, Andrés Carvallo, Pablo Ramírez, Eugenio Bravo, Nelson Baloian
2021 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, Daniel Biella, Wolfram Luther, José A. Pino, Daniel Sacher
2021 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, José A. Pino
2021 J jnl
Sensors
Belisario Panay, Nelson Baloian, José A. Pino, Sergio Peñafiel, Jonathan Frez, Cristóbal Fuenzalida, Horacio Sanson, Gustavo Zurita
2021 J jnl
IEEE Access
Sergio Peñafiel, Nelson Baloian, Horacio Sanson, José A. Pino
2021 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, José A. Pino, Gustavo Zurita, Valeria Lobos-Ossandón, Hermann Maurer
2020 J jnl
Expert Syst. Appl.
Sergio Peñafiel, Nelson Baloian, Horacio Sanson, José A. Pino
2020 J jnl
Sensors
Belisario Panay, Nelson Baloian, José A. Pino, Sergio Peñafiel, Horacio Sanson, Nicolas Bersano
2019 conf
CRIWG/CollabTech
Claudio Alvarez, Gustavo Zurita, Nelson Baloian, Oscar Jerez, Sergio Peñafiel
2019 conf
UCAmI
Gustavo Zurita, Nelson Baloian, Sergio Peñafiel, Oscar Jerez
2019 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, Yanling Chen, Ashot N. Harutyunyan
2019 conf
CRIWG/CollabTech
Nelson Baloian, Gustavo Zurita, José A. Pino, Sergio Peñafiel, Wolfram Luther
2019 J jnl
J. Univers. Comput. Sci.
Jonathan Frez, Nelson Baloian, José A. Pino, Gustavo Zurita, Franco Basso
2019 conf
UCAmI
Belisario Panay, Nelson Baloian, José A. Pino, Sergio Peñafiel, Horacio Sanson, Nicolas Bersano
2019 conf
UCAmI
Jonathan Urzúa, Nelson Baloian, José A. Pino
2019 J jnl
Sensors
Nelson Baloian, Jonathan Frez, José A. Pino, Sergio Peñafiel, Gustavo Zurita, Alvaro Abarca
2018 conf
CIC
Sebastian Hernandez, Nelson Baloian, José A. Pino, Xiaojun Yuan
2018 conf
ICACT
Sergio Peñafiel, Nelson Baloian, José A. Pino, Jorge Quinteros, Alvaro Riquelme, Horacio Sanson, Douglas Teoh
2018 ed.
CollabTech
Hironori Egi, Takaya Yuizono, Nelson Baloian, Takashi Yoshino, Satoshi Ichimura, Armanda Rodrigues
2018 conf
ICACT
Jorge Quinteros, Nelson Baloian, José A. Pino, Alvaro Riquelme, Sergio Peñafiel, Horacio Sanson, Douglas Teoh
2018 B conf
ICALT
Gustavo Zurita, Nelson Baloian, Oscar Jerez
2018 conf
CRIWG
Gustavo Zurita, Nelson Baloian, Oscar Jerez, Sergio Peñafiel, José A. Pino
2018 conf
UCAmI
Nelson Baloian, Jonathan Frez, José A. Pino, Gustavo Zurita
2018 conf
S-BPM ONE
Pedro Antunes, Nelson Baloian, Gustavo Zurita, José A. Pino
2017 B conf
ICALT
Oscar Jerez, Nelson Baloian, Gustavo Zurita
2017 C conf
CSCWD
Nelson Baloian, Enrique Bassaletti, Mario Fernández, Óscar Figueroa, Pablo Fuentes, Raúl Manásevich, Marcos E. Orchard, Sergio Peñafiel, José A. Pino, Mario Vergara
2017 C conf
CSCWD
Orlando Erazo, Nelson Baloian, José A. Pino, Sergio F. Ochoa
2017 conf
CRIWG
Nelson Baloian, Wolfram Luther, Daniel Biella, Nare Karapetyan, José A. Pino, Tobias Schreck, Andres Ferrada, Nancy Hitschfeld
2017 conf
CRIWG
Gustavo Zurita, Nelson Baloian, Oscar Jerez, Sergio Peñafiel
2017 conf
CRIWG
Claudio Álvarez, Nelson Baloian, Gustavo Zurita, Fabio Guarini
2016 J jnl
Inf. Syst. Frontiers
Nelson Baloian, Gustavo Zurita
2016 ed.
CollabTech
Takashi Yoshino, Gwo-Dong Chen, Gustavo Zurita, Takaya Yuizono, Tomoo Inoue, Nelson Baloian
2016 J jnl
J. Univers. Comput. Sci.
Daniel Biella, Thomas Pilz, Daniel Sacher, Benjamin Weyers, Wolfram Luther, Nelson Baloian, Tobias Schreck
2016 conf
UCAmI (1)
Orlando Erazo, Nelson Baloian, José A. Pino, Gustavo Zurita
2016 J jnl
J. Univers. Comput. Sci.
Gustavo Zurita, Nelson Baloian, José A. Pino, Mary Boghosian
2016 ch.
Seamless Learning in the Age of Mobile Connectivity
Gustavo Zurita, Nelson Baloian
2016 conf
CRIWG
Gustavo Zurita, Nelson Baloian, José A. Pino, Sergio Peñafiel
2016 C conf
ICCE
Janosch Zbick, Nelson Baloian, Marcelo Milrad, Marc Jansen, Gustavo Zurita
2016 J jnl
Inf. Syst. Frontiers
Francisco J. Gutierrez, Sergio F. Ochoa, Gustavo Zurita, Nelson Baloian
2015 J jnl
Comput. Informatics
Francisco J. Gutierrez, Sergio F. Ochoa, Nelson Baloian, Gustavo Zurita, Luis Loyola
2015 ed.
CRIWG
Nelson Baloian, Yervant Zorian, Perouz Taslakian, Samvel K. Shoukourian
2015 conf
CRIWG
Daniel Biella, Daniel Sacher, Benjamin Weyers, Wolfram Luther, Nelson Baloian, Tobias Schreck
2015 conf
UCAmI
Nelson Baloian, Jonathan Frez, José A. Pino, Gustavo Zurita
2015 conf
UCAmI
Daniel Biella, Daniel Sacher, Benjamin Weyers, Wolfram Luther, Nelson Baloian
2015 conf
CRIWG
Gustavo Zurita, Catalina Cárdenas, Nelson Baloian
2015 J jnl
Comput. Syst. Sci. Eng.
Diego Muñoz, Francisco J. Gutierrez, Sergio F. Ochoa, Nelson Baloian
2015 conf
CRIWG
Nelson Baloian, Jonathan Frez, José A. Pino, Gustavo Zurita
2015 conf
UCAmI
Gustavo Zurita, José A. Pino, Nelson Baloian
2015 C conf
ICCE
Gustavo Zurita, Catalina Cárdenas, Nelson Baloian
2014 conf
ICSLE
Gustavo Zurita, Beatriz Hasbun, Nelson Baloian, Oscar Jerez
2014 conf
IWAAL
Javier Orozco, Rodrigo M. Santos, Sergio F. Ochoa, Leo Ordínez, Roc Messeguer, Nelson Baloian
2014 J jnl
Behav. Inf. Technol.
Pedro Antunes, Gustavo Zurita, Nelson Baloian
2014 ed.
CollabTech
Takaya Yuizono, Gustavo Zurita, Nelson Baloian, Tomoo Inoue, Hiroaki Ogata
2014 ed.
CRIWG
Nelson Baloian, Frada Burstein, Hiroaki Ogata, Flávia Maria Santoro, Gustavo Zurita
2014 conf
CRIWG
Jonathan Frez, Nelson Baloian, José A. Pino, Gustavo Zurita
2014 C conf
CSCWD
Jonathan Frez, Nelson Baloian, Gustavo Zurita, José A. Pino
2014 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, Diego Aguirre, Gustavo Zurita
2014 J jnl
Int. J. Inf. Technol. Decis. Mak.
Pedro Antunes, Gustavo Zurita, Nelson Baloian
2014 conf
UCAmI
Pablo Romo, Sergio F. Ochoa, Nelson Baloian, Ignacio Casas, José Bravo
2014 conf
CRIWG
Nelson Baloian, José A. Pino, Jens Hardings, Heinz Ulrich Hoppe
2014 conf
CRIWG
Pedro Antunes, Gustavo Zurita, Nelson Baloian
2014 conf
UCAmI
Jonathan Frez, Nelson Baloian, Gustavo Zurita
2014 J jnl
Future Gener. Comput. Syst.
Gustavo Zurita, Nelson Baloian, Jonathan Frez
2013 C conf
CSCWD
Nelson Baloian, Francisco J. Gutierrez, Gustavo Zurita
2013 conf
CRIWG
Francisco J. Gutierrez, Gustavo Zurita, Sergio F. Ochoa, Nelson Baloian
2013 conf
IWAAL
Diego Muñoz, Francisco J. Gutierrez, Sergio F. Ochoa, Nelson Baloian
2013 B conf
CSEDU
Nelson Baloian, Gustavo Zurita, Ulrich Hoppe
2013 conf
ICEBE
Nelson Baloian, José A. Pino, Carlos Reveco, Gustavo Zurita
2013 conf
UCAmI
Francisco J. Gutierrez, Gustavo Zurita, Nelson Baloian
2013 conf
ChileCHI
Nelson Baloian, José A. Pino, Roberto Vargas
2013 J jnl
J. Univers. Comput. Sci.
Marc Jansen, Lars Bollen, Nelson Baloian, Heinz Ulrich Hoppe
2013 conf
CRIWG
Gustavo Zurita, Nelson Baloian
2012 C conf
CSCWD
Francisco J. Gutierrez, Nelson Baloian, Sergio F. Ochoa, Gustavo Zurita
2012 conf
WCLOUD
Marc Jansen, Lars Bollen, Nelson Baloian, Ulrich Hoppe
2012 B conf
SMC
Gustavo Zurita, Nelson Baloian
2012 conf
UCAmI
Gustavo Zurita, Nelson Baloian
2012 J jnl
Int. J. Inf. Technol. Decis. Mak.
Benjamin Weyers, Wolfram Luther, Nelson Baloian
2012 conf
CRIWG
Francisco J. Gutierrez, Nelson Baloian, Sergio F. Ochoa, Gustavo Zurita
2012 conf
CRIWG
Benjamin Weyers, Wolfram Luther, Nelson Baloian, José A. Pino
2012 J jnl
Sensors
Gustavo Zurita, Nelson Baloian
2012 C conf
CSCWD
Jonathan Frez, Nelson Baloian, Gustavo Zurita
2012 B conf
SMC
Sergio F. Ochoa, José A. Pino, Nelson Baloian, Pedro Antunes, Valeria Herskovic
2012 J jnl
Int. J. Inf. Technol. Decis. Mak.
Gert-Jan de Vreede, Nelson Baloian
2012 conf
UCAmI
Jonathan Frez, Nelson Baloian, Gustavo Zurita
2012 B conf
ICALT
Nelson Baloian, Gustavo Zurita, Jonathan Frez, Marcelo Milrad
2012 J jnl
Sensors
Nelson Baloian, Gustavo Zurita
2011 J jnl
J. Univers. Comput. Sci.
Roberto Konow, Wayman Tan, Luis Loyola, Javier Pereira, Nelson Baloian
2011 C conf
CSCWD
Nelson Baloian, Gustavo Zurita, Flávia Maria Santoro, Renata Mendes de Araujo, S. Wolfgan, D. Machado, José A. Pino
2011 C conf
CSCWD
Pedro Antunes, Antonio Ferreira, Gustavo Zurita, Nelson Baloian
2011 conf
CRIWG
Francisco J. Gutierrez, Nelson Baloian, Gustavo Zurita
2011 C conf
ICCE
Nelson Baloian, Gustavo Zurita, Marcelo Milrad
2011 J jnl
J. Univers. Comput. Sci.
Cláudio Sapateiro, Nelson Baloian, Pedro Antunes, Gustavo Zurita
2011 J jnl
Future Gener. Comput. Syst.
Benjamin Weyers, Wolfram Luther, Nelson Baloian
2011 J jnl
Interact. Learn. Environ.
Nelson Baloian, José A. Pino, Jens Hardings
2011 C conf
CSCWD
Nelson Baloian, Ramon Cruzat, Richard Ibarra, Javier Bustos-Jiménez
2010 C conf
CSCWD
Gustavo Zurita, Nelson Baloian
2010 ch.
Supporting Real Time Decision-Making
Pedro Antunes, Cláudio Sapateiro, Gustavo Zurita, Nelson Baloian
2010 conf
CRIWG
Luís Carriço, Gustavo Zurita, Marco de Sá, Nelson Baloian, João Faria, Isabel Sá
2010 conf
Mobile HCI
Marco de Sá, Luís Carriço, João Faria, Isabel Sá, Nelson Baloian, Gustavo Zurita
2010 conf
CRIWG
Pedro Antunes, Cláudio Sapateiro, Gustavo Zurita, Nelson Baloian
2010 C conf
CSCWD
Nelson Baloian, José A. Pino, Gabriel Pena, Gustavo Zurita
2010 C conf
CSCWD
Roberto Konow, Wayman Tan, Luis Loyola, Javier Pereira, Nelson Baloian
2010 conf
CRIWG
Gustavo Zurita, Nelson Baloian
2010 conf
SCCC
Daniel Biella, Wolfram Luther, Nelson Baloian
2009 conf
CRIWG
Pedro Antunes, Gustavo Zurita, Nelson Baloian
2009 conf
CRIWG
Benjamin Weyers, Wolfram Luther, Nelson Baloian
2009 C conf
CSCWD
Benjamin Weyers, Nelson Baloian, Wolfram Luther
2009 C conf
CSCWD
Cláudio Sapateiro, Nelson Baloian, Pedro Antunes, Gustavo Zurita
2009 C conf
CSCWD
Nelson Baloian, Francisco Claude, Roberto Konow, Sebastian Kreft
2009 ed.
CRIWG
Luís Carriço, Nelson Baloian, Benjamim Fonseca
2009 J jnl
J. Univers. Comput. Sci.
Nelson Baloian, Gustavo Zurita
2009 C conf
CSCWD
Dusan Juretic, Gustavo Zurita, Nelson Baloian
2009 J jnl
Knowl. Inf. Syst.
Olivier Motelet, Nelson Baloian, José A. Pino
2008 J jnl
Adv. Eng. Informatics
Gustavo Zurita, Nelson Baloian, Felipe Baytelman
2008 J jnl
J. Vis. Lang. Comput.
Nelson Baloian, Henning Breuer, Wolfram Luther
2008 J jnl
J. Educ. Technol. Soc.
Nelson Baloian, José A. Pino, Heinz Ulrich Hoppe
2008 conf
CRIWG
Cláudio Sapateiro, Pedro Antunes, Gustavo Zurita, Rodrigo Vogt, Nelson Baloian
2008 C conf
CSCWD
Gustavo Zurita, Nelson Baloian, Felipe Baytelman
2008 B conf
ICALT
Henning Breuer, Gustavo Zurita, Nelson Baloian, Mitsuji Matsumoto
2008 conf
CRIWG
Pedro Antunes, Rogério Bandeira, Luís Carriço, Gustavo Zurita, Nelson Baloian, Rodrigo Vogt
2008 conf
ICEBE
Cláudio Sapateiro, Pedro Antunes, Gustavo Zurita, Nelson Baloian, Rodrigo Vogt
2008 conf
WMUTE
Gustavo Zurita, Nelson Baloian, Felipe Baytelman
2008 conf
Mobile Response
Cláudio Sapateiro, Pedro Antunes, Gustavo Zurita, Nelson Baloian, Rodrigo Vogt
2008 J jnl
J. Univers. Comput. Sci.
Gustavo Zurita, Pedro Antunes, Nelson Baloian, Luís Carriço, Felipe Baytelman, Marco de Sá
2008 C conf
CSCWD
Gustavo Zurita, Nelson Baloian, Felipe Baytelman
2008 conf
CDM
Gustavo Zurita, Pedro Antunes, Nelson Baloian, Felipe Baytelman, Antonio Farias
2007 C conf
CSCWD
Nelson Baloian, Gustavo Zurita, Pedro Antunes, Felipe Baytelman
2007 C conf
CSCWD
Gustavo Zurita, Nelson Baloian, Felipe Baytelman, Antonio Farias, Pedro Antunes
2007 B conf
ICALT
Henning Breuer, Christian Sousa, Nelson Baloian, Mitsuji Matsumoto
2007 conf
CSCWD (Selected Papers)
Gustavo Zurita, Nelson Baloian, Felipe Baytelman, Antonio Farias, Pedro Antunes
2007 conf
Edutainment
Gustavo Zurita, Nelson Baloian, Felipe Baytelman, Antonio Farias
2007 conf
EC-TEL (Posters)
Gustavo Zurita, Nelson Baloian, Pedro Antunes, Felipe Baytelman
2007 Misc conf
International Conference on Computational Science (2)
Gustavo Zurita, Nelson Baloian, Felipe Baytelman, Antonio Farias
2007 C conf
SPIRE
Olivier Motelet, Benjamin Piwowarski, Georges Dupret, José A. Pino, Nelson Baloian
2007 conf
HCI (4)
Henning Breuer, Nelson Baloian, Christian Sousa, Mitsuji Matsumoto
2007 conf
HCI (5)
Jaime Sánchez, Mauricio Sáenz, Nelson Baloian
2007 B conf
ICALT
Henning Breuer, Roberto Konow, Nelson Baloian, Gustavo Zurita
2007 conf
EC-TEL (Posters)
Pedro Antunes, Nelson Baloian, Gustavo Zurita, Felipe Baytelman
2007 J jnl
J. Univers. Comput. Sci.
Gustavo Zurita, Pedro Antunes, Nelson Baloian, Felipe Baytelman
2007 conf
CSCWD (Selected Papers)
Nelson Baloian, Gustavo Zurita
2007 A conf
AIED
Olivier Motelet, Nelson Baloian, Benjamin Piwowarski, José A. Pino
2006 C conf
CSCWD
Gustavo Zurita, Nelson Baloian, Felipe Baytelman
2006 conf
OTM Conferences (1)
Gustavo Zurita, Nelson Baloian, Felipe Baytelman, Mario Morales
2006 conf
CRIWG
Gustavo Zurita, Pedro Antunes, Luís Carriço, Felipe Baytelman, Marco de Sá, Nelson Baloian
2006 conf
CSCWD (Selected Papers)
Nelson Baloian, José A. Pino, Marc Jansen
2005 conf
SCCC
Nelson Baloian, Wolfram Luther, Thorsten Putzer
2004 conf
ICLS
Nelson Baloian, Henning Breuer, Ulrich Hoppe, José A. Pino
2003 J jnl
Comput. Appl. Eng. Educ.
Sergio F. Ochoa, José A. Pino, Nelson Baloian, David A. Fuller
redb/extractors/macho_extractors/macho_signature.py
← Index redb/extractors/macho_extractors/macho_signature.py python
"""
MachO Code Signature Extractor using LIEF + asn1crypto

Parses LC_CODE_SIGNATURE to extract:
- Code Directory (identifier, team_id, flags, hashes)
- Entitlements (XML plist)
- X.509 Certificates from CMS/PKCS#7 signature
- Signature verification status
"""

import hashlib
import inspect
import json
import plistlib
import struct
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple

import lief

lief.disable_leak_warning()
from asn1crypto import cms, x509

from redb.extractors.enum import Tag
from redb.extractors.macho_extractor import MachOExtractor
from redb.models.dataclasses import (
    MachOCertificate,
    MachOCodeDirectory,
    MachOCodeSigningInfo,
    MachOEntitlement,
)


# Code Signature Magic Values
CSMAGIC_REQUIREMENT = 0xFADE0C00
CSMAGIC_REQUIREMENTS = 0xFADE0C01
CSMAGIC_CODEDIRECTORY = 0xFADE0C02
CSMAGIC_EMBEDDED_SIGNATURE = 0xFADE0CC0  # SuperBlob
CSMAGIC_DETACHED_SIGNATURE = 0xFADE0CC1
CSMAGIC_BLOBWRAPPER = 0xFADE0B01  # CMS Signature
CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xFADE7171
CSMAGIC_EMBEDDED_ENTITLEMENTS_DER = 0xFADE7172

# Code Directory Flags
CS_FLAGS = {
    0x00000001: "CS_VALID",
    0x00000002: "CS_ADHOC",
    0x00000004: "CS_GET_TASK_ALLOW",
    0x00000008: "CS_INSTALLER",
    0x00000010: "CS_FORCED_LV",
    0x00000020: "CS_INVALID_ALLOWED",
    0x00000100: "CS_HARD",
    0x00000200: "CS_KILL",
    0x00000400: "CS_CHECK_EXPIRATION",
    0x00000800: "CS_RESTRICT",
    0x00001000: "CS_ENFORCEMENT",
    0x00002000: "CS_REQUIRE_LV",
    0x00004000: "CS_ENTITLEMENTS_VALIDATED",
    0x00008000: "CS_NVRAM_UNRESTRICTED",
    0x00010000: "CS_RUNTIME",  # Hardened Runtime
    0x00020000: "CS_LINKER_SIGNED",
}

# Hash Types
HASH_TYPES = {
    0: "NO_HASH",
    1: "SHA1",
    2: "SHA256",
    3: "SHA256_TRUNCATED",
    4: "SHA384",
    5: "SHA512",
}

# Slot Types (used in SuperBlob index)
CSSLOT_CODEDIRECTORY = 0  # Primary Code Directory
CSSLOT_INFOSLOT = 1
CSSLOT_REQUIREMENTS = 2
CSSLOT_RESOURCEDIR = 3
CSSLOT_APPLICATION = 4
CSSLOT_ENTITLEMENTS = 5
CSSLOT_DER_ENTITLEMENTS = 7
CSSLOT_SIGNATURESLOT = 0x10000  # CMS Signature
CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000  # First alternate CD (0x1000, 0x1001, ...)


class MachOSignatureExtractor(MachOExtractor):
    """
    Extracts code signature information from Mach-O binaries using LIEF.
    Parses certificates using asn1crypto for full X.509 details.
    """

    def __init__(
        self,
        filepath,
        log,
        exporters=None,
        index_prefix=None,
        elastic_index=None,
        known_benign=False,
        known_malicious=False,
        macho=None,
    ):
        super().__init__(
            filepath,
            log,
            exporters,
            index_prefix,
            elastic_index,
            known_benign,
            known_malicious,
            macho,
        )
        self.elastic_index = self.index_prefix + "-macho_signature"
        self.log.debug(inspect.currentframe().f_code.co_name)
        self._lief_binary = None
        self._lief_fat = None

    def tag(self):
        return [
            Tag.MACHO_SIGNATURE.value,
            Tag.MACHO_CODE_DIRECTORY.value,
            Tag.MACHO_CERTIFICATE.value,
            Tag.MACHO_ENTITLEMENTS.value
        ]

    def _get_lief_binary(self, arch_index: int = 0) -> Optional[lief.MachO.Binary]:
        """Get LIEF binary for a specific architecture index."""
        try:
            if self._lief_fat is None:
                config = lief.MachO.ParserConfig()
                config.parse_dyld_exports = False
                config.parse_dyld_bindings = False
                config.parse_dyld_rebases = False
                self._lief_fat = lief.MachO.parse(self.filepath, config)

            if self._lief_fat is None:
                return None

            if arch_index < self._lief_fat.size:
                return self._lief_fat.at(arch_index)
            return None
        except Exception as e:
            self.log.error(f"Error parsing MachO with LIEF: {e}")
            return None

    def _parse_superblob(self, data: bytes) -> Dict[int, bytes]:
        """
        Parse the SuperBlob structure to extract individual blobs.

        SuperBlob structure:
        - magic: uint32 (0xfade0cc0)
        - length: uint32
        - count: uint32
        - index[count]: (type: uint32, offset: uint32)
        - blobs...

        Returns dict keyed by slot type (not magic), so we can handle
        multiple Code Directories (primary at slot 0, alternates at 0x1000+).
        """
        blobs = {}
        if len(data) < 12:
            return blobs

        magic, length, count = struct.unpack(">III", data[:12])

        if magic != CSMAGIC_EMBEDDED_SIGNATURE:
            self.log.warning(f"Unexpected SuperBlob magic: {hex(magic)}")
            return blobs

        # Parse blob index
        offset = 12
        blob_index = []
        for _ in range(count):
            if offset + 8 > len(data):
                break
            slot_type, blob_offset = struct.unpack(">II", data[offset:offset + 8])
            blob_index.append((slot_type, blob_offset))
            offset += 8

        # Extract each blob, keyed by slot type
        for slot_type, blob_offset in blob_index:
            if blob_offset + 8 > len(data):
                continue
            blob_magic, blob_length = struct.unpack(">II", data[blob_offset:blob_offset + 8])
            if blob_offset + blob_length <= len(data):
                blobs[slot_type] = data[blob_offset:blob_offset + blob_length]

        return blobs

    def _parse_code_directory(self, cd_data: bytes) -> Optional[MachOCodeDirectory]:
        """
        Parse CodeDirectory blob.

        CodeDirectory structure:
        - magic: uint32 (0xfade0c02)
        - length: uint32
        - version: uint32
        - flags: uint32
        - hashOffset: uint32
        - identOffset: uint32
        - nSpecialSlots: uint32
        - nCodeSlots: uint32
        - codeLimit: uint32
        - hashSize: uint8
        - hashType: uint8
        - platform: uint8
        - pageSize: uint8
        - spare2: uint32
        - scatterOffset: uint32 (v >= 0x20100)
        - teamOffset: uint32 (v >= 0x20200)
        ...
        """
        if len(cd_data) < 44:
            return None

        try:
            magic, length, version, flags = struct.unpack(">IIII", cd_data[:16])

            if magic != CSMAGIC_CODEDIRECTORY:
                return None

            hash_offset, ident_offset = struct.unpack(">II", cd_data[16:24])
            n_special_slots, n_code_slots, code_limit = struct.unpack(">III", cd_data[24:36])
            hash_size, hash_type, platform, page_size_log2 = struct.unpack(">BBBB", cd_data[36:40])

            # Extract identifier string
            identifier = None
            if ident_offset > 0 and ident_offset < len(cd_data):
                end = cd_data.find(b'\x00', ident_offset)
                if end > ident_offset:
                    identifier = cd_data[ident_offset:end].decode('utf-8', errors='replace')

            # Extract team ID (version >= 0x20200)
            team_id = None
            if version >= 0x20200 and len(cd_data) >= 52:
                team_offset = struct.unpack(">I", cd_data[48:52])[0]
                if team_offset > 0 and team_offset < len(cd_data):
                    end = cd_data.find(b'\x00', team_offset)
                    if end > team_offset:
                        team_id = cd_data[team_offset:end].decode('utf-8', errors='replace')

            # Parse flags
            flags_str = [name for flag, name in CS_FLAGS.items() if flags & flag]

            # Calculate CD hash
            cd_hash = hashlib.sha256(cd_data).hexdigest()

            return MachOCodeDirectory(
                identifier=identifier,
                team_id=team_id,
                flags=flags,
                cd_hash=cd_hash,
            )
        except Exception as e:
            self.log.error(f"Error parsing CodeDirectory: {e}")
            return None

    def _parse_entitlements(self, ent_data: bytes) -> Optional[Dict]:
        """
        Parse Entitlements blob (XML plist).

        Returns parsed entitlements dict.
        """
        if len(ent_data) < 8:
            return None

        try:
            magic, length = struct.unpack(">II", ent_data[:8])

            # Only accept XML plist magic - DER requires separate parser
            if magic != CSMAGIC_EMBEDDED_ENTITLEMENTS:
                return None

            # Extract plist data (after header)
            plist_data = ent_data[8:]

            # Parse plist
            try:
                entitlements = plistlib.loads(plist_data)
                return entitlements
            except Exception:
                # Try to strip null bytes
                plist_data = plist_data.rstrip(b'\x00')
                entitlements = plistlib.loads(plist_data)
                return entitlements

        except Exception as e:
            self.log.debug(f"Error parsing entitlements: {e}")
            return None

    def _parse_entitlements_der(self, ent_data: bytes) -> Optional[Dict]:
        """Best-effort parse of DER-encoded entitlements.

        DER-only binaries are rare (standard codesign embeds both XML and DER).
        Uses a layered approach: structured ASN.1 first, string extraction fallback.
        """
        if len(ent_data) < 8:
            return None

        try:
            magic, length = struct.unpack(">II", ent_data[:8])
            if magic != CSMAGIC_EMBEDDED_ENTITLEMENTS_DER:
                return None

            der_data = ent_data[8:]

            # Layer 1: Try structured ASN.1 parsing via asn1crypto
            try:
                from asn1crypto import core

                entitlements = {}
                seq = core.Sequence.load(der_data)
                for item in seq:
                    try:
                        if len(item) >= 2:
                            key = str(item[0].native)
                            val_element = item[1]
                            tag = val_element.tag
                            if tag == 0:      # BOOLEAN
                                entitlements[key] = val_element.contents != b'\x00'
                            elif tag == 1:    # UTF8String
                                entitlements[key] = val_element.native
                            elif tag == 2:    # INTEGER
                                entitlements[key] = val_element.native
                            elif tag == 3:    # SET OF (array of strings)
                                entitlements[key] = [str(v.native) for v in val_element]
                            else:
                                entitlements[key] = val_element.native
                    except Exception:
                        continue
                if entitlements:
                    return entitlements
            except Exception as e:
                self.log.debug(f"ASN.1 structured parse failed, trying string extraction: {e}")

            # Layer 2: Regex extraction of known entitlement key strings from raw DER
            # Gets keys but not typed values — still useful for detection/triage
            import re
            text = der_data.decode('utf-8', errors='ignore')
            keys = re.findall(
                r'(com\.apple\.[a-zA-Z0-9._-]+'
                r'|application-identifier'
                r'|get-task-allow'
                r'|platform-application'
                r'|team-identifier'
                r'|keychain-access-groups)',
                text
            )
            if keys:
                # Deduplicate while preserving order
                seen = set()
                entitlements = {}
                for key in keys:
                    if key not in seen:
                        seen.add(key)
                        entitlements[key] = True  # Assume boolean (most common type)
                self.log.info(
                    f"DER entitlements: ASN.1 parse failed, extracted {len(entitlements)} "
                    f"keys via string matching (values defaulted to True)"
                )
                return entitlements

            return None

        except Exception as e:
            self.log.debug(f"Error parsing DER entitlements: {e}")
            return None

    def _parse_cms_signature(self, cms_data: bytes) -> Tuple[List[MachOCertificate], Optional[str], bool]:
        """
        Parse CMS/PKCS#7 signature blob to extract certificates.

        Returns tuple of (certificates_list, signing_time, is_verified).
        """
        certificates = []
        signing_time = None
        has_cms_signature = False

        if len(cms_data) < 8:
            return certificates, signing_time, has_cms_signature

        try:
            magic, length = struct.unpack(">II", cms_data[:8])

            if magic != CSMAGIC_BLOBWRAPPER:
                return certificates, signing_time, has_cms_signature

            # CMS data starts after the blob header
            der_data = cms_data[8:]

            # Parse CMS ContentInfo
            content_info = cms.ContentInfo.load(der_data)

            if content_info['content_type'].native != 'signed_data':
                return certificates, signing_time, is_verified

            signed_data = content_info['content']

            # Extract certificates
            if signed_data['certificates']:
                cert_order = 0
                for cert_choice in signed_data['certificates']:
                    try:
                        if cert_choice.name == 'certificate':
                            cert = cert_choice.chosen
                            macho_cert = self._extract_certificate_info(cert, cert_order)
                            if macho_cert:
                                certificates.append(macho_cert)
                                cert_order += 1
                    except Exception as e:
                        self.log.debug(f"Error parsing certificate: {e}")
                        continue

            # Determine certificate types (leaf, intermediate, root)
            self._classify_certificates(certificates)

            # Extract signing time from signer info
            if signed_data['signer_infos']:
                for signer_info in signed_data['signer_infos']:
                    if signer_info['signed_attrs']:
                        for attr in signer_info['signed_attrs']:
                            if attr['type'].native == 'signing_time':
                                signing_time = attr['values'][0].native
                                if isinstance(signing_time, datetime):
                                    signing_time = signing_time.strftime("%Y-%m-%d %H:%M:%S")
                                break

            # Note: This only indicates presence of CMS signature structure, not
            # cryptographic verification. Full verification would require checking
            # against Apple's CA chain.
            has_cms_signature = len(certificates) > 0

        except Exception as e:
            self.log.error(f"Error parsing CMS signature: {e}")

        return certificates, signing_time, has_cms_signature

    def _extract_certificate_info(self, cert: x509.Certificate, order: int) -> Optional[MachOCertificate]:
        """Extract certificate information from an X.509 certificate."""
        try:
            tbs = cert['tbs_certificate']

            # Serial number
            serial = tbs['serial_number'].native
            serial_hex = format(serial, 'x').upper()

            # Subject and Issuer
            subject = self._format_name(tbs['subject'])
            issuer = self._format_name(tbs['issuer'])

            # Extract common name, organization, and OU from subject
            common_name = None
            organization = None
            organizational_unit = None
            try:
                for rdn in tbs['subject'].chosen:
                    for attr in rdn:
                        oid = attr['type'].native
                        value = attr['value'].native
                        if oid == 'common_name':
                            common_name = value
                        elif oid == 'organization_name':
                            organization = value
                        elif oid == 'organizational_unit_name':
                            organizational_unit = value
            except Exception:
                pass

            # Validity
            validity = tbs['validity']
            valid_from = validity['not_before'].native
            valid_to = validity['not_after'].native

            if isinstance(valid_from, datetime):
                valid_from = valid_from.strftime("%Y-%m-%d %H:%M:%S")
            if isinstance(valid_to, datetime):
                valid_to = valid_to.strftime("%Y-%m-%d %H:%M:%S")

            # Algorithm
            algorithm = tbs['signature']['algorithm'].native

            # Thumbprint (SHA-1 - industry standard for certificate thumbprints)
            cert_der = cert.dump()
            thumbprint = hashlib.sha1(cert_der).hexdigest().upper()

            return MachOCertificate(
                subject=subject,
                issuer=issuer,
                serial_number=serial_hex,
                not_before=valid_from,
                not_after=valid_to,
                thumbprint=thumbprint,
                certificate_type="unknown",  # Will be classified later
                order=order,
                algorithm=algorithm,
                common_name=common_name,
                organization=organization,
                organizational_unit=organizational_unit,
            )
        except Exception as e:
            self.log.error(f"Error extracting certificate info: {e}")
            return None

    def _format_name(self, name) -> str:
        """Format X.509 Name as a readable string."""
        try:
            parts = []
            for rdn in name.chosen:
                for attr in rdn:
                    oid = attr['type'].native
                    value = attr['value'].native
                    # Map common OIDs to abbreviations
                    oid_map = {
                        'common_name': 'CN',
                        'organization_name': 'O',
                        'organizational_unit_name': 'OU',
                        'country_name': 'C',
                        'state_or_province_name': 'ST',
                        'locality_name': 'L',
                        'email_address': 'E',
                        'user_id': 'UID',
                    }
                    abbrev = oid_map.get(oid, oid)
                    parts.append(f"{abbrev}={value}")
            return ", ".join(parts)
        except Exception:
            return str(name.human_friendly) if hasattr(name, 'human_friendly') else str(name)

    def _classify_certificates(self, certificates: List[MachOCertificate]):
        """
        Classify certificates as leaf, intermediate, or root using
        chain relationships:
        1. Root: self-signed (subject == issuer)
        2. Intermediate: issued another cert in the chain
        3. Leaf: everything else (end-entity)
        """
        if not certificates:
            return

        for cert in certificates:
            if cert.subject == cert.issuer:
                cert.certificate_type = "root"
            # Check if this cert issued any other cert in the chain
            elif any(c.issuer == cert.subject and c != cert for c in certificates):
                cert.certificate_type = "intermediate"
            else:
                cert.certificate_type = "leaf"

    def _extract_code_signature(self, arch_index: int = 0, arch_name: str = None) -> Optional[MachOCodeSigningInfo]:
        """Extract code signature information for a specific architecture."""
        self.log.debug(f"Extracting code signature for arch index {arch_index}")

        binary = self._get_lief_binary(arch_index)
        if binary is None:
            return None

        # Check if binary has code signature
        if not binary.has_code_signature:
            return MachOCodeSigningInfo(
                _id=self.sha256,
                signing_type="unsigned",
                has_signature=False,
                number_of_certificates=0,
                arch_identifier=arch_name,
            )

        # Get raw code signature data
        code_sig = binary.code_signature
        if code_sig is None:
            return None

        # LIEF's code_sig.data only returns the load command, not the actual signature
        # We need to read the signature data directly from the file
        # For FAT binaries, data_offset is relative to the slice, not the file
        sig_data = None
        try:
            with open(self.filepath, 'rb') as f:
                # fat_offset is 0 for single-arch binaries, slice offset for FAT
                file_offset = code_sig.data_offset + binary.fat_offset
                f.seek(file_offset)
                sig_data = f.read(code_sig.data_size)
        except Exception as e:
            self.log.error(f"Error reading code signature data from file: {e}")
            return None

        if not sig_data:
            return None

        # Parse SuperBlob to get individual blobs (keyed by slot type)
        blobs = self._parse_superblob(sig_data)

        # Parse all Code Directories (primary + alternates)
        code_directories = []

        # Primary Code Directory at slot 0
        if CSSLOT_CODEDIRECTORY in blobs:
            cd = self._parse_code_directory(blobs[CSSLOT_CODEDIRECTORY])
            if cd:
                cd.slot_type = CSSLOT_CODEDIRECTORY
                code_directories.append(cd)

        # Alternate Code Directories at slots 0x1000, 0x1001, ...
        for slot_type in sorted(blobs.keys()):
            if slot_type >= CSSLOT_ALTERNATE_CODEDIRECTORIES and slot_type < CSSLOT_SIGNATURESLOT:
                cd = self._parse_code_directory(blobs[slot_type])
                if cd:
                    cd.slot_type = slot_type
                    code_directories.append(cd)

        # Use primary CD (or first available) for signing info
        primary_cd = code_directories[0] if code_directories else None

        # Parse Entitlements - prefer XML plist, fall back to DER for edge cases
        entitlements = None
        if CSSLOT_ENTITLEMENTS in blobs:
            entitlements = self._parse_entitlements(blobs[CSSLOT_ENTITLEMENTS])
        if entitlements is None and CSSLOT_DER_ENTITLEMENTS in blobs:
            # DER-only binary (rare — non-standard signing tool like ldid)
            self.log.warning("Only DER entitlements found, no XML plist slot")
            entitlements = self._parse_entitlements_der(blobs[CSSLOT_DER_ENTITLEMENTS])

        # Parse CMS Signature (certificates)
        certificates = []
        signing_time = None
        has_cms_signature = False

        if CSSLOT_SIGNATURESLOT in blobs:
            certificates, signing_time, has_cms_signature = self._parse_cms_signature(blobs[CSSLOT_SIGNATURESLOT])

        # Determine signing type
        signing_type = "unsigned"
        if primary_cd:
            if primary_cd.flags and (primary_cd.flags & 0x00000002):  # CS_ADHOC
                signing_type = "ad-hoc"
            elif len(certificates) > 0:
                signing_type = "certificate"
            elif primary_cd.identifier:
                # Has CodeDirectory but no CMS = ad-hoc
                signing_type = "ad-hoc"

        # Check for hardened runtime and library validation flags
        has_hardened_runtime = False
        has_library_validation = False
        if primary_cd and primary_cd.flags:
            has_hardened_runtime = bool(primary_cd.flags & 0x00010000)  # CS_RUNTIME
            has_library_validation = bool(primary_cd.flags & 0x00002000)  # CS_REQUIRE_LV

        return MachOCodeSigningInfo(
            _id=self.sha256,
            signing_type=signing_type,
            has_signature=has_cms_signature,
            number_of_certificates=len(certificates),
            code_directories=code_directories,
            entitlements=entitlements,
            x509_certificates=certificates,
            date_signed=signing_time,
            arch_identifier=arch_name,
            has_hardened_runtime=has_hardened_runtime,
            has_library_validation=has_library_validation,
        )

    def extract(self) -> Optional[Any]:
        """Extract code signature information from the Mach-O binary."""
        self.log.debug(inspect.currentframe().f_code.co_name)

        try:
            # Get LIEF FAT binary to check architecture count
            config = lief.MachO.ParserConfig()
            config.parse_dyld_exports = False
            config.parse_dyld_bindings = False
            config.parse_dyld_rebases = False
            self._lief_fat = lief.MachO.parse(self.filepath, config)

            if self._lief_fat is None:
                self.log.error("Failed to parse MachO with LIEF")
                return None

            # Get architecture names from machofile for consistency
            arch_names = []
            if self.macho:
                try:
                    arch_names = self.macho.get_architectures()
                except Exception:
                    pass

            # If no arch names from machofile, generate them
            if not arch_names:
                arch_names = [f"arch_{i}" for i in range(self._lief_fat.size)]

            if self._lief_fat.size > 1:
                # FAT/Universal binary - extract for each architecture
                results = []
                for i in range(self._lief_fat.size):
                    arch_name = arch_names[i] if i < len(arch_names) else f"arch_{i}"
                    signature = self._extract_code_signature(i, arch_name)
                    if signature:
                        results.append(signature)
                return results if results else None
            else:
                # Single architecture
                arch_name = arch_names[0] if arch_names else None
                return self._extract_code_signature(0, arch_name)

        except Exception as e:
            self.log.error(f"Error extracting MachO code signature: {e}")
            return None

    def prepare_export_data(self, exporter_type: str) -> Any:
        """Prepare data for export to Elasticsearch or ClickHouse."""
        self.log.debug(inspect.currentframe().f_code.co_name)

        if exporter_type == "ElasticsearchExporter":
            return self.extract()

        elif exporter_type == "ClickHouseExporter":
            result = self.extract()
            if result is None:
                return None

            # Normalize to list
            signatures = result if isinstance(result, list) else [result]

            current_time = datetime.now(timezone.utc)

            # Prepare signature data
            signatures_data = []
            certificates_data = []
            entitlements_data = []

            for sig in signatures:
                if sig is None:
                    continue

                # Get architecture-specific hash if available
                arch_sha256 = self.sha256

                # If FAT binary, try to get arch-specific sha256
                if self.macho and sig.arch_identifier:
                    try:
                        arch_info = self.macho.get_general_info(arch=sig.arch_identifier)
                        if arch_info:
                            arch_sha256 = arch_info.get('SHA256', self.sha256)
                    except Exception as e:
                        self.log.debug(f"Could not get arch-specific info: {e}")

                # Get primary CD fields for signatures table
                primary_cd = sig.code_directories[0] if sig.code_directories else None
                cd_identifier = primary_cd.identifier if primary_cd else None
                cd_team_id = primary_cd.team_id if primary_cd else None
                cd_flags = primary_cd.flags if primary_cd else None
                cd_hash = primary_cd.cd_hash if primary_cd else None

                signatures_data.append([
                    arch_sha256,
                    sig.signing_type,
                    sig.has_signature,
                    sig.number_of_certificates,
                    cd_identifier,
                    cd_team_id,
                    cd_flags,
                    cd_hash,
                    sig.has_hardened_runtime,
                    sig.has_library_validation,
                    sig.date_signed,
                    current_time,
                ])

                # Prepare certificate data
                if sig.x509_certificates:
                    for cert in sig.x509_certificates:
                        certificates_data.append([
                            arch_sha256,
                            cert.certificate_type,
                            cert.algorithm,
                            cert.issuer,
                            cert.subject,
                            cert.serial_number,
                            cert.thumbprint,
                            datetime.strptime(cert.not_before, "%Y-%m-%d %H:%M:%S") if cert.not_before else None,
                            datetime.strptime(cert.not_after, "%Y-%m-%d %H:%M:%S") if cert.not_after else None,
                            current_time,
                        ])

                # Prepare entitlements data
                if sig.entitlements:
                    for ent_key, ent_value in sig.entitlements.items():
                        # Determine type and set appropriate value columns
                        if isinstance(ent_value, bool):
                            ent_type = 'boolean'
                            value_bool = 1 if ent_value else 0
                            value_string = None
                            value_array = []
                        elif isinstance(ent_value, list):
                            ent_type = 'array'
                            value_bool = None
                            value_string = None
                            # Convert all list items to strings
                            value_array = [str(v) for v in ent_value]
                        else:
                            ent_type = 'string'
                            value_bool = None
                            value_string = str(ent_value)
                            value_array = []

                        entitlements_data.append([
                            arch_sha256,
                            ent_key,
                            ent_type,
                            value_bool,
                            value_string,
                            value_array,
                            current_time,
                        ])

            return {
                'multi_table': True,
                'signatures': {
                    'table': 'redb_macho_signatures',
                    'data': signatures_data,
                    'column_names': [
                        'sha256',
                        'signing_type', 'has_signature', 'number_of_certificates',
                        'cd_identifier', 'cd_team_id', 'cd_flags', 'cd_hash',
                        'has_hardened_runtime', 'has_library_validation',
                        'date_signed', 'analysis_date'
                    ],
                    'column_type_names': [
                        'FixedString(64)',
                        "Enum8('unsigned' = 0, 'ad-hoc' = 1, 'certificate' = 2)", 'UInt8', 'UInt32',
                        'Nullable(String)', 'Nullable(String)', 'Nullable(UInt32)', 'Nullable(String)',
                        'UInt8', 'UInt8',
                        'Nullable(DateTime64(3, \'UTC\'))', 'DateTime64(3, \'UTC\')'
                    ]
                },
                'certificates': {
                    'table': 'redb_macho_certificates',
                    'data': certificates_data,
                    'column_names': [
                        'sha256', 'certificate_type', 'certificate_algorithm',
                        'certificate_issuer', 'certificate_subject', 'certificate_serial_number',
                        'certificate_thumbprint', 'certificate_valid_from', 'certificate_valid_to',
                        'analysis_date'
                    ],
                    'column_type_names': [
                        'FixedString(64)',
                        "Enum8('leaf' = 1, 'intermediate' = 2, 'root' = 3)", 'LowCardinality(String)',
                        'String', 'String', 'String', 'FixedString(40)',
                        'DateTime64(3, \'UTC\')', 'DateTime64(3, \'UTC\')', 'DateTime64(3, \'UTC\')'
                    ]
                },
                'entitlements': {
                    'table': 'redb_macho_entitlements',
                    'data': entitlements_data,
                    'column_names': [
                        'sha256', 'entitlement_key', 'entitlement_type',
                        'value_bool', 'value_string', 'value_array',
                        'analysis_date'
                    ],
                    'column_type_names': [
                        'FixedString(64)', 'LowCardinality(String)',
                        "Enum8('boolean' = 1, 'string' = 2, 'array' = 3)",
                        'Nullable(UInt8)', 'Nullable(String)', 'Array(String)',
                        'DateTime64(3, \'UTC\')'
                    ]
                }
            }

        return None

    def get_clickhouse_table(self) -> str:
        """Return primary ClickHouse table name."""
        return "redb_macho_signatures"

    def get_clickhouse_tables(self) -> Dict[str, str]:
        """Return all ClickHouse tables for multi-table export."""
        return {
            'signatures': 'redb_macho_signatures',
            'certificates': 'redb_macho_certificates',
            'entitlements': 'redb_macho_entitlements'
        }