polyproto/certs/capabilities/
mod.rs1pub mod basic_constraints;
7pub mod key_usage;
9
10pub use basic_constraints::*;
11pub use key_usage::*;
12use x509_cert::{
13 attr::{Attribute, Attributes},
14 ext::{Extension, Extensions},
15};
16
17use crate::errors::{CertificateConversionError, InvalidInput};
18
19pub const OID_KEY_USAGE_DIGITAL_SIGNATURE: &str = "1.3.6.1.5.5.7.3.3";
21pub const OID_KEY_USAGE_CRL_SIGN: &str = "1.3.6.1.5.5.7.3.2";
23pub const OID_KEY_USAGE_CONTENT_COMMITMENT: &str = "1.3.6.1.5.5.7.3.8";
25pub const OID_KEY_USAGE_KEY_ENCIPHERMENT: &str = "1.3.6.1.5.5.7.3.1";
27pub const OID_KEY_USAGE_DATA_ENCIPHERMENT: &str = "1.3.6.1.5.5.7.3.4";
29pub const OID_KEY_USAGE_KEY_AGREEMENT: &str = "1.3.6.1.5.5.7.3.9";
31pub const OID_KEY_USAGE_KEY_CERT_SIGN: &str = "1.3.6.1.5.5.7.3.3";
33pub const OID_KEY_USAGE_ENCIPHER_ONLY: &str = "1.3.6.1.5.5.7.3.7";
35pub const OID_KEY_USAGE_DECIPHER_ONLY: &str = "1.3.6.1.5.5.7.3.6";
37pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
39pub const OID_KEY_USAGE: &str = "2.5.29.15";
41
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct Capabilities {
50 pub key_usage: KeyUsages,
53 pub basic_constraints: BasicConstraints,
57}
58
59impl Default for Capabilities {
60 fn default() -> Self {
61 Self {
62 key_usage: Default::default(),
63 basic_constraints: BasicConstraints { ca: false, path_length: None },
64 }
65 }
66}
67
68impl Capabilities {
69 #[must_use]
72 pub fn default_actor() -> Self {
73 let key_usage = KeyUsages::new(&[KeyUsage::DigitalSignature]);
74 let basic_constraints = BasicConstraints { ca: false, path_length: None };
75 Self { key_usage, basic_constraints }
76 }
77
78 #[must_use]
80 pub fn default_home_server() -> Self {
81 let key_usage = KeyUsages::new(&[KeyUsage::KeyCertSign]);
82 let basic_constraints = BasicConstraints { ca: true, path_length: Some(1) };
83 Self { key_usage, basic_constraints }
84 }
85
86 pub fn validate(&self) -> Result<(), crate::errors::ConstraintError> {
94 let is_ca = self.basic_constraints.ca;
95
96 let mut can_commit_content = false;
97 let mut can_sign = false;
98 let mut key_cert_sign = false;
99 let mut has_only_encipher = false;
100 let mut has_only_decipher = false;
101 let mut has_key_agreement = false;
102
103 for item in self.key_usage.key_usages.iter() {
104 if !has_only_encipher && item == &KeyUsage::EncipherOnly {
105 has_only_encipher = true;
106 }
107 if !has_only_decipher && item == &KeyUsage::DecipherOnly {
108 has_only_decipher = true;
109 }
110 if !has_key_agreement && item == &KeyUsage::KeyAgreement {
111 has_key_agreement = true;
112 }
113 if item == &KeyUsage::ContentCommitment {
114 can_commit_content = true;
115 }
116 if item == &KeyUsage::DigitalSignature {
117 can_sign = true;
118 }
119 if item == &KeyUsage::KeyCertSign {
120 key_cert_sign = true;
121 }
122 }
123
124 if !is_ca && !can_sign && !can_commit_content {
126 return Err(crate::errors::ConstraintError::Malformed(Some(
127 crate::errors::ERR_MSG_ACTOR_MISSING_SIGNING_CAPS.to_owned(),
128 )));
129 }
130
131 if can_sign && can_commit_content {
133 return Err(crate::errors::ConstraintError::Malformed(Some(
134 "Cannot have both signing and non-repudiation signing capabilities".to_owned(),
135 )));
136 }
137
138 if is_ca || key_cert_sign {
140 if !is_ca {
141 return Err(crate::errors::ConstraintError::Malformed(Some(
142 "If KeyCertSign capability is wanted, CA flag must be true".to_owned(),
143 )));
144 }
145 if !key_cert_sign {
146 return Err(crate::errors::ConstraintError::Malformed(Some(format!(
147 "{} Missing capability \"KeyCertSign\"",
148 crate::errors::ERR_MSG_HOME_SERVER_MISSING_CA_ATTR
149 ))));
150 }
151 }
152
153 if (has_only_encipher || has_only_decipher) && !has_key_agreement {
155 Err(crate::errors::ConstraintError::Malformed(Some(
156 "KeyAgreement capability needs to be true to use OnlyEncipher or OnlyDecipher"
157 .to_owned(),
158 )))
159 } else {
160 Ok(())
161 }
162 }
163
164 pub fn validate_for_actor(&self) -> Result<(), crate::errors::ConstraintError> {
177 self.validate()?;
178 if self.basic_constraints.ca {
179 return Err(crate::errors::ConstraintError::Malformed(Some(
180 crate::errors::ERR_MSG_ACTOR_CANNOT_BE_CA.to_owned(),
181 )));
182 }
183 Ok(())
184 }
185
186 pub fn validate_for_home_server(&self) -> Result<(), crate::errors::ConstraintError> {
198 self.validate()?;
199 if !self.basic_constraints.ca {
200 return Err(crate::errors::ConstraintError::Malformed(Some(
201 crate::errors::ERR_MSG_HOME_SERVER_MISSING_CA_ATTR.to_owned(),
202 )));
203 }
204 Ok(())
205 }
206}
207
208impl TryFrom<Attributes> for Capabilities {
209 type Error = CertificateConversionError;
210
211 fn try_from(value: Attributes) -> Result<Self, Self::Error> {
219 let mut key_usages = KeyUsages::new(&[]);
220 let mut basic_constraints = BasicConstraints::default();
221 let mut num_basic_constraints = 0u8;
222 for item in value.iter() {
223 match item.oid.to_string().as_str() {
224 #[allow(unreachable_patterns)] OID_KEY_USAGE => {
226 key_usages = KeyUsages::try_from(item.clone())?;
227 }
228 OID_BASIC_CONSTRAINTS => {
229 num_basic_constraints = num_basic_constraints.saturating_add(1);
230 if num_basic_constraints > 1 {
231 return Err(CertificateConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting > 1 BasicConstraints into Capabilities. Expected 1 BasicConstraints".to_owned())));
232 }
233 basic_constraints = BasicConstraints::try_from(item.clone())?;
234 }
235 _ => (),
236 }
237 }
238 Ok(Self { key_usage: key_usages, basic_constraints })
239 }
240}
241
242impl TryFrom<Capabilities> for Attributes {
243 type Error = CertificateConversionError;
244
245 fn try_from(value: Capabilities) -> Result<Self, Self::Error> {
249 let mut sov = Self::new();
250 let insertion = sov.insert(Attribute::try_from(value.key_usage)?);
251 if insertion.is_err() {
252 return Err(CertificateConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_owned())));
253 }
254 let insertion = sov.insert(Attribute::try_from(value.basic_constraints)?);
255 if insertion.is_err() {
256 return Err(CertificateConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_owned())));
257 }
258 Ok(sov)
259 }
260}
261
262impl TryFrom<Capabilities> for Extensions {
263 type Error = CertificateConversionError;
264 fn try_from(value: Capabilities) -> Result<Self, Self::Error> {
269 Ok(vec![
271 Extension::try_from(value.basic_constraints)?,
272 Extension::try_from(value.key_usage)?,
273 ])
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub struct ActorCapabilities(pub(crate) Capabilities);
286
287impl ActorCapabilities {
288 pub fn try_new(key_usages: KeyUsages) -> Result<Self, crate::errors::ConstraintError> {
298 let caps = Capabilities {
299 key_usage: key_usages,
300 basic_constraints: BasicConstraints { ca: false, path_length: None },
301 };
302 caps.validate_for_actor()?;
303 Ok(Self(caps))
304 }
305}
306
307impl Default for ActorCapabilities {
308 fn default() -> Self {
309 Self(Capabilities::default_actor())
310 }
311}
312
313impl std::ops::Deref for ActorCapabilities {
314 type Target = Capabilities;
315
316 fn deref(&self) -> &Self::Target {
317 &self.0
318 }
319}
320
321impl From<ActorCapabilities> for Capabilities {
322 fn from(value: ActorCapabilities) -> Self {
323 value.0
324 }
325}
326
327impl TryFrom<Capabilities> for ActorCapabilities {
328 type Error = crate::errors::ConstraintError;
329
330 fn try_from(value: Capabilities) -> Result<Self, Self::Error> {
331 Self::try_new(value.key_usage)
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
343pub struct HomeServerCapabilities(pub Capabilities);
344
345impl HomeServerCapabilities {
346 pub fn try_new(
356 key_usages: KeyUsages,
357 path_length: Option<u64>,
358 ) -> Result<Self, crate::errors::ConstraintError> {
359 let caps = Capabilities {
360 key_usage: key_usages,
361 basic_constraints: BasicConstraints { ca: true, path_length },
362 };
363 caps.validate_for_home_server()?;
364 Ok(Self(caps))
365 }
366}
367
368impl Default for HomeServerCapabilities {
369 fn default() -> Self {
370 Self(Capabilities::default_home_server())
371 }
372}
373
374impl std::ops::Deref for HomeServerCapabilities {
375 type Target = Capabilities;
376
377 fn deref(&self) -> &Self::Target {
378 &self.0
379 }
380}
381
382impl From<HomeServerCapabilities> for Capabilities {
383 fn from(value: HomeServerCapabilities) -> Self {
384 value.0
385 }
386}
387
388impl TryFrom<Capabilities> for HomeServerCapabilities {
389 type Error = crate::errors::ConstraintError;
390
391 fn try_from(value: Capabilities) -> Result<Self, Self::Error> {
392 Self::try_new(value.key_usage, value.basic_constraints.path_length)
393 }
394}
395
396impl TryFrom<Extensions> for Capabilities {
397 type Error = CertificateConversionError;
398
399 fn try_from(value: Extensions) -> Result<Self, Self::Error> {
406 let mut basic_constraints: BasicConstraints = BasicConstraints::default();
407 let mut key_usage: KeyUsages = KeyUsages::default();
408 for item in value.iter() {
409 #[allow(unreachable_patterns)] match item.extn_id.to_string().as_str() {
411 OID_BASIC_CONSTRAINTS => {
412 basic_constraints = BasicConstraints::try_from(item.clone())?
413 }
414 OID_KEY_USAGE => key_usage = KeyUsages::try_from(item.clone())?,
415 _ => {
416 return Err(CertificateConversionError::InvalidInput(InvalidInput::Malformed(
417 format!(
418 "Invalid OID found for converting this set of Extensions to Capabilities: {} is not a valid OID for BasicConstraints or KeyUsages",
419 item.extn_id
420 ),
421 )));
422 }
423 };
424 }
425 Ok(Capabilities { key_usage, basic_constraints })
426 }
427}
428
429#[cfg(test)]
430#[allow(clippy::unwrap_used)]
431mod test {
432 use spki::ObjectIdentifier;
433
434 use super::*;
435
436 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
437 #[cfg_attr(not(target_arch = "wasm32"), test)]
438 fn test_basic_constraints_to_object_identifier() {
439 let bc = BasicConstraints { ca: true, path_length: None };
440 let _ = ObjectIdentifier::from(bc);
441 }
442
443 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
444 #[cfg_attr(not(target_arch = "wasm32"), test)]
445 fn test_basic_constraints_to_attribute() {
446 let mut bc = BasicConstraints { ca: false, path_length: None };
447 let _ = Attribute::try_from(bc).expect("Should not fail in test");
448
449 bc.ca = true;
450 let _ = Attribute::try_from(bc).expect("Should not fail in test");
451
452 bc.path_length = Some(0);
453 let _ = Attribute::try_from(bc).expect("Should not fail in test");
454
455 let mut county_count = 2u64;
457 while county_count != u64::MAX {
458 bc.path_length = Some(county_count);
459 let _ = Attribute::try_from(bc).expect("Should not fail in test");
460 if let Some(res) = county_count.checked_mul(2) {
461 county_count = res;
462 } else {
463 county_count = u64::MAX;
464 }
465 }
466 }
467}
468
469#[cfg(test)]
470#[allow(clippy::unwrap_used)]
471mod test_key_usage_from_attribute {
472 use std::str::FromStr;
473
474 use der::{Any, Tag, asn1::SetOfVec};
475 use spki::ObjectIdentifier;
476
477 use super::*;
478
479 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
480 #[cfg_attr(not(target_arch = "wasm32"), test)]
481 fn test_key_usage_tag_mismatch() {
482 let mut sov = SetOfVec::new();
483 sov.insert(Any::new(Tag::Integer, vec![0x00]).expect("Should not fail in test"))
484 .expect("Should not fail in test");
485 let attribute = Attribute {
486 oid: ObjectIdentifier::from_str(OID_KEY_USAGE_CONTENT_COMMITMENT)
487 .expect("Should not fail in test"),
488 values: sov,
489 };
490 let result = KeyUsages::try_from(attribute);
491 assert!(result.is_err());
492 }
493
494 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
495 #[cfg_attr(not(target_arch = "wasm32"), test)]
496 fn test_key_usage_wrong_oid() {
497 let mut sov = SetOfVec::new();
498 sov.insert(Any::new(Tag::Boolean, vec![0x00]).expect("Should not fail in test"))
499 .expect("Should not fail in test");
500 let attribute = Attribute {
501 oid: ObjectIdentifier::from_str("1.2.4.2.1.1.1.1.1.1.1.1.1.1.161.69")
502 .expect("Should not fail in test"),
503 values: sov,
504 };
505 let result = KeyUsages::try_from(attribute);
506 assert!(result.is_err());
507 }
508
509 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
510 #[cfg_attr(not(target_arch = "wasm32"), test)]
511 fn test_key_usage_from_attribute_weird_bool_value() {
512 let mut sov = SetOfVec::new();
513 sov.insert(Any::new(Tag::Boolean, vec![0x02]).expect("Should not fail in test"))
514 .expect("Should not fail in test");
515 let attribute = Attribute {
516 oid: ObjectIdentifier::from_str(OID_KEY_USAGE_CONTENT_COMMITMENT)
517 .expect("Should not fail in test"),
518 values: sov,
519 };
520 let result = KeyUsages::try_from(attribute);
521 assert!(result.is_err());
522 }
523}
524
525#[cfg(test)]
526#[allow(clippy::unwrap_used)]
527mod test_basic_constraints_from_attribute {
528 use std::str::FromStr;
529
530 use der::{
531 Any, Tag,
532 asn1::{Ia5String, SetOfVec},
533 };
534 use spki::ObjectIdentifier;
535
536 use super::*;
537
538 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
539 #[cfg_attr(not(target_arch = "wasm32"), test)]
540 fn test_basic_constraints_from_attribute() {
541 let bc = BasicConstraints { ca: true, path_length: Some(0) };
542 let attribute = Attribute::try_from(bc).expect("Should not fail in test");
543 let result = BasicConstraints::try_from(attribute);
544 assert!(result.is_ok());
545 }
546
547 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
548 #[cfg_attr(not(target_arch = "wasm32"), test)]
549 fn test_basic_constraints_wrong_value_type() {
550 let mut sov = SetOfVec::new();
551 sov.insert(
552 Any::new(
553 Tag::Ia5String,
554 Ia5String::new("hello").expect("Should not fail in test").as_bytes(),
555 )
556 .expect("Should not fail in test"),
557 )
558 .expect("Should not fail in test");
559 let attribute = Attribute {
560 oid: ObjectIdentifier::from_str(OID_BASIC_CONSTRAINTS)
561 .expect("Should not fail in test"),
562 values: sov,
563 };
564 let result = BasicConstraints::try_from(attribute);
565 assert!(result.is_err());
566 }
567
568 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
569 #[cfg_attr(not(target_arch = "wasm32"), test)]
570 fn test_basic_constraints_wrong_oid() {
571 let bc = BasicConstraints { ca: true, path_length: Some(0) };
572 let mut attribute = Attribute::try_from(bc).expect("Should not fail in test");
573 attribute.oid =
574 ObjectIdentifier::from_str("0.0.161.80085").expect("Should not fail in test");
575 let result = BasicConstraints::try_from(attribute);
576 assert!(result.is_err());
577 }
578
579 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
580 #[cfg_attr(not(target_arch = "wasm32"), test)]
581 fn test_basic_constraints_from_attribute_too_many_bools_or_ints() {
582 let mut sov = SetOfVec::new();
583 sov.insert(Any::new(Tag::Boolean, vec![0x00]).expect("Should not fail in test"))
584 .expect("Should not fail in test");
585 sov.insert(Any::new(Tag::Boolean, vec![0x01]).expect("Should not fail in test"))
586 .expect("Should not fail in test");
587 let attribute = Attribute {
588 oid: ObjectIdentifier::from_str(OID_BASIC_CONSTRAINTS)
589 .expect("Should not fail in test"),
590 values: sov,
591 };
592 let result = BasicConstraints::try_from(attribute);
593 assert!(result.is_err());
594
595 let mut sov = SetOfVec::new();
596 sov.insert(Any::new(Tag::Integer, vec![0x00]).expect("Should not fail in test"))
597 .expect("Should not fail in test");
598 sov.insert(Any::new(Tag::Integer, vec![0x01]).expect("Should not fail in test"))
599 .expect("Should not fail in test");
600 let attribute = Attribute {
601 oid: ObjectIdentifier::from_str(OID_BASIC_CONSTRAINTS)
602 .expect("Should not fail in test"),
603 values: sov,
604 };
605 let result = BasicConstraints::try_from(attribute);
606 assert!(result.is_err());
607 }
608
609 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
610 #[cfg_attr(not(target_arch = "wasm32"), test)]
611 fn test_basic_constraints_from_attribute_weird_bool_value() {
612 let mut sov = SetOfVec::new();
613 sov.insert(Any::new(Tag::Boolean, vec![0x02]).expect("Should not fail in test"))
614 .expect("Should not fail in test");
615 let attribute = Attribute {
616 oid: ObjectIdentifier::from_str(OID_BASIC_CONSTRAINTS)
617 .expect("Should not fail in test"),
618 values: sov,
619 };
620 let result = BasicConstraints::try_from(attribute);
621 assert!(result.is_err());
622 }
623}