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 }
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}
315
316
317#[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 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}