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                }
167                "nTSecurityDescriptor" => {
168                    // nTSecurityDescriptor raw to string
169                    let relations_ace =  parse_ntsecuritydescriptor(
170                        self,
171                        &value[0],
172                        "CertTemplate",
173                        &result_attrs,
174                        &result_bin,
175                        domain,
176                        schema_guid_map,
177                    );
178                    self.aces = relations_ace;
179                }
180                "pKIExpirationPeriod" => {
181                    self.properties.validityperiod = span_to_string(filetime_to_span(value[0].to_owned())?);
182                }
183                "pKIOverlapPeriod" => {
184                    self.properties.renewalperiod = span_to_string(filetime_to_span(value[0].to_owned())?);
185                }
186                _ => {}
187            }
188        }
189
190        // Get all effective ekus.
191        self.properties.effectiveekus = Self::get_effectiveekus(
192            &self.properties.schemaversion,
193            &self.properties.ekus,
194            &self.properties.certificateapplicationpolicy,
195        );
196
197        // Check if authentication is enabled or not for this template.
198        self.properties.authenticationenabled = Self::authentication_is_enabled(self);
199
200        // Push DN and SID in HashMap
201        if self.object_identifier != "SID" {
202            dn_sid.insert(
203                self.properties.distinguishedname.to_string(),
204                self.object_identifier.to_string()
205            );
206            // Push DN and Type
207            sid_type.insert(
208                self.object_identifier.to_string(),
209                "CertTemplate".to_string()
210            );
211        }
212
213        // Trace and return CertTemplate struct
214        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
215        Ok(())
216    }
217
218    /// Function to get effective ekus for one template.
219    fn get_effectiveekus(
220        schema_version: &i64,
221        ekus: &[String],
222        certificateapplicationpolicy: &[String],
223    ) -> Vec<String> {
224        if schema_version == &1 && !ekus.is_empty() {
225            ekus.to_vec()
226        } else {
227            certificateapplicationpolicy.to_vec()
228        }
229    }
230
231    /// Function to check if authentication is enabled or not.
232    fn authentication_is_enabled(&mut self) -> bool {
233        let authentication_oids = [
234            "1.3.6.1.5.5.7.3.2", // ClientAuthentication,
235            "1.3.6.1.5.2.3.4", // PKINITClientAuthentication
236            "1.3.6.1.4.1.311.20.2.2", // SmartcardLogon
237            "2.5.29.37.0", // AnyPurpose
238        ];
239        self.properties.effectiveekus.iter()
240            .any(|eku| authentication_oids.contains(&eku.as_str()))
241            || self.properties.effectiveekus.is_empty()
242    }
243}
244
245impl LdapObject for CertTemplate {
246    // To JSON
247    fn to_json(&self) -> Value {
248        serde_json::to_value(self).unwrap()
249    }
250
251    // Get values
252    fn get_object_identifier(&self) -> &String {
253        &self.object_identifier
254    }
255    fn get_is_acl_protected(&self) -> &bool {
256        &self.is_acl_protected
257    }
258    fn get_aces(&self) -> &Vec<AceTemplate> {
259        &self.aces
260    }
261    fn get_spntargets(&self) -> &Vec<SPNTarget> {
262        panic!("Not used by current object.");
263    }
264    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
265        panic!("Not used by current object.");
266    }
267    fn get_links(&self) -> &Vec<Link> {
268        panic!("Not used by current object.");
269    }
270    fn get_contained_by(&self) -> &Option<Member> {
271        &self.contained_by
272    }
273    fn get_child_objects(&self) -> &Vec<Member> {
274        panic!("Not used by current object.");
275    }
276    fn get_haslaps(&self) -> &bool {
277        &false
278    }
279    
280    // Get mutable values
281    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
282        &mut self.aces
283    }
284    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
285        panic!("Not used by current object.");
286    }
287    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
288        panic!("Not used by current object.");
289    }
290    
291    // Edit values
292    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
293        self.is_acl_protected = is_acl_protected;
294        self.properties.isaclprotected = is_acl_protected;
295    }
296    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
297        self.aces = aces;
298    }
299    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
300        // Not used by current object.
301    }
302    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
303        // Not used by current object.
304    }
305    fn set_links(&mut self, _links: Vec<Link>) {
306        // Not used by current object.
307    }
308    fn set_contained_by(&mut self, contained_by: Option<Member>) {
309        self.contained_by = contained_by;
310    }
311    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
312        // Not used by current object.
313    }
314}
315
316
317// CertTemplate properties structure
318#[derive(Debug, Clone, Deserialize, Serialize)]
319pub struct CertTemplateProperties {
320   domain: String,
321   name: String,
322   distinguishedname: String,
323   domainsid: String,
324   isaclprotected: bool,
325   description: Option<String>,
326   whencreated: i64,
327   validityperiod: String,
328   renewalperiod: String,
329   schemaversion: i64,
330   displayname: String,
331   oid: String,
332   enrollmentflag: String,
333   requiresmanagerapproval: bool,
334   nosecurityextension: bool,
335   certificatenameflag: String,
336   enrolleesuppliessubject: bool,
337   subjectaltrequireupn: bool,
338   subjectaltrequiredns: bool,
339   subjectaltrequiredomaindns: bool,
340   subjectaltrequireemail: bool,
341   subjectaltrequirespn: bool,
342   subjectrequireemail: bool,
343   ekus: Vec<String>,
344   certificateapplicationpolicy: Vec<String>,
345   authorizedsignatures: i64,
346   applicationpolicies: Vec<String>,
347   issuancepolicies: Vec<String>,
348   effectiveekus: Vec<String>,
349   authenticationenabled: bool,
350}
351
352impl Default for CertTemplateProperties {
353    fn default() -> CertTemplateProperties {
354        CertTemplateProperties {
355            domain: String::from(""),
356            name: String::from(""),
357            distinguishedname: String::from(""),
358            domainsid: String::from(""),
359            isaclprotected: false,
360            description: None,
361            whencreated: -1,
362            validityperiod: String::from(""),
363            renewalperiod: String::from(""),
364            schemaversion: 1,
365            displayname: String::from(""),
366            oid: String::from(""),
367            enrollmentflag: String::from(""),
368            requiresmanagerapproval: false,
369            nosecurityextension: false,
370            certificatenameflag: String::from(""),
371            enrolleesuppliessubject: false,
372            subjectaltrequireupn: false,
373            subjectaltrequiredns: false,
374            subjectaltrequiredomaindns: false,
375            subjectaltrequireemail: false,
376            subjectaltrequirespn: false,
377            subjectrequireemail: false,
378            ekus: Vec::new(),
379            certificateapplicationpolicy: Vec::new(),
380            authorizedsignatures: 0,
381            applicationpolicies: Vec::new(),
382            issuancepolicies: Vec::new(),
383            effectiveekus: Vec::new(),
384            authenticationenabled: false,
385       }
386    }
387 }
388
389impl CertTemplateProperties {
390    // Immutable access.
391    pub fn name(&self) -> &String {
392        &self.name
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399
400    const SUBJECT_NAME_FLAG_PROPERTIES: [&str; 6] = [
401        "subjectaltrequiredomaindns",
402        "subjectaltrequirespn",
403        "subjectaltrequireupn",
404        "subjectaltrequireemail",
405        "subjectaltrequiredns",
406        "subjectrequireemail",
407    ];
408
409    fn parse_certtemplate_with_name_flag(flag: Option<i64>) -> CertTemplate {
410        let mut attrs = HashMap::new();
411        attrs.insert("name".to_string(), vec!["RustHoundLab".to_string()]);
412        if let Some(flag) = flag {
413            attrs.insert(
414                "msPKI-Certificate-Name-Flag".to_string(),
415                vec![flag.to_string()],
416            );
417        }
418
419        let result = SearchEntry {
420            dn: "CN=RustHoundLab,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=local".to_string(),
421            attrs,
422            bin_attrs: HashMap::new(),
423        };
424        let mut certtemplate = CertTemplate::new();
425        let mut dn_sid = HashMap::new();
426        let mut sid_type = HashMap::new();
427
428        certtemplate
429            .parse(
430                result,
431                "example.local",
432                &mut dn_sid,
433                &mut sid_type,
434                "S-1-5-21-1-2-3",
435                &HashMap::new(),
436            )
437            .unwrap();
438
439        certtemplate
440    }
441
442    #[test]
443    fn parse_maps_each_subject_name_flag_to_its_boolean_property() {
444        let cases = [
445            (0x0040_0000, "subjectaltrequiredomaindns"),
446            (0x0080_0000, "subjectaltrequirespn"),
447            (0x0200_0000, "subjectaltrequireupn"),
448            (0x0400_0000, "subjectaltrequireemail"),
449            (0x0800_0000, "subjectaltrequiredns"),
450            (0x2000_0000, "subjectrequireemail"),
451        ];
452
453        for (flag, expected_property) in cases {
454            let certtemplate = parse_certtemplate_with_name_flag(Some(flag));
455            let properties = &certtemplate.to_json()["Properties"];
456
457            for property in SUBJECT_NAME_FLAG_PROPERTIES {
458                assert_eq!(
459                    properties[property],
460                    property == expected_property,
461                    "unexpected value for {property} with flag {flag:#010x}",
462                );
463            }
464        }
465    }
466
467    #[test]
468    fn subject_name_flag_properties_default_to_false_when_attribute_is_absent() {
469        let certtemplate = parse_certtemplate_with_name_flag(None);
470        let properties = &certtemplate.to_json()["Properties"];
471
472        for property in SUBJECT_NAME_FLAG_PROPERTIES {
473            assert_eq!(properties[property], false, "{property} should default to false");
474        }
475    }
476}