Skip to main content

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