rusthound_ce/objects/
enterpriseca.rs

1use colored::Colorize;
2use serde::{Deserialize, Serialize};
3use serde_json::value::Value;
4use x509_parser::oid_registry::asn1_rs::oid;
5use x509_parser::prelude::*;
6use ldap3::SearchEntry;
7use log::{debug, error, info, trace};
8use std::collections::HashMap;
9use std::error::Error;
10
11use crate::enums::{
12    MaskFlags, SecurityDescriptor, AceFormat, Acl,
13    decode_guid_le, parse_ntsecuritydescriptor, sid_maker, parse_ca_security
14};
15use crate::json::checker::common::get_name_from_full_distinguishedname;
16use crate::objects::common::{LdapObject, AceTemplate, SPNTarget, Link, Member};
17use crate::utils::crypto::calculate_sha1;
18use crate::utils::date::string_to_epoch;
19
20/// EnterpriseCA structure
21#[derive(Debug, Clone, Deserialize, Serialize, Default)]
22pub struct EnterpriseCA {
23    #[serde(rename = "Properties")]
24    properties: EnterpriseCAProperties,
25    #[serde(rename = "HostingComputer")]
26    hosting_computer: String,
27    #[serde(rename = "CARegistryData")]
28    ca_registry_data: CARegistryData,
29    #[serde(rename = "EnabledCertTemplates")]
30    enabled_cert_templates: Vec<Member>,
31    #[serde(rename = "Aces")]
32    aces: Vec<AceTemplate>,
33    #[serde(rename = "ObjectIdentifier")]
34    object_identifier: String,
35    #[serde(rename = "IsDeleted")]
36    is_deleted: bool,
37    #[serde(rename = "IsACLProtected")]
38    is_acl_protected: bool,
39    #[serde(rename = "ContainedBy")]
40    contained_by: Option<Member>,
41}
42
43impl EnterpriseCA {
44    // New EnterpriseCA
45    pub fn new() -> Self { 
46        Self { ..Default::default() } 
47    }
48
49    // Immutable access.
50    pub fn enabled_cert_templates(&self) -> &Vec<Member> {
51        &self.enabled_cert_templates
52    }
53
54    // Mutable access.
55    pub fn enabled_cert_templates_mut(&mut self) -> &mut Vec<Member> {
56        &mut self.enabled_cert_templates
57    }
58
59    /// Function to parse and replace value in json template for Enterprise CA object.
60    pub fn parse(
61        &mut self,
62        result: SearchEntry,
63        domain: &str,
64        dn_sid: &mut HashMap<String, String>,
65        sid_type: &mut HashMap<String, String>,
66        domain_sid: &str,
67    ) -> Result<(), Box<dyn Error>> {
68        let result_dn: String = result.dn.to_uppercase();
69        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
70        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
71
72        // Debug for current object
73        debug!("Parse EnterpriseCA: {result_dn}");
74
75        // Trace all result attributes
76        for (key, value) in &result_attrs {
77            trace!("  {key:?}:{value:?}");
78        }
79        // Trace all bin result attributes
80        for (key, value) in &result_bin {
81            trace!("  {key:?}:{value:?}");
82        }
83
84        // Change all values...
85        self.properties.domain = domain.to_uppercase();
86        self.properties.distinguishedname = result_dn;
87        self.properties.domainsid = domain_sid.to_string();
88        let ca_name = get_name_from_full_distinguishedname(&self.properties.distinguishedname);
89        self.properties.caname = ca_name;
90
91        // With a check
92        for (key, value) in &result_attrs {
93            match key.as_str() {
94                "name" => {
95                    let name = format!("{}@{}", &value[0], domain);
96                    self.properties.name = name.to_uppercase();
97                }
98                "description" => {
99                    self.properties.description = Some(value[0].to_owned());
100                }
101                "dNSHostName" => {
102                    self.properties.dnshostname = value[0].to_owned();
103                }
104                "certificateTemplates" => {
105                    if value.is_empty() {
106                        error!("No certificate templates enabled for {}", self.properties.caname);
107                    } else {
108                        //ca.enabled_templates = value.to_vec();
109                        info!("Found {} enabled certificate templates", value.len().to_string().bold());
110                        trace!("Enabled certificate templates: {:?}", value);
111                        let enabled_templates: Vec<Member> = value.iter().map(|template_name| {
112                            let mut member = Member::new();
113                            *member.object_identifier_mut() = template_name.to_owned();
114                            *member.object_type_mut() = String::from("CertTemplate");
115
116                            member
117                        }).collect();
118                        self.enabled_cert_templates = enabled_templates;
119                    }
120                }
121                "whenCreated" => {
122                    let epoch = string_to_epoch(&value[0])?;
123                    if epoch.is_positive() {
124                        self.properties.whencreated = epoch;
125                    }
126                }
127                "IsDeleted" => {
128                    self.is_deleted = true;
129                }
130                _ => {}
131            }
132        }
133
134        // For all, bins attributs
135        for (key, value) in &result_bin {
136            match key.as_str() {
137                "objectGUID" => {
138                    // objectGUID raw to string
139                    let guid = decode_guid_le(&value[0]);
140                    self.object_identifier = guid.to_owned();
141                }
142                "nTSecurityDescriptor" => {
143                    // nTSecurityDescriptor raw to string
144                    let relations_ace = parse_ntsecuritydescriptor(
145                        self,
146                        &value[0],
147                        "EnterpriseCA",
148                        &result_attrs,
149                        &result_bin,
150                        domain,
151                    );
152                    // Aces
153                    self.aces = relations_ace;
154                    // HostingComputer
155                    self.hosting_computer = Self::get_hosting_computer(&value[0], domain);
156                    // CASecurity
157                    let ca_security_data = parse_ca_security(&value[0], &self.hosting_computer, domain);
158                    if !ca_security_data.is_empty() {
159                        let ca_security = CASecurity {
160                            data: ca_security_data,
161                            collected: true,
162                            failure_reason: None,
163                        };
164                        self.properties.casecuritycollected = true;
165                        let ca_registry_data = CARegistryData::new(ca_security);
166                        self.ca_registry_data = ca_registry_data;
167                    } else {
168                        let ca_security = CASecurity {
169                            data: Vec::new(),
170                            collected: false,
171                            failure_reason: Some(String::from("Failed to get CASecurity!"))
172                        };
173                        self.properties.casecuritycollected = false;
174                        let ca_registry_data = CARegistryData::new(ca_security);
175                        self.ca_registry_data = ca_registry_data;
176                    }
177                }
178                "cACertificate" => {
179                    //info!("{:?}:{:?}", key,value[0].to_owned());
180                    let certsha1: String = calculate_sha1(&value[0]);
181                    self.properties.certthumbprint = certsha1.to_owned();
182                    self.properties.certname = certsha1.to_owned();
183                    self.properties.certchain = vec![certsha1.to_owned()];
184
185                    // Parsing certificate.
186                    let res = X509Certificate::from_der(&value[0]);
187                    match res {
188                        Ok((_rem, cert)) => {
189                            // println!("Basic Constraints Extensions:");
190                            for ext in cert.extensions() {
191                                // println!("{:?} : {:?}",&ext.oid, ext);
192                                if &ext.oid == &oid!(2.5.29.19) {
193                                    // <https://docs.rs/x509-parser/latest/x509_parser/extensions/struct.BasicConstraints.html>
194                                    if let ParsedExtension::BasicConstraints(basic_constraints) = &ext.parsed_extension() {
195                                        let _ca = &basic_constraints.ca;
196                                        let _path_len_constraint = &basic_constraints.path_len_constraint;
197                                        // println!("ca: {:?}", _ca);
198                                        // println!("path_len_constraint: {:?}", _path_len_constraint);
199                                        match _path_len_constraint {
200                                            Some(_path_len_constraint) => {
201                                                if _path_len_constraint > &0 {
202                                                    self.properties.hasbasicconstraints = true;
203                                                    self.properties.basicconstraintpathlength = _path_len_constraint.to_owned();
204
205                                                } else {
206                                                    self.properties.hasbasicconstraints = false;
207                                                    self.properties.basicconstraintpathlength = 0;
208                                                }
209                                            }
210                                            None => {
211                                                self.properties.hasbasicconstraints = false;
212                                                self.properties.basicconstraintpathlength = 0;
213                                            }
214                                        }
215                                    }
216                                }
217                            }
218                        },
219                        _ => error!("CA x509 certificate parsing failed: {:?}", res),
220                    }
221                }
222                _ => {}
223            }
224        }
225
226        // Push DN and SID in HashMap
227        if self.object_identifier != "SID" {
228            dn_sid.insert(
229                self.properties.distinguishedname.to_string(),
230                self.object_identifier.to_string(),
231            );
232            // Push DN and Type
233            sid_type.insert(
234                self.object_identifier.to_string(),
235                "EnterpriseCA".to_string(),
236            );
237        }
238
239        // Trace and return EnterpriseCA struct
240        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
241        Ok(())
242    }
243
244    /// Function to get HostingComputer from ACL if ACE get ManageCertificates and is not Group.
245    fn get_hosting_computer(
246        nt: &[u8],
247        domain: &str,
248    ) -> String {
249        let mut hosting_computer = String::from("Not found");
250        let blacklist_sid = [
251            // <https://learn.microsoft.com/fr-fr/windows-server/identity/ad-ds/manage/understand-security-identifiers>
252            "-544", // Administrators
253            "-519", // Enterprise Administrators
254            "-512", // Domain Admins
255        ];
256        let secdesc: SecurityDescriptor = SecurityDescriptor::parse(nt).unwrap().1;
257        if secdesc.offset_dacl as usize != 0 
258        {
259            let res = Acl::parse(&nt[secdesc.offset_dacl as usize..]);
260            match res {
261                Ok(_res) => {
262                    let dacl = _res.1;
263                    let aces = dacl.data;
264                    for ace in aces {
265                        if ace.ace_type == 0x00 {
266                            let sid = sid_maker(AceFormat::get_sid(ace.data.to_owned()).unwrap(), domain);
267                            let mask = match AceFormat::get_mask(&ace.data) {
268                                Some(mask) => mask,
269                                None => continue,
270                            };
271                            if (MaskFlags::MANAGE_CERTIFICATES.bits() | mask) == mask
272                            && !blacklist_sid.iter().any(|blacklisted| sid.ends_with(blacklisted)) 
273                            {
274                                // println!("SID MANAGE_CERTIFICATES: {:?}",&sid);
275                                hosting_computer = sid;
276                                return hosting_computer
277                            }
278                        }
279                    }
280                },
281                Err(err) => error!("Error. Reason: {err}")
282            }
283        }
284        hosting_computer
285    }
286}
287
288impl LdapObject for EnterpriseCA {
289    // To JSON
290    fn to_json(&self) -> Value {
291        serde_json::to_value(self).unwrap()
292    }
293
294    // Get values
295    fn get_object_identifier(&self) -> &String {
296        &self.object_identifier
297    }
298    fn get_is_acl_protected(&self) -> &bool {
299        &self.is_acl_protected
300    }
301    fn get_aces(&self) -> &Vec<AceTemplate> {
302        &self.aces
303    }
304    fn get_spntargets(&self) -> &Vec<SPNTarget> {
305        panic!("Not used by current object.");
306    }
307    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
308        panic!("Not used by current object.");
309    }
310    fn get_links(&self) -> &Vec<Link> {
311        panic!("Not used by current object.");
312    }
313    fn get_contained_by(&self) -> &Option<Member> {
314        &self.contained_by
315    }
316    fn get_child_objects(&self) -> &Vec<Member> {
317        panic!("Not used by current object.");
318    }
319    fn get_haslaps(&self) -> &bool {
320        &false
321    }
322
323    // Get mutable values
324    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
325        &mut self.aces
326    }
327    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
328        panic!("Not used by current object.");
329    }
330    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
331        panic!("Not used by current object.");
332    }
333
334    // Edit values
335    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
336        self.is_acl_protected = is_acl_protected;
337        self.properties.isaclprotected = is_acl_protected;
338    }
339    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
340        self.aces = aces;
341    }
342    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
343        // Not used by current object.
344    }
345    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
346        // Not used by current object.
347    }
348    fn set_links(&mut self, _links: Vec<Link>) {
349        // Not used by current object.
350    }
351    fn set_contained_by(&mut self, contained_by: Option<Member>) {
352        self.contained_by = contained_by;
353    }
354    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
355        // Not used by current object.
356    }
357}
358
359
360// EnterpriseCA properties structure
361#[derive(Debug, Clone, Deserialize, Serialize)]
362pub struct EnterpriseCAProperties {
363    domain: String,
364    name: String,
365    distinguishedname: String,
366    domainsid: String,
367    isaclprotected: bool,
368    description: Option<String>,
369    whencreated: i64,
370    flags: String,
371    caname: String,
372    dnshostname: String,
373    certthumbprint: String,
374    certname: String,
375    certchain: Vec<String>,
376    hasbasicconstraints: bool,
377    basicconstraintpathlength: u32,
378    unresolvedpublishedtemplates: Vec<String>,
379    casecuritycollected: bool,
380    enrollmentagentrestrictionscollected: bool,
381    isuserspecifiessanenabledcollected: bool,
382    roleseparationenabledcollected: bool,
383}
384
385impl Default for EnterpriseCAProperties {
386    fn default() -> EnterpriseCAProperties {
387        EnterpriseCAProperties {
388            domain: String::from(""),
389            name: String::from(""),
390            distinguishedname: String::from(""),
391            domainsid: String::from(""),
392            isaclprotected: false,
393            description: None,
394            whencreated: -1,
395            flags: String::from(""),
396            caname: String::from(""),
397            dnshostname: String::from(""),
398            certthumbprint: String::from(""),
399            certname: String::from(""),
400            certchain: Vec::new(),
401            hasbasicconstraints: false,
402            basicconstraintpathlength: 0,
403            unresolvedpublishedtemplates: Vec::new(),
404            casecuritycollected: false,
405            enrollmentagentrestrictionscollected: false,
406            isuserspecifiessanenabledcollected: false,
407            roleseparationenabledcollected: false,
408       }
409    }
410 }
411
412// CARegistryData properties structure
413#[derive(Debug, Clone, Deserialize, Serialize, Default)]
414pub struct CARegistryData {
415    #[serde(rename = "CASecurity")]
416    ca_security: CASecurity,
417    #[serde(rename = "EnrollmentAgentRestrictions")]
418    enrollment_agent_restrictions: EnrollmentAgentRestrictions,
419    #[serde(rename = "IsUserSpecifiesSanEnabled")]
420    is_user_specifies_san_enabled: IsUserSpecifiesSanEnabled,
421    #[serde(rename = "RoleSeparationEnabled")]
422    role_separation_enabled: RoleSeparationEnabled,
423}
424
425impl CARegistryData {
426    pub fn new(
427        ca_security: CASecurity,
428    ) -> Self { 
429        Self { 
430            ca_security,
431            ..Default::default()
432        }
433    }
434}
435
436// CASecurity properties structure
437#[derive(Debug, Clone, Deserialize, Serialize)]
438pub struct CASecurity {
439    #[serde(rename = "Data")]
440    data: Vec<AceTemplate>,
441    #[serde(rename = "Collected")]
442    collected: bool,
443    #[serde(rename = "FailureReason")]
444    failure_reason: Option<String>,
445}
446
447
448impl Default for CASecurity {
449    fn default() -> CASecurity {
450        CASecurity {
451            data: Vec::new(),
452            collected: true,
453            failure_reason: None,
454        }
455    }
456}
457
458// EnrollmentAgentRestrictions properties structure
459#[derive(Debug, Clone, Deserialize, Serialize)]
460pub struct EnrollmentAgentRestrictions {
461    #[serde(rename = "Restrictions")]
462    restrictions: Vec<String>, // data to validate
463    #[serde(rename = "Collected")]
464    collected: bool,
465    #[serde(rename = "FailureReason")]
466    failure_reason: Option<String>,
467}
468
469impl Default for EnrollmentAgentRestrictions {
470    fn default() -> EnrollmentAgentRestrictions {
471        EnrollmentAgentRestrictions {
472            restrictions: Vec::new(),
473            collected: true,
474            failure_reason: None,
475        }
476    }
477}
478
479// IsUserSpecifiesSanEnabled properties structure
480#[derive(Debug, Clone, Deserialize, Serialize)]
481pub struct IsUserSpecifiesSanEnabled {
482    #[serde(rename = "Value")]
483    value: bool,
484    #[serde(rename = "Collected")]
485    collected: bool,
486    #[serde(rename = "FailureReason")]
487    failure_reason: Option<String>,
488}
489
490impl Default for IsUserSpecifiesSanEnabled {
491    fn default() -> IsUserSpecifiesSanEnabled {
492        IsUserSpecifiesSanEnabled {
493            value: false,
494            collected: true,
495            failure_reason: None,
496        }
497    }
498}
499
500// RoleSeparationEnabled properties structure
501#[derive(Debug, Clone, Deserialize, Serialize)]
502pub struct RoleSeparationEnabled {
503    #[serde(rename = "Value")]
504    value: bool,
505    #[serde(rename = "Collected")]
506    collected: bool,
507    #[serde(rename = "FailureReason")]
508    failure_reason: Option<String>,
509}
510
511impl Default for RoleSeparationEnabled {
512    fn default() -> RoleSeparationEnabled {
513        RoleSeparationEnabled {
514            value: false,
515            collected: true,
516            failure_reason: None,
517        }
518    }
519}