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#[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 pub fn new() -> Self {
33 Self { ..Default::default() }
34 }
35
36 pub fn properties(&self) -> &CertTemplateProperties {
38 &self.properties
39 }
40 pub fn object_identifier(&self) -> &String {
41 &self.object_identifier
42 }
43
44 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!("Parse CertTemplate: {result_dn}");
60
61 for (key, value) in &result_attrs {
63 trace!(" {key:?}:{value:?}");
64 }
65 for (key, value) in &result_bin {
67 trace!(" {key:?}:{value:?}");
68 }
69
70 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 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 }
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 (key, value) in &result_bin {
161 match key.as_str() {
162 "objectGUID" => {
163 let guid = decode_guid_le(&value[0]);
165 self.properties.objectguid = guid;
166 }
167 "nTSecurityDescriptor" => {
168 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 self.properties.effectiveekus = Self::get_effectiveekus(
192 &self.properties.schemaversion,
193 &self.properties.ekus,
194 &self.properties.certificateapplicationpolicy,
195 );
196
197 self.properties.authenticationenabled = Self::authentication_is_enabled(self);
199
200 if self.object_identifier != "SID" {
202 dn_sid.insert(
203 self.properties.distinguishedname.to_string(),
204 self.object_identifier.to_string()
205 );
206 sid_type.insert(
208 self.object_identifier.to_string(),
209 "CertTemplate".to_string()
210 );
211 }
212
213 Ok(())
216 }
217
218 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 fn authentication_is_enabled(&mut self) -> bool {
233 let authentication_oids = [
234 "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.2.3.4", "1.3.6.1.4.1.311.20.2.2", "2.5.29.37.0", ];
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 fn to_json(&self) -> Value {
248 serde_json::to_value(self).unwrap()
249 }
250
251 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 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 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 }
302 fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
303 }
305 fn set_links(&mut self, _links: Vec<Link>) {
306 }
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 }
314 fn set_owner_rights_flags(&mut self, any: bool, any_inherited: bool) {
315 self.properties.doesanyacegrantownerrights = any;
316 self.properties.doesanyinheritedacegrantownerrights = any_inherited;
317 }
318}
319
320
321#[derive(Debug, Clone, Deserialize, Serialize)]
323pub struct CertTemplateProperties {
324 domain: String,
325 name: String,
326 distinguishedname: String,
327 domainsid: String,
328 objectguid: String,
329 doesanyacegrantownerrights: bool,
330 doesanyinheritedacegrantownerrights: bool,
331 isaclprotected: bool,
332 description: Option<String>,
333 whencreated: i64,
334 validityperiod: String,
335 renewalperiod: String,
336 schemaversion: i64,
337 displayname: String,
338 oid: String,
339 enrollmentflag: String,
340 requiresmanagerapproval: bool,
341 nosecurityextension: bool,
342 certificatenameflag: String,
343 enrolleesuppliessubject: bool,
344 subjectaltrequireupn: bool,
345 subjectaltrequiredns: bool,
346 subjectaltrequiredomaindns: bool,
347 subjectaltrequireemail: bool,
348 subjectaltrequirespn: bool,
349 subjectrequireemail: bool,
350 ekus: Vec<String>,
351 certificateapplicationpolicy: Vec<String>,
352 authorizedsignatures: i64,
353 applicationpolicies: Vec<String>,
354 issuancepolicies: Vec<String>,
355 effectiveekus: Vec<String>,
356 authenticationenabled: bool,
357}
358
359impl Default for CertTemplateProperties {
360 fn default() -> CertTemplateProperties {
361 CertTemplateProperties {
362 domain: String::from(""),
363 name: String::from(""),
364 distinguishedname: String::from(""),
365 domainsid: String::from(""),
366 objectguid: String::from(""),
367 doesanyacegrantownerrights: false,
368 doesanyinheritedacegrantownerrights: false,
369 isaclprotected: false,
370 description: None,
371 whencreated: -1,
372 validityperiod: String::from(""),
373 renewalperiod: String::from(""),
374 schemaversion: 1,
375 displayname: String::from(""),
376 oid: String::from(""),
377 enrollmentflag: String::from(""),
378 requiresmanagerapproval: false,
379 nosecurityextension: false,
380 certificatenameflag: String::from(""),
381 enrolleesuppliessubject: false,
382 subjectaltrequireupn: false,
383 subjectaltrequiredns: false,
384 subjectaltrequiredomaindns: false,
385 subjectaltrequireemail: false,
386 subjectaltrequirespn: false,
387 subjectrequireemail: false,
388 ekus: Vec::new(),
389 certificateapplicationpolicy: Vec::new(),
390 authorizedsignatures: 0,
391 applicationpolicies: Vec::new(),
392 issuancepolicies: Vec::new(),
393 effectiveekus: Vec::new(),
394 authenticationenabled: false,
395 }
396 }
397 }
398
399impl CertTemplateProperties {
400 pub fn name(&self) -> &String {
402 &self.name
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 const SUBJECT_NAME_FLAG_PROPERTIES: [&str; 6] = [
411 "subjectaltrequiredomaindns",
412 "subjectaltrequirespn",
413 "subjectaltrequireupn",
414 "subjectaltrequireemail",
415 "subjectaltrequiredns",
416 "subjectrequireemail",
417 ];
418
419 fn parse_certtemplate_with_name_flag(flag: Option<i64>) -> CertTemplate {
420 let mut attrs = HashMap::new();
421 attrs.insert("name".to_string(), vec!["RustHoundLab".to_string()]);
422 if let Some(flag) = flag {
423 attrs.insert(
424 "msPKI-Certificate-Name-Flag".to_string(),
425 vec![flag.to_string()],
426 );
427 }
428
429 let result = SearchEntry {
430 dn: "CN=RustHoundLab,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=local".to_string(),
431 attrs,
432 bin_attrs: HashMap::new(),
433 };
434 let mut certtemplate = CertTemplate::new();
435 let mut dn_sid = HashMap::new();
436 let mut sid_type = HashMap::new();
437
438 certtemplate
439 .parse(
440 result,
441 "example.local",
442 &mut dn_sid,
443 &mut sid_type,
444 "S-1-5-21-1-2-3",
445 &HashMap::new(),
446 )
447 .unwrap();
448
449 certtemplate
450 }
451
452 #[test]
453 fn parse_maps_each_subject_name_flag_to_its_boolean_property() {
454 let cases = [
455 (0x0040_0000, "subjectaltrequiredomaindns"),
456 (0x0080_0000, "subjectaltrequirespn"),
457 (0x0200_0000, "subjectaltrequireupn"),
458 (0x0400_0000, "subjectaltrequireemail"),
459 (0x0800_0000, "subjectaltrequiredns"),
460 (0x2000_0000, "subjectrequireemail"),
461 ];
462
463 for (flag, expected_property) in cases {
464 let certtemplate = parse_certtemplate_with_name_flag(Some(flag));
465 let properties = &certtemplate.to_json()["Properties"];
466
467 for property in SUBJECT_NAME_FLAG_PROPERTIES {
468 assert_eq!(
469 properties[property],
470 property == expected_property,
471 "unexpected value for {property} with flag {flag:#010x}",
472 );
473 }
474 }
475 }
476
477 #[test]
478 fn subject_name_flag_properties_default_to_false_when_attribute_is_absent() {
479 let certtemplate = parse_certtemplate_with_name_flag(None);
480 let properties = &certtemplate.to_json()["Properties"];
481
482 for property in SUBJECT_NAME_FLAG_PROPERTIES {
483 assert_eq!(properties[property], false, "{property} should default to false");
484 }
485 }
486}