diff --git a/Cargo.lock b/Cargo.lock index 9f9b07d71..5dffe744d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,7 @@ version = "0.6.0-pre.1" dependencies = [ "const-oid", "der_derive", + "flagset", "hex-literal", "pem-rfc7468", "proptest", @@ -349,6 +350,12 @@ dependencies = [ "instant", ] +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + [[package]] name = "fnv" version = "1.0.7" @@ -1236,6 +1243,7 @@ name = "x509" version = "0.0.1" dependencies = [ "der", + "flagset", "hex-literal", "spki", "x501", diff --git a/der/Cargo.toml b/der/Cargo.toml index 71792bead..d832b681c 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -18,6 +18,7 @@ rust-version = "1.57" [dependencies] const-oid = { version = "0.8", optional = true, path = "../const-oid" } der_derive = { version = "=0.6.0-pre.1", optional = true, path = "derive" } +flagset = { version = "0.4.3", optional = true } pem-rfc7468 = { version = "=0.4.0-pre.0", optional = true, path = "../pem-rfc7468" } time = { version = "0.3", optional = true, default-features = false } diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs index 025585a85..b9ce726a3 100644 --- a/der/src/asn1/bit_string.rs +++ b/der/src/asn1/bit_string.rs @@ -225,6 +225,74 @@ impl<'a> ExactSizeIterator for BitStringIter<'a> { impl<'a> FusedIterator for BitStringIter<'a> {} +#[cfg(feature = "flagset")] +impl FixedTag for flagset::FlagSet { + const TAG: Tag = BitString::TAG; +} + +#[cfg(feature = "flagset")] +impl<'a, T> DecodeValue<'a> for flagset::FlagSet +where + T: flagset::Flags, + T::Type: From, + T::Type: core::ops::Shl, +{ + fn decode_value(decoder: &mut Decoder<'a>, header: Header) -> Result { + let position = decoder.position(); + + let bits = BitString::decode_value(decoder, header)?; + + let mut flags = T::none().bits(); + if bits.bit_len() > core::mem::size_of_val(&flags) * 8 { + return Err(Error::new(ErrorKind::Overlength, position)); + } + + for (i, bit) in bits.bits().enumerate() { + flags |= T::Type::from(bit) << i; + } + + Ok(Self::new_truncated(flags)) + } +} + +#[cfg(feature = "flagset")] +#[inline(always)] +fn encode(set: &flagset::FlagSet) -> (usize, [u8; 16]) +where + T: flagset::Flags, + u128: From, +{ + let bits: u128 = set.bits().into(); + let mut swap = 0u128; + + for i in 0..128 { + let on = bits & (1 << i); + swap |= on >> i << (128 - i - 1); + } + + (bits.leading_zeros() as usize, swap.to_be_bytes()) +} + +#[cfg(feature = "flagset")] +impl EncodeValue for flagset::FlagSet +where + T::Type: From, + T::Type: core::ops::Shl, + u128: From, +{ + fn value_len(&self) -> Result { + let (lead, buff) = encode(self); + let buff = &buff[..buff.len() - lead / 8]; + BitString::new((lead % 8) as u8, buff)?.value_len() + } + + fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> { + let (lead, buff) = encode(self); + let buff = &buff[..buff.len() - lead / 8]; + BitString::new((lead % 8) as u8, buff)?.encode_value(encoder) + } +} + #[cfg(test)] mod tests { use super::{BitString, Result, Tag}; diff --git a/x509/Cargo.toml b/x509/Cargo.toml index f87d03c88..f3997083f 100644 --- a/x509/Cargo.toml +++ b/x509/Cargo.toml @@ -15,7 +15,8 @@ edition = "2021" rust-version = "1.56" [dependencies] -der = { version = "=0.6.0-pre.1", features = ["derive", "alloc"], path = "../der" } +der = { version = "=0.6.0-pre.1", features = ["derive", "alloc", "flagset"], path = "../der" } +flagset = { version = "0.4.3" } spki = { version = "=0.6.0-pre.0", path = "../spki" } x501 = { version = "=0.1.0-pre.0", path = "../x501" } diff --git a/x509/src/extensions_utils.rs b/x509/src/extensions_utils.rs deleted file mode 100644 index cf0a68afa..000000000 --- a/x509/src/extensions_utils.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Utility functions and enums related to X.509 extensions - -use alloc::vec::Vec; -use core::fmt; -use der::asn1::BitString; - -/// Enum representing values from the KeyUsage structure -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum KeyUsageValues { - /// DigitalSignature - DigitalSignature = 0, - /// NonRepudiation - NonRepudiation = 1, - /// KeyEncipherment - KeyEncipherment = 2, - /// DataEncipherment - DataEncipherment = 3, - /// KeyAgreement - KeyAgreement = 4, - /// KeyCertSign - KeyCertSign = 5, - /// CRLSign - CRLSign = 6, - /// EncipherOnly - EncipherOnly = 7, - /// DecipherOnly - DecipherOnly = 8, -} - -impl KeyUsageValues { - fn as_str(self) -> &'static str { - match self { - KeyUsageValues::DigitalSignature => "DigitalSignature", - KeyUsageValues::NonRepudiation => "NonRepudiation", - KeyUsageValues::KeyEncipherment => "KeyEncipherment", - KeyUsageValues::DataEncipherment => "DataEncipherment", - KeyUsageValues::KeyAgreement => "KeyAgreement", - KeyUsageValues::KeyCertSign => "KeyCertSign", - KeyUsageValues::CRLSign => "CRLSign", - KeyUsageValues::EncipherOnly => "EncipherOnly", - KeyUsageValues::DecipherOnly => "DecipherOnly", - } - } -} - -impl fmt::Display for KeyUsageValues { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(self.as_str()) - } -} - -/// Takes a BitString that contains one or two bytes and returns a Vec containing -/// enum values representing the KeyUsage values that were set in the BitString -pub fn get_key_usage_values(ku: &BitString<'_>) -> Vec { - let mut retval: Vec = Vec::new(); - let b = ku.raw_bytes(); - if 0x80 == 0x80 & b[0] { - retval.push(KeyUsageValues::DigitalSignature); - } - if 0x40 == 0x40 & b[0] { - retval.push(KeyUsageValues::NonRepudiation); - } - if 0x20 == 0x20 & b[0] { - retval.push(KeyUsageValues::KeyEncipherment); - } - if 0x10 == 0x10 & b[0] { - retval.push(KeyUsageValues::DataEncipherment); - } - if 0x08 == 0x08 & b[0] { - retval.push(KeyUsageValues::KeyAgreement); - } - if 0x04 == 0x04 & b[0] { - retval.push(KeyUsageValues::KeyCertSign); - } - if 0x02 == 0x02 & b[0] { - retval.push(KeyUsageValues::CRLSign); - } - if 0x01 == 0x01 & b[0] { - retval.push(KeyUsageValues::EncipherOnly); - } - - if 2 == b.len() && 0x80 == 0x80 & b[1] { - retval.push(KeyUsageValues::DecipherOnly); - } - - retval -} - -/// ReasonFlags ::= BIT STRING { -/// unused (0), -/// keyCompromise (1), -/// cACompromise (2), -/// affiliationChanged (3), -/// superseded (4), -/// cessationOfOperation (5), -/// certificateHold (6), -/// privilegeWithdrawn (7), -/// aACompromise (8) } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ReasonFlagsValues { - /// unused (0), - pub unused: bool, - /// keyCompromise (1), - pub key_compromise: bool, - /// cACompromise (2), - pub ca_compromise: bool, - /// affiliationChanged (3), - pub affiliation_changed: bool, - /// superseded (4), - pub superseded: bool, - /// cessationOfOperation (5), - pub cessation_of_operation: bool, - /// certificateHold (6), - pub certificate_hold: bool, - /// privilegeWithdrawn (7), - pub remove_from_crl: bool, - /// aACompromise (8) - pub aa_compromise: bool, -} - -/// Takes a BitString that contains one or two bytes and returns a Vec containing -/// enum values representing the KeyUsage values that were set in the BitString -pub fn get_reason_flags_values(ku: &BitString<'_>) -> ReasonFlagsValues { - let b = ku.raw_bytes(); - let unused = 0x80 == 0x80 & b[0]; - let key_compromise = 0x40 == 0x40 & b[0]; - let ca_compromise = 0x20 == 0x20 & b[0]; - let affiliation_changed = 0x10 == 0x10 & b[0]; - let superseded = 0x08 == 0x08 & b[0]; - let cessation_of_operation = 0x04 == 0x04 & b[0]; - let certificate_hold = 0x02 == 0x02 & b[0]; - let remove_from_crl = 0x01 == 0x01 & b[0]; - let aa_compromise = 2 == b.len() && 0x80 == 0x80 & b[1]; - - let retval: ReasonFlagsValues = ReasonFlagsValues { - unused, - key_compromise, - ca_compromise, - affiliation_changed, - superseded, - cessation_of_operation, - certificate_hold, - remove_from_crl, - aa_compromise, - }; - - retval -} diff --git a/x509/src/lib.rs b/x509/src/lib.rs index aadf4848b..be312a3c7 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -15,14 +15,11 @@ extern crate alloc; extern crate std; mod certificate; -pub mod extensions_utils; mod general_name; pub mod pkix_extensions; pub mod pkix_oids; pub mod trust_anchor_format; -pub use crate::{ - certificate::*, extensions_utils::*, general_name::*, pkix_extensions::*, pkix_oids::*, -}; +pub use crate::{certificate::*, general_name::*, pkix_extensions::*, pkix_oids::*}; pub use der::{self, asn1::ObjectIdentifier}; pub use spki::{self, AlgorithmIdentifier, SubjectPublicKeyInfo}; diff --git a/x509/src/pkix_extensions.rs b/x509/src/pkix_extensions.rs index 1d3890658..067d4ae16 100644 --- a/x509/src/pkix_extensions.rs +++ b/x509/src/pkix_extensions.rs @@ -5,14 +5,15 @@ use crate::general_name::GeneralNames; use alloc::vec::Vec; use der::asn1::{ - Any, BitString, ContextSpecific, GeneralizedTime, Ia5String, Null, ObjectIdentifier, - OctetString, UIntBytes, Utf8String, + Any, ContextSpecific, GeneralizedTime, Ia5String, Null, ObjectIdentifier, OctetString, + UIntBytes, Utf8String, }; use der::Header; use der::{ Choice, Decodable, DecodeValue, Decoder, Encodable, EncodeValue, Enumerated, ErrorKind, FixedTag, Sequence, Tag, TagMode, TagNumber, }; +use flagset::{flags, FlagSet}; use x501::attr::AttributeTypeAndValue; use x501::name::RelativeDistinguishedName; @@ -127,24 +128,43 @@ pub struct BasicConstraints { /// [RFC 5280 Section 4.2.1.2]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 pub type SubjectKeyIdentifier<'a> = OctetString<'a>; -/// Key usage extension as defined in [RFC 5280 Section 4.2.1.3] and as identified by the [`PKIX_CE_KEY_USAGE`](constant.PKIX_CE_KEY_USAGE.html) OID. -/// -/// ```text -/// KeyUsage ::= BIT STRING { -/// digitalSignature (0), -/// nonRepudiation (1), -- recent editions of X.509 have -/// -- renamed this bit to contentCommitment -/// keyEncipherment (2), -/// dataEncipherment (3), -/// keyAgreement (4), -/// keyCertSign (5), -/// cRLSign (6), -/// encipherOnly (7), -/// decipherOnly (8) } -/// ``` +flags! { + /// Key usage flags as defined in [RFC 5280 Section 4.2.1.3]. + /// + /// ```text + /// KeyUsage ::= BIT STRING { + /// digitalSignature (0), + /// nonRepudiation (1), -- recent editions of X.509 have + /// -- renamed this bit to contentCommitment + /// keyEncipherment (2), + /// dataEncipherment (3), + /// keyAgreement (4), + /// keyCertSign (5), + /// cRLSign (6), + /// encipherOnly (7), + /// decipherOnly (8) + /// } + /// ``` + /// + /// [RFC 5280 Section 4.2.1.3]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 + #[allow(missing_docs)] + pub enum KeyUsages: u16 { + DigitalSignature = 1 << 0, + NonRepudiation = 1 << 1, + KeyEncipherment = 1 << 2, + DataEncipherment = 1 << 3, + KeyAgreement = 1 << 4, + KeyCertSign = 1 << 5, + CRLSign = 1 << 6, + EncipherOnly = 1 << 7, + DecipherOnly = 1 << 8, + } +} + +/// KeyUsage as defined in [RFC 5280 Section 4.2.1.3] and as identified by the [`PKIX_CE_KEY_USAGE`](constant.PKIX_CE_KEY_USAGE.html) OID. /// /// [RFC 5280 Section 4.2.1.3]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 -pub type KeyUsage<'a> = BitString<'a>; +pub type KeyUsage<'a> = FlagSet; /// Certificate policies extension as defined in [RFC 5280 Section 4.2.1.4] and as identified by the [`PKIX_CE_CERTIFICATE_POLICIES`](constant.PKIX_CE_CERTIFICATE_POLICIES.html) OID. /// @@ -696,23 +716,41 @@ impl<'a> ::der::Sequence<'a> for AuthorityKeyIdentifier<'a> { } } -/// ReasonFlags as defined in [RFC 5280 Section 4.2.1.13] in support of the CRL distribution points extension. -/// -/// ```text -/// ReasonFlags ::= BIT STRING { -/// unused (0), -/// keyCompromise (1), -/// cACompromise (2), -/// affiliationChanged (3), -/// superseded (4), -/// cessationOfOperation (5), -/// certificateHold (6), -/// privilegeWithdrawn (7), -/// aACompromise (8) } -/// ``` +flags! { + /// Reason flags as defined in [RFC 5280 Section 4.2.1.13]. + /// + /// ```text + /// ReasonFlags ::= BIT STRING { + /// unused (0), + /// keyCompromise (1), + /// cACompromise (2), + /// affiliationChanged (3), + /// superseded (4), + /// cessationOfOperation (5), + /// certificateHold (6), + /// privilegeWithdrawn (7), + /// aACompromise (8) } + /// ``` + /// + /// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 + #[allow(missing_docs)] + pub enum Reasons: u16 { + Unused = 1 << 0, + KeyCompromise = 1 << 1, + CaCompromise = 1 << 2, + AffiliationChanged = 1 << 3, + Superseded = 1 << 4, + CessationOfOperation = 1 << 5, + CertificateHold = 1 << 6, + PrivilegeWithdrawn = 1 << 7, + AaCompromise = 1 << 8, + } +} + +/// `ReasonFlags` as defined in [RFC 5280 Section 4.2.1.13] in support of the CRL distribution points extension. /// /// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 -pub type ReasonFlags<'a> = BitString<'a>; +pub type ReasonFlags = FlagSet; /// CRL distribution points extension as defined in [RFC 5280 Section 4.2.1.13] and as identified by the [`PKIX_CE_CRL_DISTRIBUTION_POINTS`](constant.PKIX_CE_CRL_DISTRIBUTION_POINTS.html) OID. /// @@ -741,7 +779,7 @@ pub struct DistributionPoint<'a> { /// reasons [1] ReasonFlags OPTIONAL, //#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] - pub reasons: Option>, + pub reasons: Option, /// cRLIssuer [2] GeneralNames OPTIONAL } //#[asn1(context_specific = "2", optional = "true", tag_mode = "IMPLICIT")] @@ -927,7 +965,7 @@ pub struct IssuingDistributionPoint<'a> { pub only_contains_cacerts: bool, /// onlySomeReasons [3] ReasonFlags OPTIONAL, - pub only_some_reasons: Option>, + pub only_some_reasons: Option, /// indirectCRL [4] BOOLEAN DEFAULT FALSE, pub indirect_crl: bool, @@ -965,7 +1003,7 @@ impl<'a> Decodable<'a> for IssuingDistributionPoint<'a> { } let only_some_reasons = decoder - .context_specific::>(ONLY_SOME_REASONS_TAG, TagMode::Implicit)?; + .context_specific::(ONLY_SOME_REASONS_TAG, TagMode::Implicit)?; let mut indirect_crl = decoder.context_specific::(INDIRECT_TAG, TagMode::Implicit)?; diff --git a/x509/tests/pkix_extensions.rs b/x509/tests/pkix_extensions.rs index 262147468..5f15382fa 100644 --- a/x509/tests/pkix_extensions.rs +++ b/x509/tests/pkix_extensions.rs @@ -278,20 +278,8 @@ fn decode_cert() { assert_eq!(ext.extn_id.to_string(), PKIX_CE_KEY_USAGE.to_string()); assert_eq!(ext.critical, true); - use x509::extensions_utils::KeyUsageValues; let ku = KeyUsage::from_der(ext.extn_value).unwrap(); - let kuv = x509::extensions_utils::get_key_usage_values(&ku); - let mut count = 0; - for v in kuv { - if 0 == count { - assert_eq!(v, KeyUsageValues::KeyCertSign); - } else if 1 == count { - assert_eq!(v, KeyUsageValues::CRLSign); - } else { - panic!("Should not occur"); - } - count += 1; - } + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku); let reencoded = ku.to_vec().unwrap(); assert_eq!(ext.extn_value, reencoded); @@ -757,20 +745,8 @@ fn decode_cert() { } else if 2 == counter { assert_eq!(ext.extn_id.to_string(), PKIX_CE_KEY_USAGE.to_string()); assert_eq!(ext.critical, true); - use x509::extensions_utils::KeyUsageValues; let ku = KeyUsage::from_der(ext.extn_value).unwrap(); - let kuv = x509::extensions_utils::get_key_usage_values(&ku); - let mut count = 0; - for v in kuv { - if 0 == count { - assert_eq!(v, KeyUsageValues::KeyCertSign); - } else if 1 == count { - assert_eq!(v, KeyUsageValues::CRLSign); - } else { - panic!("Should not occur"); - } - count += 1; - } + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku); } else if 3 == counter { assert_eq!( ext.extn_id.to_string(), @@ -1008,16 +984,16 @@ fn decode_idp() { assert!(idp.only_some_reasons.is_some()); assert!(idp.distribution_point.is_some()); - let rfv = get_reason_flags_values(&idp.only_some_reasons.unwrap()); - assert_eq!(true, rfv.unused); - assert_eq!(false, rfv.key_compromise); - assert_eq!(false, rfv.ca_compromise); - assert_eq!(true, rfv.affiliation_changed); - assert_eq!(true, rfv.superseded); - assert_eq!(true, rfv.cessation_of_operation); - assert_eq!(true, rfv.certificate_hold); - assert_eq!(true, rfv.remove_from_crl); - assert_eq!(true, rfv.aa_compromise); + assert_eq!( + Reasons::Unused + | Reasons::AffiliationChanged + | Reasons::Superseded + | Reasons::CessationOfOperation + | Reasons::CertificateHold + | Reasons::PrivilegeWithdrawn + | Reasons::AaCompromise, + idp.only_some_reasons.unwrap() + ); // 930 360: SEQUENCE { // 934 353: [0] {