rusthound_ce/objects/
enterpriseca.rs

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