Skip to main content

rusthound_ce/objects/
certtemplate.rs

1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use ldap3::SearchEntry;
4use log::{debug, trace};
5use std::collections::HashMap;
6use std::error::Error;
7
8use crate::objects::common::{LdapObject, AceTemplate, SPNTarget, Link, Member};
9use crate::enums::{decode_guid_le, get_pki_cert_name_flags, get_pki_enrollment_flags, parse_ntsecuritydescriptor};
10use crate::json::checker::common::get_name_from_full_distinguishedname;
11use crate::utils::date::{filetime_to_span, span_to_string, string_to_epoch};
12
13/// CertTemplate structure
14#[derive(Debug, Clone, Deserialize, Serialize, Default)]
15pub struct CertTemplate {
16    #[serde(rename = "Properties")]
17    properties: CertTemplateProperties,
18    #[serde(rename = "Aces")]
19    aces: Vec<AceTemplate>,
20    #[serde(rename = "ObjectIdentifier")]
21    object_identifier: String,
22    #[serde(rename = "IsDeleted")]
23    is_deleted: bool,
24    #[serde(rename = "IsACLProtected")]
25    is_acl_protected: bool,
26    #[serde(rename = "ContainedBy")]
27    contained_by: Option<Member>,
28}
29
30impl CertTemplate {
31    // New CertTemplate
32    pub fn new() -> Self { 
33        Self { ..Default::default() } 
34    }
35
36    // Immutable access.
37    pub fn properties(&self) -> &CertTemplateProperties {
38        &self.properties
39    }
40    pub fn object_identifier(&self) -> &String {
41        &self.object_identifier
42    }
43
44    /// Function to parse and replace value in json template for Certificate Template object.
45    pub fn parse(
46        &mut self,
47        result: SearchEntry,
48        domain: &str,
49        dn_sid: &mut HashMap<String, String>,
50        sid_type: &mut HashMap<String, String>,
51        domain_sid: &str,
52        schema_guid_map: &HashMap<String, String>,
53    ) -> Result<(), Box<dyn Error>> {
54        let result_dn: String = result.dn.to_uppercase();
55        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
56        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
57
58        // Debug for current object
59        debug!("Parse CertTemplate: {result_dn}");
60
61        // Trace all result attributes
62        for (key, value) in &result_attrs {
63            trace!("  {key:?}:{value:?}");
64        }
65        // Trace all bin result attributes
66        for (key, value) in &result_bin {
67            trace!("  {key:?}:{value:?}");
68        }
69
70        // Change all values...
71        self.properties.domain = domain.to_uppercase();
72        self.properties.distinguishedname = result_dn;    
73        self.properties.domainsid = domain_sid.to_string();
74        let _ca_name = get_name_from_full_distinguishedname(&self.properties.distinguishedname);
75
76        // With a check
77        for (key, value) in &result_attrs {
78            match key.as_str() {
79                "name" => {
80                    let name = format!("{}@{}",&value[0],domain);
81                    self.properties.name = name.to_uppercase();
82                }
83                "description" => {
84                    self.properties.description = Some(value[0].to_owned());
85                }
86                "displayName" => {
87                    self.properties.displayname = value[0].to_owned();
88                }
89                "msPKI-Certificate-Name-Flag" => {
90                    if !value.is_empty() {
91                        self.properties.certificatenameflag = get_pki_cert_name_flags(value[0].parse::<i64>().unwrap_or(0) as u64);
92                        self.properties.enrolleesuppliessubject = self.properties.certificatenameflag.contains("ENROLLEE_SUPPLIES_SUBJECT");
93                        self.properties.subjectaltrequireupn = self.properties.certificatenameflag.contains("SUBJECT_ALT_REQUIRE_UPN");
94                        self.properties.subjectaltrequiredns = self.properties.certificatenameflag.contains("SUBJECT_ALT_REQUIRE_DNS");
95                        self.properties.subjectaltrequiredomaindns = self.properties.certificatenameflag.contains("SUBJECT_ALT_REQUIRE_DOMAIN_DNS");
96                        self.properties.subjectaltrequireemail = self.properties.certificatenameflag.contains("SUBJECT_ALT_REQUIRE_EMAIL");
97                        self.properties.subjectaltrequirespn = self.properties.certificatenameflag.contains("SUBJECT_ALT_REQUIRE_SPN");
98                        self.properties.subjectrequireemail = self.properties.certificatenameflag.contains("SUBJECT_REQUIRE_EMAIL");
99                    }
100                }
101                "msPKI-Enrollment-Flag" => {
102                    if !value.is_empty() {
103                        self.properties.enrollmentflag = get_pki_enrollment_flags(value[0].parse::<i64>().unwrap_or(0) as u64);
104                        self.properties.requiresmanagerapproval = self.properties.enrollmentflag.contains("PEND_ALL_REQUESTS");
105                        self.properties.nosecurityextension = self.properties.enrollmentflag.contains("NO_SECURITY_EXTENSION");
106                    }
107                }
108                "msPKI-Private-Key-Flag" => {
109                    // if !value.is_empty() {
110                    //     self.properties.() = get_pki_private_flags(value[0].parse::<i64>().unwrap_or(0) as u64);
111                    // }
112                }
113                "msPKI-RA-Signature" => {
114                    if !value.is_empty() {
115                        self.properties.authorizedsignatures = value.first().unwrap_or(&"0".to_string()).parse::<i64>().unwrap_or(0);
116                    }
117                }
118                "msPKI-RA-Application-Policies" => {
119                    if !value.is_empty() {
120                        self.properties.applicationpolicies = value.to_owned();
121                    }
122                }
123                "msPKI-Certificate-Application-Policy" => {
124                    if !value.is_empty() {
125                        self.properties.certificateapplicationpolicy = value.to_owned();
126                    }
127                }
128                "msPKI-RA-Policies" => {
129                    if !value.is_empty() {
130                        self.properties.issuancepolicies = value.to_owned();
131                    }
132                }
133                "msPKI-Cert-Template-OID" => {
134                    if !value.is_empty() {
135                        self.properties.oid = value[0].to_owned();
136                    }
137                }
138                "pKIExtendedKeyUsage" => {
139                    if !value.is_empty() {
140                        self.properties.ekus = value.to_owned();
141                    }
142                }
143                "msPKI-Template-Schema-Version" => {
144                    self.properties.schemaversion = value[0].parse::<i64>().unwrap_or(0);
145                }
146                "whenCreated" => {
147                    let epoch = string_to_epoch(&value[0])?;
148                    if epoch.is_positive() {
149                        self.properties.whencreated = epoch;
150                    }
151                }
152                "isDeleted" => {
153                    self.is_deleted = true;
154                }
155                _ => {}
156            }
157        }
158
159        // For all, bins attributs
160        for (key, value) in &result_bin {
161            match key.as_str() {
162                "objectGUID" => {
163                    // objectGUID raw to string
164                    let guid = decode_guid_le(&value[0]);
165                    self.object_identifier = guid.to_owned();
166                    self.properties.objectguid = guid;
167                }
168                "nTSecurityDescriptor" => {
169                    // nTSecurityDescriptor raw to string
170                    let relations_ace =  parse_ntsecuritydescriptor(
171                        self,
172                        &value[0],
173                        "CertTemplate",
174                        &result_attrs,
175                        &result_bin,
176                        domain,
177                        schema_guid_map,
178                    );
179                    self.aces = relations_ace;
180                }
181                "pKIExpirationPeriod" => {
182                    self.properties.validityperiod = span_to_string(filetime_to_span(value[0].to_owned())?);
183                }
184                "pKIOverlapPeriod" => {
185                    self.properties.renewalperiod = span_to_string(filetime_to_span(value[0].to_owned())?);
186                }
187                _ => {}
188            }
189        }
190
191        // Get all effective ekus.
192        self.properties.effectiveekus = Self::get_effectiveekus(
193            &self.properties.schemaversion,
194            &self.properties.ekus,
195            &self.properties.certificateapplicationpolicy,
196        );
197
198        // Check if authentication is enabled or not for this template.
199        self.properties.authenticationenabled = Self::authentication_is_enabled(self);
200
201        // Push DN and SID in HashMap
202        if self.object_identifier != "SID" {
203            dn_sid.insert(
204                self.properties.distinguishedname.to_string(),
205                self.object_identifier.to_string()
206            );
207            // Push DN and Type
208            sid_type.insert(
209                self.object_identifier.to_string(),
210                "CertTemplate".to_string()
211            );
212        }
213
214        // Trace and return CertTemplate struct
215        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
216        Ok(())
217    }
218
219    /// Function to get effective ekus for one template.
220    fn get_effectiveekus(
221        schema_version: &i64,
222        ekus: &[String],
223        certificateapplicationpolicy: &[String],
224    ) -> Vec<String> {
225        if schema_version == &1 && !ekus.is_empty() {
226            ekus.to_vec()
227        } else {
228            certificateapplicationpolicy.to_vec()
229        }
230    }
231
232    /// Function to check if authentication is enabled or not.
233    fn authentication_is_enabled(&mut self) -> bool {
234        let authentication_oids = [
235            "1.3.6.1.5.5.7.3.2", // ClientAuthentication,
236            "1.3.6.1.5.2.3.4", // PKINITClientAuthentication
237            "1.3.6.1.4.1.311.20.2.2", // SmartcardLogon
238            "2.5.29.37.0", // AnyPurpose
239        ];
240        self.properties.effectiveekus.iter()
241            .any(|eku| authentication_oids.contains(&eku.as_str()))
242            || self.properties.effectiveekus.is_empty()
243    }
244}
245
246impl LdapObject for CertTemplate {
247    // To JSON
248    fn to_json(&self) -> Value {
249        serde_json::to_value(self).unwrap()
250    }
251
252    // Get values
253    fn get_object_identifier(&self) -> &String {
254        &self.object_identifier
255    }
256    fn get_is_acl_protected(&self) -> &bool {
257        &self.is_acl_protected
258    }
259    fn get_aces(&self) -> &Vec<AceTemplate> {
260        &self.aces
261    }
262    fn get_spntargets(&self) -> &Vec<SPNTarget> {
263        panic!("Not used by current object.");
264    }
265    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
266        panic!("Not used by current object.");
267    }
268    fn get_links(&self) -> &Vec<Link> {
269        panic!("Not used by current object.");
270    }
271    fn get_contained_by(&self) -> &Option<Member> {
272        &self.contained_by
273    }
274    fn get_child_objects(&self) -> &Vec<Member> {
275        panic!("Not used by current object.");
276    }
277    fn get_haslaps(&self) -> &bool {
278        &false
279    }
280    
281    // Get mutable values
282    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
283        &mut self.aces
284    }
285    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
286        panic!("Not used by current object.");
287    }
288    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
289        panic!("Not used by current object.");
290    }
291    
292    // Edit values
293    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
294        self.is_acl_protected = is_acl_protected;
295        self.properties.isaclprotected = is_acl_protected;
296    }
297    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
298        self.aces = aces;
299    }
300    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
301        // Not used by current object.
302    }
303    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
304        // Not used by current object.
305    }
306    fn set_links(&mut self, _links: Vec<Link>) {
307        // Not used by current object.
308    }
309    fn set_contained_by(&mut self, contained_by: Option<Member>) {
310        self.contained_by = contained_by;
311    }
312    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
313        // Not used by current object.
314    }
315    fn set_owner_rights_flags(&mut self, any: bool, any_inherited: bool) {
316        self.properties.doesanyacegrantownerrights = any;
317        self.properties.doesanyinheritedacegrantownerrights = any_inherited;
318    }
319}
320
321
322// CertTemplate properties structure
323#[derive(Debug, Clone, Deserialize, Serialize)]
324pub struct CertTemplateProperties {
325    domain: String,
326    name: String,
327    distinguishedname: String,
328    domainsid: String,
329    objectguid: String,
330    doesanyacegrantownerrights: bool,
331    doesanyinheritedacegrantownerrights: bool,
332    isaclprotected: bool,
333    description: Option<String>,
334    whencreated: i64,
335    validityperiod: String,
336    renewalperiod: String,
337    schemaversion: i64,
338    displayname: String,
339    oid: String,
340    enrollmentflag: String,
341    requiresmanagerapproval: bool,
342    nosecurityextension: bool,
343    certificatenameflag: String,
344    enrolleesuppliessubject: bool,
345    subjectaltrequireupn: bool,
346    subjectaltrequiredns: bool,
347    subjectaltrequiredomaindns: bool,
348    subjectaltrequireemail: bool,
349    subjectaltrequirespn: bool,
350    subjectrequireemail: bool,
351    ekus: Vec<String>,
352    certificateapplicationpolicy: Vec<String>,
353    authorizedsignatures: i64,
354    applicationpolicies: Vec<String>,
355    issuancepolicies: Vec<String>,
356    effectiveekus: Vec<String>,
357    authenticationenabled: bool,
358}
359
360impl Default for CertTemplateProperties {
361    fn default() -> CertTemplateProperties {
362        CertTemplateProperties {
363            domain: String::from(""),
364            name: String::from(""),
365            distinguishedname: String::from(""),
366            domainsid: String::from(""),
367            objectguid: String::from(""),
368            doesanyacegrantownerrights: false,
369            doesanyinheritedacegrantownerrights: false,
370            isaclprotected: false,
371            description: None,
372            whencreated: -1,
373            validityperiod: String::from(""),
374            renewalperiod: String::from(""),
375            schemaversion: 1,
376            displayname: String::from(""),
377            oid: String::from(""),
378            enrollmentflag: String::from(""),
379            requiresmanagerapproval: false,
380            nosecurityextension: false,
381            certificatenameflag: String::from(""),
382            enrolleesuppliessubject: false,
383            subjectaltrequireupn: false,
384            subjectaltrequiredns: false,
385            subjectaltrequiredomaindns: false,
386            subjectaltrequireemail: false,
387            subjectaltrequirespn: false,
388            subjectrequireemail: false,
389            ekus: Vec::new(),
390            certificateapplicationpolicy: Vec::new(),
391            authorizedsignatures: 0,
392            applicationpolicies: Vec::new(),
393            issuancepolicies: Vec::new(),
394            effectiveekus: Vec::new(),
395            authenticationenabled: false,
396       }
397    }
398 }
399
400impl CertTemplateProperties {
401    // Immutable access.
402    pub fn name(&self) -> &String {
403        &self.name
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410
411    const SUBJECT_NAME_FLAG_PROPERTIES: [&str; 6] = [
412        "subjectaltrequiredomaindns",
413        "subjectaltrequirespn",
414        "subjectaltrequireupn",
415        "subjectaltrequireemail",
416        "subjectaltrequiredns",
417        "subjectrequireemail",
418    ];
419
420    fn parse_certtemplate_with_name_flag(flag: Option<i64>) -> CertTemplate {
421        let mut attrs = HashMap::new();
422        attrs.insert("name".to_string(), vec!["RustHoundLab".to_string()]);
423        if let Some(flag) = flag {
424            attrs.insert(
425                "msPKI-Certificate-Name-Flag".to_string(),
426                vec![flag.to_string()],
427            );
428        }
429
430        let result = SearchEntry {
431            dn: "CN=RustHoundLab,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=local".to_string(),
432            attrs,
433            bin_attrs: HashMap::new(),
434        };
435        let mut certtemplate = CertTemplate::new();
436        let mut dn_sid = HashMap::new();
437        let mut sid_type = HashMap::new();
438
439        certtemplate
440            .parse(
441                result,
442                "example.local",
443                &mut dn_sid,
444                &mut sid_type,
445                "S-1-5-21-1-2-3",
446                &HashMap::new(),
447            )
448            .unwrap();
449
450        certtemplate
451    }
452
453    #[test]
454    fn parse_maps_each_subject_name_flag_to_its_boolean_property() {
455        let cases = [
456            (0x0040_0000, "subjectaltrequiredomaindns"),
457            (0x0080_0000, "subjectaltrequirespn"),
458            (0x0200_0000, "subjectaltrequireupn"),
459            (0x0400_0000, "subjectaltrequireemail"),
460            (0x0800_0000, "subjectaltrequiredns"),
461            (0x2000_0000, "subjectrequireemail"),
462        ];
463
464        for (flag, expected_property) in cases {
465            let certtemplate = parse_certtemplate_with_name_flag(Some(flag));
466            let properties = &certtemplate.to_json()["Properties"];
467
468            for property in SUBJECT_NAME_FLAG_PROPERTIES {
469                assert_eq!(
470                    properties[property],
471                    property == expected_property,
472                    "unexpected value for {property} with flag {flag:#010x}",
473                );
474            }
475        }
476    }
477
478    #[test]
479    fn subject_name_flag_properties_default_to_false_when_attribute_is_absent() {
480        let certtemplate = parse_certtemplate_with_name_flag(None);
481        let properties = &certtemplate.to_json()["Properties"];
482
483        for property in SUBJECT_NAME_FLAG_PROPERTIES {
484            assert_eq!(properties[property], false, "{property} should default to false");
485        }
486    }
487}