Neil A. M. Maiden

221 papers A* 8A 41B 30C 6Misc 2Journal 81Unranked 47
YearRankTypeTitle / Venue / Authors
2025 J jnl
CoRR
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie, Kostas Petrianakis, Amanda Brown
2025 J jnl
IEEE Softw.
Neil A. M. Maiden, Konstantinos Zachos, Kostas Petrianakis, James Lockerbie, Chantakan Chanpalangsri, Holger Ernst, Seyfettin Kara
2025 J jnl
IEEE Softw.
Neil A. M. Maiden
2024 conf
CHI Extended Abstracts
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie, Amanda Brown, Sam Steele, Alex Wolf
2023 C conf
ICCC
Janet Rafner, Blanka Zana, Tristan Beolet, Safinaz Büyükgüzel, Neil A. M. Maiden, Ewen Michel, Sebastian Risi, Jacob Sherson
2023 J jnl
Expert Syst. J. Knowl. Eng.
Neil A. M. Maiden, James Lockerbie, Konstantinos Zachos, Alex Wolf, Amanda Brown
2022 B conf
Creativity & Cognition
Neil A. M. Maiden, Konstantinos Zachos
2022 B conf
Creativity & Cognition
Konstantinos Zachos, James Lockerbie, Amanda Brown, Stephanie Terwindt, Sam Steele, Aimee Kyffin, Bassam Jabry, Clarence Ng, Alex Wolf, Pete Goodman, Neil A. M. Maiden
2022 J jnl
J. Syst. Softw.
Benito Giunta, Corentin Burnay, Neil A. M. Maiden, Stéphane Faulkner
2022 B conf
Creativity & Cognition
Antoni Petrov Rayzhekov, Claudia Schnugg, Neil A. M. Maiden, Konstantinos Zachos
2022 J jnl
Inf. Syst.
James Lockerbie, Neil A. M. Maiden
2021 conf
INFORSID
Neil A. M. Maiden
2021 J jnl
Behav. Inf. Technol.
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie
2021 B conf
AIME
Dympna O'Sullivan, Rilwan Remilekun Basaru, Simone Stumpf, Neil A. M. Maiden
2021 B ed.
Creativity & Cognition
Corina Sas, Neil A. M. Maiden, Brian P. Bailey, Celine Latulipe, Ellen Yi-Luen Do
2021 conf
icSPORTS
James Lockerbie, Neil A. M. Maiden, Alex Wolf, Konstantinos Zachos
2021 conf
CMNA
Elena Musi, Rudi Palmieri, Chiara Mercuri, Alessandro Giudici, Neil A. M. Maiden, Charlotte Hardman, Rita Borgo
2020 Misc conf
NordiCHI
Neil A. M. Maiden, Konstantinos Zachos, Suzanne Franks, Rebecca Wells, Samantha Stallard
2020 J jnl
Commun. ACM
Neil A. M. Maiden, Konstantinos Zachos, Amanda Brown, Dimitris Apostolou, Balder Holm, Lars Nyre, Aleksander Nygård Tonheim, Arend van den Beld
2020 conf
REFSQ Workshops
James Lockerbie, Neil A. M. Maiden
2019 J jnl
Inf. Softw. Technol.
Jennifer Horkoff, Neil A. M. Maiden, David Asboth
2019 J jnl
Int. J. Hum. Comput. Interact.
Neil A. M. Maiden, Kristine Pitts, Kevin Pudney, Konstantinos Zachos
2019 B conf
Creativity & Cognition
Neil A. M. Maiden, Konstantinos Zachos, Amanda Brown, Lars Nyre, Balder Holm, Aleksander Nygård Tonheim, Claus Hesseling, Andrea Wagemans, Dimitris Apostolou
2019 J jnl
Pers. Ubiquitous Comput.
Jordan Tewell, Dympna O'Sullivan, Neil A. M. Maiden, James Lockerbie, Simone Stumpf
2019 A conf
CAiSE
James Lockerbie, Neil A. M. Maiden
2018 conf
IISA
Konstantinos Zachos, Dimitris Apostolou, Fotis Paraskevopoulos, Stergios Ientsek, Neil A. M. Maiden, Amanda Brown, Gregoris Mentzas
2018 A* conf
CHI
Neil A. M. Maiden, Konstantinos Zachos, Amanda Brown, George Brock, Lars Nyre, Aleksander Nygård Tonheim, Dimitris Apostolou, Jeremy Evans
2017 B conf
REFSQ
James Lockerbie, Neil A. M. Maiden, Chris Williams, Leigh Chase
2017 B conf
Creativity & Cognition
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie, Kasia Camargo, Shaun Hoddy
2017 A* conf
CHI
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie, Sergio Levis, Kasia Camargo, Shaun Hoddy, Gianluca Allemandi
2016 conf
iStar
Jennifer Horkoff, Neil A. M. Maiden
2016 conf
BCS HCI
Neil A. M. Maiden, Konstantinos Zachos, James Lockerbie, George Brock, Christopher Traver
2016 conf
BCS HCI
Max Schubmann, Rebecca Sulley, Michael McHugh, Alex Lai, Neil A. M. Maiden
2016 J jnl
IEEE Comput. Intell. Mag.
Dimitris Apostolou, Konstantinos Zachos, Neil A. M. Maiden, Núria Agell, Germán Sánchez-Hernández, Maria Taramigkou, Kam Star, Meia Wippoo
2016 A conf
RE
Corentin Burnay, Jennifer Horkoff, Neil A. M. Maiden
2016 ch.
Collaboration in Creative Design
James Lockerbie, Neil A. M. Maiden
2015 B conf
Creativity & Cognition
Konstantinos Zachos, Neil A. M. Maiden, Sergio Levis
2015 conf
REFSQ Workshops
Jennifer Horkoff, Neil A. M. Maiden
2015 B conf
Creativity & Cognition
Jennifer Horkoff, Neil A. M. Maiden, James Lockerbie
2015 conf
iStar
Marilia Guterres Ferreira, Julio César Sampaio do Prado Leite, Neil A. M. Maiden
2015 conf
EUSPN/ICTH
Paul Hodge, Jacqueline Davis, Neil A. M. Maiden, Bradley Mann, Aili Nidsjo, Alan Simpson, Lisa Reynolds
2015 B conf
Creativity & Cognition
Peter Dalsgård, Kim Halskov, Wendy E. Mackay, Neil A. M. Maiden, Jean-Bernard Martens
2015 conf
iStar
Jennifer Horkoff, Neil A. M. Maiden
2015 J jnl
Behav. Inf. Technol.
Kristine Pitts, Kevin Pudney, Konstantinos Zachos, Neil A. M. Maiden, Birgit R. Krogstie, Sara Jones, Malcolm Rose, Julie MacManus, Ian Turner
2014 B conf
REFSQ
Neil A. M. Maiden, James Lockerbie, Konstantinos Zachos, Antonia Bertolino, Guglielmo De Angelis, Francesca Lonetti
2014 J jnl
IEEE Softw.
Tore Dybå, Neil A. M. Maiden, Robert Glass
2014 A conf
RE
Amanda Rubython, Neil A. M. Maiden
2013 C conf
ICCC
Konstantinos Zachos, Neil A. M. Maiden
2013 B conf
Creativity & Cognition
Konstantinos Zachos, Neil A. M. Maiden, Kristine Pitts, Sara Jones, Ian Turner, Malcolm Rose, Kevin Pudney, Julie MacManus
2013 C conf
ICCC
Shiona Webster, Konstantinos Zachos, Neil A. M. Maiden
2013 conf
SGDA
Anja Sisarica, Neil A. M. Maiden
2013 A* conf
ICSE
Dalal Alrajeh, Alessandra Russo, James Lockerbie, Neil A. M. Maiden, Alistair Mavin, Mark Novak
2013 J jnl
Commun. ACM
Neil A. M. Maiden, Sonali D'Souza, Sara Jones, Lars Müller, Lucia Pannese, Kristine Pitts, Michael Prilla, Kevin Pudney, Malcolm Rose, Ian Turner, Konstantinos Zachos
2013 Misc conf
EICS
Neil A. M. Maiden, Bianca Hollis
2013 B conf
Creativity & Cognition
Anja Sisarica, Neil A. M. Maiden, Dalia Morosini, Lucia Panesse, Kevin Pudney, Malcolm Rose
2013 J jnl
Int. J. Creative Comput.
Konstantinos Zachos, Neil A. M. Maiden, Kristine Pitts, Sara Jones, Ian Turner, Malcolm Rose, Kevin Pudney, Julie MacManus
2013 J jnl
IEEE Softw.
Bianca Hollis, Neil A. M. Maiden
2013 A conf
RE
Neil A. M. Maiden
2013 J jnl
IEEE Softw.
Neil A. M. Maiden
2013 ed.
iStar
Jaelson Castro, Jennifer Horkoff, Neil A. M. Maiden, Eric S. K. Yu
2013 A conf
RE
Neil A. M. Maiden
2013 ed.
NGEBIS Short Papers
Gash Bhullar, Neil A. M. Maiden, Michele Missikoff, Fabrizio Smith
2013 J jnl
IEEE Softw.
Neil A. M. Maiden
2012 J jnl
IEEE Softw.
Neil A. M. Maiden
2012 conf
I-KNOW
Birgit R. Krogstie, John Krogstie, Neil A. M. Maiden, James Lockerbie, Daniel Wessel, Kristin Knipfer
2012 conf
INFORSID
Neil A. M. Maiden
2012 J jnl
IEEE Softw.
Neil A. M. Maiden
2012 J jnl
Int. J. Syst. Serv. Oriented Eng.
Konstantinos Zachos, Angela P. Kounkou, Neil A. M. Maiden
2012 J jnl
IEEE Softw.
Neil A. M. Maiden
2012 J jnl
Behav. Inf. Technol.
Peter J. Wild, Geke van Dijk, Neil A. M. Maiden
2012 J jnl
IEEE Softw.
Neil A. M. Maiden
2012 J jnl
Requir. Eng.
Alastair Milne, Neil A. M. Maiden
2012 J jnl
IEEE Softw.
Neil A. M. Maiden
2011 conf
RESS
Luciano Baresi, Jane Cleland-Huang, James Lockerbie, Neil A. M. Maiden, Liliana Pasquale
2011 B conf
REFSQ
Neil A. M. Maiden
2011 B conf
Creativity & Cognition
Konstantinos Zachos, Neil A. M. Maiden, Inger Kristine Karlsen, Sara Jones, Ian Turner, Malcolm Rose, Kevin Pudney, Julie MacManus
2011 conf
EmpiRE
Neil A. M. Maiden, Massimiliano Di Penta, Fabio Massacci
2011 A conf
RE
Alastair Milne, Neil A. M. Maiden
2011 J jnl
IEEE Softw.
Neil A. M. Maiden, Anja Wever
2011 J jnl
IEEE Softw.
Neil A. M. Maiden
2011 A conf
CAiSE
Markus Nöbauer, Norbert Seyff, Neil A. M. Maiden, Konstantinos Zachos
2011 book
Eric S. K. Yu, Paolo Giorgini, Neil A. M. Maiden, John Mylopoulos
2011 ch.
Social Modeling for Requirements Engineering
Eric Yu, Paolo Giorgini, Neil A. M. Maiden, John Mylopoulos
2011 conf
PervasiveHealth
Inger Kristine Karlsen, Konstantinos Zachos, Neil A. M. Maiden, Sara Jones, Ian Turner, Malcolm Rose, Kevin Pudney
2011 J jnl
IEEE Softw.
Neil A. M. Maiden
2011 B conf
Creativity & Cognition
Sara Jones, Anni Poulsen, Neil A. M. Maiden, Konstantinos Zachos
2011 ch.
Social Modeling for Requirements Engineering
Neil A. M. Maiden, Sara Jones, Cornelius Ncube, James Lockerbie
2011 J jnl
IEEE Softw.
Neil A. M. Maiden
2011 A conf
RE
Anja Wever, Neil A. M. Maiden
2010 J jnl
IEEE Softw.
Neil A. M. Maiden, Sara Jones
2010 conf
ICSE (2)
Norbert Seyff, Florian Graf, Neil A. M. Maiden
2010 A conf
RE
Kevin Ryan, Neil A. M. Maiden, Martin Glinz
2010 A conf
RE
Martin Mahaux, Patrick Heymans, Neil A. M. Maiden
2010 A conf
RE
Neil A. M. Maiden, Sara Jones, Inger Kristine Karlsen, Roger Neill, Konstantinos Zachos, Alastair Milne
2010 J jnl
IEEE Softw.
Neil A. M. Maiden
2010 J jnl
IEEE Softw.
M. Sutherland, Neil A. M. Maiden
2010 J jnl
IEEE Softw.
Neil A. M. Maiden
2010 conf
iStar
James Lockerbie, Neil A. M. Maiden, Amir Dotan, Valentina Lichtner
2010 A conf
RE
Norbert Seyff, Florian Graf, Neil A. M. Maiden
2010 A conf
RE
James Lockerbie, David Bush, Neil A. M. Maiden, Henk A. P. Blom, Mariken H. C. Everdij
2009 conf
CHI Extended Abstracts
Valentina Lichtner, Angela P. Kounkou, Amir Dotan, José P. Kooken, Neil A. M. Maiden
2009 J jnl
IEEE Softw.
Neil A. M. Maiden
2009 conf
INTERACT (2)
Amir Dotan, Neil A. M. Maiden, Valentina Lichtner, Lola Germanovich
2009 A conf
UMAP
Tobias Ley, Barbara Kump, Antonia Maas, Neil A. M. Maiden, Dietrich Albert
2009 J jnl
Requir. Eng.
Norbert Seyff, Neil A. M. Maiden, Inger Kristine Karlsen, James Lockerbie, Paul Grünbacher, Florian Graf, Cornelius Ncube
2009 J jnl
IEEE Softw.
Peter Sawyer, Neil A. M. Maiden
2009 B conf
REFSQ
Inger Kristine Karlsen, Neil A. M. Maiden, Andruid Kerne
2009 J jnl
IEEE Softw.
Neil A. M. Maiden
2009 B conf
REFSQ
Norbert Seyff, Florian Graf, Neil A. M. Maiden, Paul Grünbacher
2009 J jnl
IEEE Softw.
Neil A. M. Maiden
2008 B conf
REFSQ
Konstantinos Zachos, Neil A. M. Maiden, Rhydian Howells-Morris
2008 A conf
CAiSE
Alberto Siena, Neil A. M. Maiden, James Lockerbie, Inger Kristine Karlsen, Anna Perini, Angelo Susi
2008 conf
iStar
James Lockerbie, Neil A. M. Maiden
2008 A conf
RE
Konstantinos Zachos, Neil A. M. Maiden
2008 B conf
REFSQ
Neil A. M. Maiden, Cornelius Ncube, James Lockerbie
2008 B conf
REFSQ
Norbert Seyff, Florian Graf, Paul Grünbacher, Neil A. M. Maiden
2008 J jnl
Inf. Softw. Technol.
Gemma Grau, Xavier Franch, Neil A. M. Maiden
2008 conf
BCS HCI (2)
Neil A. M. Maiden, Sara Jones
2008 conf
CAiSE Forum
James Lockerbie, Neil A. M. Maiden
2008 J jnl
IEEE Softw.
Neil A. M. Maiden
2008 A conf
RE
Luciano Baresi, Neil A. M. Maiden, Peter Sawyer
2008 J jnl
IEEE Softw.
Martin Mahaux, Neil A. M. Maiden
2008 A conf
RE
Sara Jones, Perry Lynch, Neil A. M. Maiden, Stefanie N. Lindstaedt
2008 J jnl
IEEE Softw.
Neil A. M. Maiden
2008 A conf
RE
Alistair Mavin, Mark Novak, Philip Wilkinson, Neil A. M. Maiden, Perry Lynch
2008 B conf
REFSQ
Claudia Schlosser, Sara Jones, Neil A. M. Maiden
2007 B conf
REFSQ
Cornelius Ncube, James Lockerbie, Neil A. M. Maiden
2007 A* conf
ICSE
Neil A. M. Maiden, Cornelius Ncube, Suzanne Robertson
2007 J jnl
IEEE Softw.
Neil A. M. Maiden, Norbert Seyff, Paul Grünbacher, Omo Otojare, Karl Mitteregger
2007 A conf
CAiSE
Konstantinos Zachos, Neil A. M. Maiden, Xiaohong Zhu, Sara Jones
2007 A conf
RE
Neil A. M. Maiden, Cornelius Ncube, Simin Kamali, Norbert Seyff, Paul Grünbacher
2007 J jnl
IEEE Softw.
Neil A. M. Maiden, Ian F. Alexander
2007 B conf
REFSQ
Sara Jones, Neil A. M. Maiden, Sharon Manning, John Greenwood
2007 J jnl
IEEE Softw.
Neil A. M. Maiden
2007 A conf
RE
Norbert Seyff, Florian Graf, Paul Grünbacher, Neil A. M. Maiden
2007 A conf
RE
Neil A. M. Maiden, James Lockerbie, Debbie Randall, Sean Jones, David Bush
2006 conf
MERE
Rick Rabiser, Norbert Seyff, Paul Grünbacher, Neil A. M. Maiden
2006 conf
EC-TEL Workshops
Tobias Ley, Barbara Kump, Stefanie N. Lindstaedt, Dietrich Albert, Neil A. M. Maiden, Sara Jones
2006 A* conf
ICSE
Neil A. M. Maiden, Suzanne Robertson, James Robertson
2006 A conf
CAiSE
Nektarios Dourdas, Xiaohong Zhu, Neil A. M. Maiden, Sara Jones, Konstantinos Zachos
2006 J jnl
IEEE Softw.
Neil A. M. Maiden
2006 A conf
RE
Neil A. M. Maiden, Norbert Seyff, Paul Grünbacher, Omo Otojare, Karl Mitteregger
2006 A conf
RE
James Lockerbie, Neil A. M. Maiden
2006 J jnl
Requir. Eng.
Roel J. Wieringa, Neil A. M. Maiden, Nancy R. Mead, Colette Rolland
2006 J jnl
IEEE Softw.
Neil A. M. Maiden
2006 J jnl
IEEE Softw.
Norbert Seyff, Paul Grünbacher, Neil A. M. Maiden
2006 J jnl
Requir. Eng.
Neil A. M. Maiden
2006 B conf
XP
Stephanie Chamberlain, Helen Sharp, Neil A. M. Maiden
2005 A conf
ER
Jolita Ralyté, Neil A. M. Maiden, Colette Rolland, Rébecca Deneckère
2005 A* conf
ICSE
Neil A. M. Maiden, Suzanne Robertson
2005 J jnl
Requir. Eng.
Neil A. M. Maiden, Sharon Manning, Sara Jones, John Greenwood
2005 J jnl
IEEE Softw.
Neil A. M. Maiden, Suzanne Robertson, Christof Ebert
2005 J jnl
Requir. Eng.
Alistair G. Sutcliffe, Brian Gault, Neil A. M. Maiden
2005 A conf
RE
Neil A. M. Maiden, Suzanne Robertson
2005 conf
CAiSE Short Paper Proceedings
Jolita Ralyté, Neil A. M. Maiden, Colette Rolland, Rébecca Deneckère
2005 A* conf
ICSE
Lawrence Chung, Xavier Franch, Neil A. M. Maiden
2005 A conf
RE
Gemma Grau, Xavier Franch, Neil A. M. Maiden
2005 J jnl
IEEE Softw.
Konstantinos Zachos, Neil A. M. Maiden, Amit Tosar
2005 J jnl
ACM SIGSOFT Softw. Eng. Notes
Lawrence Chung, Xavier Franch, Neil A. M. Maiden
2005 J jnl
IEEE Softw.
Neil A. M. Maiden
2005 A conf
RE
Ian F. Alexander, Suzanne Robertson, Neil A. M. Maiden
2004 A conf
RE
Konstantinos Zachos, Neil A. M. Maiden
2004 conf
ICCBSS
Vijay Sai, Xavier Franch, Neil A. M. Maiden
2004 A conf
Conference on Designing Interactive Systems
Neil A. M. Maiden, Sharon Manning, Suzanne Robertson, John Greenwood
2004 J jnl
Softwaretechnik-Trends
Norbert Seyff, Paul Grünbacher, Neil A. M. Maiden, Amit Tosar
2004 A conf
CAiSE
Neil A. M. Maiden, Sara Jones, Sharon Manning, John Greenwood, L. Renou
2004 J jnl
IEEE Softw.
Neil A. M. Maiden, Alexis Gizikis, Suzanne Robertson
2004 A* conf
ICSE
Norbert Seyff, Paul Grünbacher, Neil A. M. Maiden, Amit Tosar
2004 C conf
WETICE
Neil A. M. Maiden, Norbert Seyff, Paul Grünbacher
2003 A conf
RE
Alistair Mavin, Neil A. M. Maiden
2003 A conf
RE
Neil A. M. Maiden, Sara Jones, Mary Flynn
2003 conf
ICCBSS
Xavier Franch, Neil A. M. Maiden
2003 conf
Component-Based Software Quality
Neil A. M. Maiden, V. Croce, Kim Harries, Giuseppe Sajeva, S. Topuzidou
2003 conf
STRAW
Pete Pavan, Neil A. M. Maiden, Xiaohong Zhu
2002 conf
ICCBSS
Neil A. M. Maiden, Kim Harries, Cornelius Ncube
2001 J jnl
ACM SIGSOFT Softw. Eng. Notes
John C. Dean, Patricia A. Oberndorf, Mark R. Vigder, Chris Abts, Hakan Erdogmus, Neil A. M. Maiden, Michael Looney, George T. Heineman, Michael Guntersdorfer
2001 J jnl
Inf. Syst.
Marina Krumbholz, Neil A. M. Maiden
2001 J jnl
IEEE Softw.
Neil A. M. Maiden, Alexis Gizikis
2000 A conf
CAiSE
Marina Krumbholz, Neil A. M. Maiden
2000 J jnl
J. Inf. Technol.
Marina Krumbholz, Julia Galliers, N. Coulianos, Neil A. M. Maiden
2000 J jnl
Expert Syst. J. Knowl. Eng.
Gordon Rugg, Peter McGeorge, Neil A. M. Maiden
1999 J jnl
Interact. Comput.
Neil A. M. Maiden, Shailey Minocha, Alistair G. Sutcliffe, Darrel Manuel, Michele Ryan
1999 A conf
RE
Cornelius Ncube, Neil A. M. Maiden
1999 conf
DEXA Workshop
Neil A. M. Maiden, Gordon Rugg, P. Patel
1999 A conf
RE
Camille Ben Achour, Colette Rolland, Carine Souveyet, Neil A. M. Maiden
1999 J jnl
Interact. Comput.
Kulwinder Kaur, Neil A. M. Maiden, Alistair G. Sutcliffe
1999 B conf
INTERACT
Kulwinder Kaur Deol, Alistair G. Sutcliffe, Neil A. M. Maiden
1998 J jnl
Requir. Eng.
Colette Rolland, Camille Ben Achour, Corine Cauvet, Jolita Ralyté, Alistair G. Sutcliffe, Neil A. M. Maiden, Matthias Jarke, Peter Haumer, Klaus Pohl, Eric Dubois, Patrick Heymans
1998 conf
ICRE
Neil A. M. Maiden, Cornelius Ncube
1998 J jnl
IEEE Softw.
Neil A. M. Maiden, Cornelius Ncube
1998 B conf
REFSQ
Neil A. M. Maiden, M. Cisse, H. Perez, Darrel Manuel
1998 J jnl
Autom. Softw. Eng.
Neil A. M. Maiden
1998 conf
ICRE
Neil A. M. Maiden, Shailey Minocha, K. Manning, Michele Ryan
1998 J jnl
Int. J. Hum. Comput. Stud.
Neil A. M. Maiden, Matthew P. Hare
1998 J jnl
IEEE Trans. Software Eng.
Alistair G. Sutcliffe, Neil A. M. Maiden, Shailey Minocha, Darrel Manuel
1998 J jnl
IEEE Trans. Software Eng.
Alistair G. Sutcliffe, Neil A. M. Maiden
1997 J jnl
Commun. ACM
Neil A. M. Maiden, Cornelius Ncube, Andrew Moore
1996 conf
IWSSD
Neil A. M. Maiden, Alistair G. Sutcliffe
1996 J jnl
Softw. Eng. J.
Neil A. M. Maiden, Gordon Rugg
1996 J jnl
Softw. Eng. J.
Neil A. M. Maiden, Alistair G. Sutcliffe
1996 J jnl
Requir. Eng.
Neil A. M. Maiden, George Spanoudakis, Hans W. Nissen
1996 C conf
WETICE
Neil A. M. Maiden, B. P. Bright
1996 conf
EWCBR
Neil A. M. Maiden
1995 C conf
SEKE
Neil A. M. Maiden, Alistair G. Sutcliffe, Petia Assenova, P. Johanneson, Matthias Jarke, Hans W. Nissen, Panos Constantopoulos, George Spanoudakis
1995 A conf
RE
Neil A. M. Maiden, P. Mistry, Alistair G. Sutcliffe
1995 J jnl
ACM SIGSOFT Softw. Eng. Notes
Neil A. M. Maiden
1994 B conf
ICSR
Alistair G. Sutcliffe, Neil A. M. Maiden
1994 conf
ICRE
Neil A. M. Maiden, Alistair G. Sutcliffe
1993 conf
IWSSD
Alistair G. Sutcliffe, Neil A. M. Maiden
1993 conf
IWSR
Neil A. M. Maiden, Alistair G. Sutcliffe
1993 conf
ESEC
Matthias Jarke, Klaus Pohl, Stephan Jacobs, Janis A. Bubenko Jr., Petia Assenova, Peter Holm, Benkt Wangler, Colette Rolland, Véronique Plihon, Jean-Roch Schmitt, Alistair G. Sutcliffe, Sara Jones, Neil A. M. Maiden, David Till, Yannis Vassiliou, Panos Constantopoulos, George Spanoudakis
1993 A conf
RE
Neil A. M. Maiden, Alistair G. Sutcliffe
1993 conf
Information System Development Process
Alistair G. Sutcliffe, Neil A. M. Maiden
1992 J jnl
Int. J. Man Mach. Stud.
Alistair G. Sutcliffe, Neil A. M. Maiden
1992 conf
KBSE
Neil A. M. Maiden, Alistair G. Sutcliffe
1992 J jnl
Commun. ACM
Neil A. M. Maiden, Alistair G. Sutcliffe
1992 A conf
CAiSE
Alistair G. Sutcliffe, Neil A. M. Maiden
1991 conf
KBSE
Neil A. M. Maiden, Alistair G. Sutcliffe
1991 J jnl
Softw. Eng. J.
Neil A. M. Maiden
1991 conf
IWSSD
Neil A. M. Maiden, Alistair G. Sutcliffe
1990 B conf
INTERACT
Alistair G. Sutcliffe, Neil A. M. Maiden
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'
        }