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.object_identifier = guid.to_owned();
166 self.properties.objectguid = guid;
167 }
168 "nTSecurityDescriptor" => {
169 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 self.properties.effectiveekus = Self::get_effectiveekus(
193 &self.properties.schemaversion,
194 &self.properties.ekus,
195 &self.properties.certificateapplicationpolicy,
196 );
197
198 self.properties.authenticationenabled = Self::authentication_is_enabled(self);
200
201 if self.object_identifier != "SID" {
203 dn_sid.insert(
204 self.properties.distinguishedname.to_string(),
205 self.object_identifier.to_string()
206 );
207 sid_type.insert(
209 self.object_identifier.to_string(),
210 "CertTemplate".to_string()
211 );
212 }
213
214 Ok(())
217 }
218
219 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 fn authentication_is_enabled(&mut self) -> bool {
234 let authentication_oids = [
235 "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", ];
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 fn to_json(&self) -> Value {
249 serde_json::to_value(self).unwrap()
250 }
251
252 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 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 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 }
303 fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
304 }
306 fn set_links(&mut self, _links: Vec<Link>) {
307 }
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 }
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#[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 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}