diff --git a/examples/ed25519_cert.rs b/examples/ed25519_cert.rs index a2d961e..27c2784 100644 --- a/examples/ed25519_cert.rs +++ b/examples/ed25519_cert.rs @@ -56,9 +56,7 @@ fn main() { &Capabilities::default_actor(), ) .unwrap(); - let data = csr.clone().to_der().unwrap(); - dbg!(&data); let file_name_with_extension = "cert.csr"; #[cfg(not(target_arch = "wasm32"))] std::fs::write(file_name_with_extension, &data).unwrap(); diff --git a/examples/ed25519_from_der.rs b/examples/ed25519_from_der.rs new file mode 100644 index 0000000..643e19c --- /dev/null +++ b/examples/ed25519_from_der.rs @@ -0,0 +1,229 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![allow(unused)] + +use std::str::FromStr; +use std::time::Duration; + +use der::asn1::{BitString, Ia5String, Uint, UtcTime}; +use der::{Decode, Encode}; +use ed25519_dalek::{Signature as Ed25519DalekSignature, Signer, SigningKey, VerifyingKey}; +use polyproto::certs::capabilities::Capabilities; +use polyproto::certs::idcert::IdCert; +use polyproto::certs::PublicKeyInfo; +use polyproto::key::{PrivateKey, PublicKey}; +use polyproto::signature::Signature; +use rand::rngs::OsRng; +use spki::{AlgorithmIdentifierOwned, ObjectIdentifier, SignatureBitStringEncoding}; +use thiserror::Error; +use x509_cert::attr::Attributes; +use x509_cert::name::RdnSequence; +use x509_cert::request::CertReq; +use x509_cert::time::{Time, Validity}; +use x509_cert::Certificate; + +/// The following example uses the same setup as in ed25519_basic.rs, but in its main method, it +/// creates a certificate signing request (CSR) and writes it to a file. The CSR is created from a +/// polyproto ID CSR, which is a wrapper around a PKCS #10 CSR. +/// +/// If you have openssl installed, you can inspect the CSR by running: +/// +/// ```sh +/// openssl req -in cert.csr -verify -inform der +/// ``` +/// +/// After that, the program creates an ID-Cert from the given ID-CSR. The `cert.der` file can also +/// be validated using openssl: +/// +/// ```sh +/// openssl x509 -in cert.der -text -noout -inform der +/// ``` + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn main() { + let mut csprng = rand::rngs::OsRng; + let priv_key = Ed25519PrivateKey::gen_keypair(&mut csprng); + println!("Private Key is: {:?}", priv_key.key.to_bytes()); + println!("Public Key is: {:?}", priv_key.public_key.key.to_bytes()); + println!(); + + let csr = polyproto::certs::idcsr::IdCsr::new( + &RdnSequence::from_str("CN=flori,DC=www,DC=polyphony,DC=chat,UID=flori@polyphony.chat,uniqueIdentifier=client1").unwrap(), + &priv_key, + &Capabilities::default_actor(), + ) + .unwrap(); + + let data = csr.clone().to_der().unwrap(); + let file_name_with_extension = "cert.csr"; + #[cfg(not(target_arch = "wasm32"))] + std::fs::write(file_name_with_extension, &data).unwrap(); + + let cert = IdCert::from_actor_csr( + csr, + &priv_key, + Uint::new(&8932489u64.to_be_bytes()).unwrap(), + RdnSequence::from_str( + "CN=root,DC=www,DC=polyphony,DC=chat,UID=root@polyphony.chat,uniqueIdentifier=root", + ) + .unwrap(), + Validity { + not_before: Time::UtcTime( + UtcTime::from_unix_duration(Duration::from_secs(10)).unwrap(), + ), + not_after: Time::UtcTime( + UtcTime::from_unix_duration(Duration::from_secs(1000)).unwrap(), + ), + }, + ) + .unwrap(); + let data = cert.clone().to_der().unwrap(); + let cert_from_der = IdCert::from_der(data).unwrap(); + assert_eq!(cert_from_der, cert) +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(test))] +fn main() {} + +// As mentioned in the README, we start by implementing the signature trait. + +// Here, we start by defining the signature type, which is a wrapper around the signature type from +// the ed25519-dalek crate. +#[derive(Debug, PartialEq, Eq, Clone)] +struct Ed25519Signature { + signature: Ed25519DalekSignature, + algorithm: AlgorithmIdentifierOwned, +} + +// We implement the Signature trait for our signature type. +impl Signature for Ed25519Signature { + // We define the signature type from the ed25519-dalek crate as the associated type. + type Signature = Ed25519DalekSignature; + + // This is straightforward: we return a reference to the signature. + fn as_signature(&self) -> &Self::Signature { + &self.signature + } + + // The algorithm identifier for a given signature implementation is constant. We just need + // to define it here. + fn algorithm_identifier() -> AlgorithmIdentifierOwned { + AlgorithmIdentifierOwned { + // This is the OID for Ed25519. It is defined in the IANA registry. + oid: ObjectIdentifier::from_str("1.3.101.112").unwrap(), + // For this example, we don't need or want any parameters. + parameters: None, + } + } + + fn from_bitstring(signature: &[u8]) -> Self { + let mut signature_vec = signature.to_vec(); + signature_vec.resize(64, 0); + let signature_array: [u8; 64] = { + let mut array = [0; 64]; + array.copy_from_slice(&signature_vec[..]); + array + }; + Self { + signature: Ed25519DalekSignature::from_bytes(&signature_array), + algorithm: Self::algorithm_identifier(), + } + } +} + +// The `SignatureBitStringEncoding` trait is used to convert a signature to a bit string. We implement +// it for our signature type. +impl SignatureBitStringEncoding for Ed25519Signature { + fn to_bitstring(&self) -> der::Result { + BitString::from_bytes(&self.as_signature().to_bytes()) + } +} + +// Next, we implement the key traits. We start by defining the private key type. +#[derive(Debug, Clone, PartialEq, Eq)] +struct Ed25519PrivateKey { + // Defined below + public_key: Ed25519PublicKey, + // The private key from the ed25519-dalek crate + key: SigningKey, +} + +impl PrivateKey for Ed25519PrivateKey { + type PublicKey = Ed25519PublicKey; + + // Return a reference to the public key + fn pubkey(&self) -> &Self::PublicKey { + &self.public_key + } + + // Signs a message. The beauty of having to wrap the ed25519-dalek crate is that we can + // harness all of its functionality, such as the `sign` method. + fn sign(&self, data: &[u8]) -> Ed25519Signature { + let signature = self.key.sign(data); + Ed25519Signature { + signature, + algorithm: self.algorithm_identifier(), + } + } +} + +impl Ed25519PrivateKey { + // Let's also define a handy method to generate a key pair. + pub fn gen_keypair(csprng: &mut OsRng) -> Self { + let key = SigningKey::generate(csprng); + let public_key = Ed25519PublicKey { + key: key.verifying_key(), + }; + Self { public_key, key } + } +} + +// Same thing as above for the public key type. +#[derive(Debug, Clone, PartialEq, Eq)] +struct Ed25519PublicKey { + // The public key type from the ed25519-dalek crate + key: VerifyingKey, +} + +impl PublicKey for Ed25519PublicKey { + // Verifies a signature. We use the `verify_strict` method from the ed25519-dalek crate. + // This method is used to mitigate weak key forgery. + fn verify_signature( + &self, + signature: &Ed25519Signature, + data: &[u8], + ) -> Result<(), polyproto::errors::composite::PublicKeyError> { + match self.key.verify_strict(data, signature.as_signature()) { + Ok(_) => Ok(()), + Err(_) => Err(polyproto::errors::composite::PublicKeyError::BadSignature), + } + } + + // Returns the public key info. Public key info is used to encode the public key in a + // certificate or a CSR. It is named after the `SubjectPublicKeyInfo` type from the X.509 + // standard, and thus includes the information needed to encode the public key in a certificate + // or a CSR. + fn public_key_info(&self) -> PublicKeyInfo { + PublicKeyInfo { + algorithm: Ed25519Signature::algorithm_identifier(), + public_key_bitstring: BitString::from_bytes(&self.key.to_bytes()).unwrap(), + } + } + + fn from_public_key_info(public_key_info: PublicKeyInfo) -> Self { + let mut key_vec = public_key_info.public_key_bitstring.raw_bytes().to_vec(); + key_vec.resize(32, 0); + let signature_array: [u8; 32] = { + let mut array = [0; 32]; + array.copy_from_slice(&key_vec[..]); + array + }; + Self { + key: VerifyingKey::from_bytes(&signature_array).unwrap(), + } + } +} diff --git a/src/certs/capabilities/basic_constraints.rs b/src/certs/capabilities/basic_constraints.rs index 5feb568..36104b5 100644 --- a/src/certs/capabilities/basic_constraints.rs +++ b/src/certs/capabilities/basic_constraints.rs @@ -11,6 +11,7 @@ use x509_cert::attr::Attribute; use x509_cert::ext::Extension; use crate::errors::base::{ConstraintError, InvalidInput}; +use crate::errors::composite::ConversionError; use super::OID_BASIC_CONSTRAINTS; @@ -36,7 +37,7 @@ impl From for ObjectIdentifier { } impl TryFrom for BasicConstraints { - type Error = InvalidInput; + type Error = ConversionError; /// Performs the conversion. /// /// Fails, if the input attribute @@ -51,21 +52,20 @@ impl TryFrom for BasicConstraints { fn try_from(value: Attribute) -> Result { // Basic input validation. Check OID of Attribute and length of the "values" SetOfVec provided. if value.oid.to_string() != super::OID_BASIC_CONSTRAINTS { - return Err(Self::Error::IncompatibleVariantForConversion { - reason: format!( + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + format!( "OID of value does not match any of OID_BASIC_CONSTRAINTS. Found OID {}", value.oid ), - }); + ))); } let values = value.values; if values.len() > 2usize { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: format!( - "Expected 1 or 2 values for BasicConstraints, found {}", - values.len() - ), - }); + return Err(ConversionError::InvalidInput(InvalidInput::Length { + min_length: 1, + max_length: 2, + actual_length: values.len().to_string(), + })); } let mut num_ca = 0u8; let mut num_path_length = 0u8; @@ -79,7 +79,7 @@ impl TryFrom for BasicConstraints { num_ca += 1; ca = any_to_bool(value.clone())?; } else { - return Err(InvalidInput::IncompatibleVariantForConversion { reason: "Encountered > 1 Boolean tags. Expected 1 Boolean tag.".to_string() }); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed("Encountered > 1 Boolean tags. Expected 1 Boolean tag.".to_string()))); } } Tag::Integer => { @@ -88,54 +88,57 @@ impl TryFrom for BasicConstraints { num_path_length += 1; path_length = Some(any_to_u64(value.clone())?); } else { - return Err(InvalidInput::IncompatibleVariantForConversion { reason: "Encountered > 1 Integer tags. Expected 0 or 1 Integer tags.".to_string() }); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed("Encountered > 1 Integer tags. Expected 0 or 1 Integer tags.".to_string()))); } } - _ => return Err(InvalidInput::IncompatibleVariantForConversion { reason: format!("Encountered unexpected tag {:?}, when tag should have been either Boolean or Integer", value.tag()) }), + _ => return Err(ConversionError::InvalidInput(InvalidInput::Malformed(format!("Encountered unexpected tag {:?}, when tag should have been either Boolean or Integer", value.tag())))), } } if num_ca == 0 { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: "Expected 1 Boolean tag, found 0".to_string(), - }); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + "Expected 1 Boolean tag, found 0".to_string(), + ))); } Ok(BasicConstraints { ca, path_length }) } } -impl From for Attribute { - fn from(value: BasicConstraints) -> Self { +impl TryFrom for Attribute { + type Error = ConversionError; + fn try_from(value: BasicConstraints) -> Result { let mut sov = SetOfVec::new(); - sov.insert(Any::new(der::Tag::Boolean, match value.ca { - true => vec![0xff], - false => vec![0x00], - }, - ).expect("Error occurred when converting BasicConstraints bool to der::Any. Please report this crash at https://github.com/polyphony-chat/polyproto.")).expect("Error occurred when inserting into der::Any to SetOfVec. Please report this crash at https://github.com/polyphony-chat/polyproto"); + sov.insert(Any::new( + der::Tag::Boolean, + match value.ca { + true => vec![0xff], + false => vec![0x00], + }, + )?)?; if let Some(length) = value.path_length { - sov.insert(Any::new(der::Tag::Integer, length.to_be_bytes()).expect("Error occurred when converting BasicConstraints u64 to der::Any. Please report this crash at https://github.com/polyphony-chat/polyproto.")). - expect("Error occurred when inserting into der::Any to SetOfVec. Please report this crash at https://github.com/polyphony-chat/polyproto"); + sov.insert(Any::new(der::Tag::Integer, length.to_be_bytes())?)?; } - Attribute { + Ok(Attribute { oid: value.into(), values: sov, - } + }) } } -impl From for Extension { - fn from(value: BasicConstraints) -> Self { - let attribute = Attribute::from(value); - Extension { +impl TryFrom for Extension { + type Error = ConversionError; + fn try_from(value: BasicConstraints) -> Result { + let attribute = Attribute::try_from(value)?; + Ok(Extension { extn_id: value.into(), critical: true, // This should be infallible. We are converting a boolean and a 64bit integer into DER and creating an OctetString from it. - extn_value: OctetString::new(attribute.values.to_der().expect("Error occurred when converting BasicConstraints u64 to DER. Please report this crash at https://github.com/polyphony-chat/polyproto.")).expect("Error occurred when converting BasicConstraints u64 to OctetString. Please report this crash at https://github.com/polyphony-chat/polyproto."), - } + extn_value: OctetString::new(attribute.values.to_der()?)?, + }) } } impl TryFrom for BasicConstraints { - type Error = InvalidInput; + type Error = ConversionError; /// Performs the conversion. Assumes, that the order of the bool value and the /// `int`/`none` value is **not** important. @@ -147,21 +150,18 @@ impl TryFrom for BasicConstraints { #[allow(unreachable_patterns)] if value.critical && !matches!(value.extn_id.to_string().as_str(), OID_BASIC_CONSTRAINTS) { // Error if we encounter a "critical" X.509 extension which we do not know of - return Err(InvalidInput::UnknownCriticalExtension { oid: value.extn_id }); + return Err(ConversionError::UnknownCriticalExtension { oid: value.extn_id }); } // If the Extension is a valid BasicConstraint, the octet string will contain DER ANY values // in a DER SET OF type - let sov: SetOfVec = match SetOfVec::from_der(value.extn_value.as_bytes()) { - Ok(sov) => sov, - Err(e) => return Err(InvalidInput::DerError(e)), - }; + let sov: SetOfVec = SetOfVec::from_der(value.extn_value.as_bytes())?; if sov.len() > 2 { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: format!( + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + format!( "This x509_cert::Extension has {} values stored. Expected a maximum of 2 values", sov.len() ), - }); + ))); } let mut bool_encounters = 0u8; let mut int_encounters = 0u8; @@ -182,10 +182,14 @@ impl TryFrom for BasicConstraints { null_encounters += 1; path_length = None; }, - _ => return Err(InvalidInput::IncompatibleVariantForConversion { reason: format!("Found {:?} in value, which does not match expected [Tag::Boolean, Tag::Integer, Tag::Null]", item.tag().to_string()) }), + _ => return Err(ConversionError::InvalidInput(InvalidInput::Malformed(format!("Encountered unexpected tag {:?}, when tag should have been either Boolean, Integer or Null", item.tag())))), } if bool_encounters > 1 || int_encounters > 1 || null_encounters > 1 { - return Err(InvalidInput::ConstraintError(ConstraintError::OutOfBounds { lower: 0, upper: 1, actual: 2.to_string(), reason: Some("Expected 0 or 1 Boolean, Integer or Null values, found more than one of one of these variants".to_string()) })); + return Err(ConversionError::InvalidInput(InvalidInput::Length { + min_length: 0, + max_length: 1, + actual_length: 2.to_string(), + })); } } Ok(BasicConstraints { ca, path_length }) @@ -225,6 +229,8 @@ fn any_to_u64(value: Any) -> Result { } } +#[cfg(test)] +#[allow(clippy::unwrap_used)] #[cfg(test)] mod test { use super::*; @@ -236,8 +242,7 @@ mod test { ca: true, path_length: Some(0u64), }; - let extension = Extension::from(basic_constraints); - dbg!(extension); + let _extension = Extension::try_from(basic_constraints).unwrap(); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] @@ -247,7 +252,7 @@ mod test { ca: true, path_length: Some(u64::MAX), }; - let extension = Extension::from(basic_constraints); + let extension = Extension::try_from(basic_constraints).unwrap(); #[allow(clippy::unwrap_used)] let from_extension = BasicConstraints::try_from(extension).unwrap(); assert_eq!(from_extension, basic_constraints); @@ -256,7 +261,7 @@ mod test { ca: true, path_length: None, }; - let extension = Extension::from(basic_constraints); + let extension = Extension::try_from(basic_constraints).unwrap(); #[allow(clippy::unwrap_used)] let from_extension = BasicConstraints::try_from(extension).unwrap(); assert_eq!(from_extension, basic_constraints); @@ -265,7 +270,7 @@ mod test { ca: false, path_length: Some(u64::MAX), }; - let extension = Extension::from(basic_constraints); + let extension = Extension::try_from(basic_constraints).unwrap(); #[allow(clippy::unwrap_used)] let from_extension = BasicConstraints::try_from(extension).unwrap(); assert_eq!(from_extension, basic_constraints); @@ -274,7 +279,7 @@ mod test { ca: false, path_length: None, }; - let extension = Extension::from(basic_constraints); + let extension = Extension::try_from(basic_constraints).unwrap(); #[allow(clippy::unwrap_used)] let from_extension = BasicConstraints::try_from(extension).unwrap(); assert_eq!(from_extension, basic_constraints); diff --git a/src/certs/capabilities/key_usage.rs b/src/certs/capabilities/key_usage.rs index 64387a1..c034def 100644 --- a/src/certs/capabilities/key_usage.rs +++ b/src/certs/capabilities/key_usage.rs @@ -4,13 +4,14 @@ use std::str::FromStr; -use der::asn1::{OctetString, SetOfVec}; -use der::{Any, Encode, Tag, Tagged}; +use der::asn1::{BitString, OctetString, SetOfVec}; +use der::{Any, Decode, Encode, Tag, Tagged}; use spki::ObjectIdentifier; use x509_cert::attr::Attribute; use x509_cert::ext::Extension; use crate::errors::base::InvalidInput; +use crate::errors::composite::ConversionError; use super::*; @@ -19,259 +20,309 @@ use super::*; /// restriction might be employed when a key that could be used for more than one operation is to /// be restricted. See pub enum KeyUsage { - /// This purpose is set to true when the subject public key is used for verifying digital + /// This purpose is set when the subject public key is used for verifying digital /// signatures, other than signatures on certificates (`key_cert_sign`) and CRLs (`crl_sign`). - DigitalSignature(bool), - /// This purpose is set to true when the subject public key is used for verifying signatures on - /// certificate revocation lists. - CrlSign(bool), - /// This purpose is set to true when the subject public key is used for verifying digital + DigitalSignature = 1, + /// This purpose is set when the subject public key is used for verifying digital /// signatures, other than signatures on certificates (`key_cert_sign`) and CRLs (`crl_sign`). /// It is used to provide a non-repudiation service that protects against the signing entity /// falsely denying some action. In the case of later conflict, a reliable third party may /// determine the authenticity of the signed data. This was called `non_repudiation` in older /// revisions of the X.509 specification. - ContentCommitment(bool), - /// This purpose is set to true when the subject public key is used for enciphering private or + ContentCommitment = 2, + /// This purpose is set when the subject public key is used for enciphering private or /// secret keys. - KeyEncipherment(bool), - /// This purpose is set to true when the subject public key is used for directly enciphering raw + KeyEncipherment = 4, + /// This purpose is set when the subject public key is used for directly enciphering raw /// user data without the use of an intermediate symmetric cipher. - DataEncipherment(bool), - /// This purpose is set to true when the subject public key is used for key agreement. For + DataEncipherment = 8, + /// This purpose is set when the subject public key is used for key agreement. For /// example, when a Diffie-Hellman key is to be used for key management, then this purpose is - /// set to true. - KeyAgreement(bool), - /// This purpose is set to true when the subject public key is used for verifying signatures on + /// set. + KeyAgreement = 16, + /// This purpose is set when the subject public key is used for verifying signatures on /// public key certificates. If this purpose is set to true then ca must be true in the /// `BasicConstraints` extension. - KeyCertSign(bool), - /// When this purposes is set to true and the `key_agreement` purpose is also set, the subject + KeyCertSign = 32, + /// This purpose is set when the subject public key is used for verifying signatures on + /// certificate revocation lists. + CrlSign = 64, + /// When this purpose is set and the `key_agreement` purpose is also set, the subject /// public key may be used only for enciphering data while performing key agreement. The - /// `KeyAgreement` capability must be set to `true` for this. - EncipherOnly(bool), - /// When this purposes is set to true and the `key_agreement` purpose is also set, the subject + /// `KeyAgreement` capability must be set for this. + EncipherOnly = 128, + /// When this purpose is set and the `key_agreement` purpose is also set, the subject /// public key may be used only for deciphering data while performing key agreement. The - /// `KeyAgreement` capability must be set to `true` for this. - DecipherOnly(bool), + /// `KeyAgreement` capability must be set for this. + DecipherOnly = 256, } -impl From for bool { - fn from(value: KeyUsage) -> Self { +impl TryFrom for KeyUsage { + type Error = ConversionError; + + fn try_from(value: u32) -> Result { + if value > 256 { + return Err(ConversionError::InvalidInput(InvalidInput::Length { + min_length: 0, + max_length: 256, + actual_length: value.to_string(), + })); + } match value { - KeyUsage::DigitalSignature(val) => val, - KeyUsage::CrlSign(val) => val, - KeyUsage::ContentCommitment(val) => val, - KeyUsage::KeyEncipherment(val) => val, - KeyUsage::DataEncipherment(val) => val, - KeyUsage::KeyAgreement(val) => val, - KeyUsage::KeyCertSign(val) => val, - KeyUsage::EncipherOnly(val) => val, - KeyUsage::DecipherOnly(val) => val, + x if x == KeyUsage::DigitalSignature as u32 => Ok(KeyUsage::DigitalSignature), + x if x == KeyUsage::ContentCommitment as u32 => Ok(KeyUsage::ContentCommitment), + x if x == KeyUsage::KeyEncipherment as u32 => Ok(KeyUsage::KeyEncipherment), + x if x == KeyUsage::DataEncipherment as u32 => Ok(KeyUsage::DataEncipherment), + x if x == KeyUsage::KeyAgreement as u32 => Ok(KeyUsage::KeyAgreement), + x if x == KeyUsage::KeyCertSign as u32 => Ok(KeyUsage::KeyCertSign), + x if x == KeyUsage::CrlSign as u32 => Ok(KeyUsage::CrlSign), + x if x == KeyUsage::EncipherOnly as u32 => Ok(KeyUsage::EncipherOnly), + x if x == KeyUsage::DecipherOnly as u32 => Ok(KeyUsage::DecipherOnly), + _ => Err(ConversionError::InvalidInput(InvalidInput::Malformed( + "Input cannot be matched to any of the KeyUsage variants".to_string(), + ))), } } } -impl TryFrom for KeyUsage { - type Error = InvalidInput; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +/// The KeyUsages struct is a collection of KeyUsage variants. +pub struct KeyUsages { + /// Vector of KeyUsage variants. + pub key_usages: Vec, +} - /// Performs the conversion. +impl KeyUsages { + /// Creates a new KeyUsages struct from a slice of KeyUsage variants. + pub fn new(key_usages: &[KeyUsage]) -> Self { + KeyUsages { + key_usages: key_usages.to_vec(), + } + } + + /// Converts a byte slice to a KeyUsages struct, given that the byte slice is a bitstring + /// representing the KeyUsages. /// - /// Fails, if the input attribute does not contain exactly one value, or if the input attribute - /// does not contain a boolean value. Also fails if the OID of the attribute does not match any - /// known KeyUsage variant. - fn try_from(value: Attribute) -> Result { - // PRETTYFYME: I know this is a bit of a mess, but it works. If anyone wants to make it - // prettier, feel free to do so. + /// RFC 5280 says: + /// ```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) } + /// ``` + pub fn from_bitstring(bitstring: BitString) -> Result { + /* Below, we are doing some operations on bits. RFC 5280 says: + 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) } - // Check if the attribute contains exactly one value - if value.values.len() != 1usize { - return Err(InvalidInput::IncompatibleVariantForConversion { reason: "This attribute does not store exactly one value, as would be expected for a KeyUsage attribute".to_string() }); + It is now our task to check, which bits (0 - 8) are set, and to construct KeyUsage variants + from this information. + Note, that BitStrings represent the bits they store in big endian order, meaning that the + most significant bit (MSB) is the first bit in the BitString. + */ + let mut starting_number = 1; + let mut key_usages = Vec::new(); + // We iterate over all bits, check if the current bit is set and try to convert the + // current value of starting_number to a KeyUsage variant. On every iteration, we divide + // starting_number by two, until it equals 1 and thus cannot be divided any further. + for bit in bitstring.raw_bytes().iter() { + if *bit == 0 { + // If the bit is 0, we can skip the current iteration, increment the starting_number + // and continue with the next iteration. + multiply_starting_number(&mut starting_number); + continue; + } + if *bit != 1 { + // If the bit is not 0 or 1, we are likely looking at the "unused bits" byte of the + // BitString. We can safely ignore this byte. + continue; + } + key_usages.push(KeyUsage::try_from(starting_number)?); + if starting_number == 256 { + // Stop the loop if starting_number is already 256. + break; + } + multiply_starting_number(&mut starting_number); } - let sov = value.values.get(0); + Ok(KeyUsages::new(&key_usages)) + } - // The first value inside the Attribute is a SetOfVec. We need to look inside the SetOfVec to - // find the actual attribute we are interested in. - if let Some(inner_value) = sov { - if inner_value.tag() != Tag::Boolean { - return Err(InvalidInput::IncompatibleVariantForConversion { reason: format!("Only Any objects with boolean tags can be converted to a KeyUsage enum variant. Expected Tag::Boolean, found {:?}", inner_value.tag()) }); + /// Converts the KeyUsages to a bitstring in little endian order. + pub fn to_le_bits(mut self) -> [bool; 9] { + self.key_usages.sort(); + self.key_usages.dedup(); + let mut bit_vec = [false; 9]; + for item in self.key_usages.into_iter() { + match item { + KeyUsage::DigitalSignature => bit_vec[0] = true, + KeyUsage::ContentCommitment => bit_vec[1] = true, + KeyUsage::KeyEncipherment => bit_vec[2] = true, + KeyUsage::DataEncipherment => bit_vec[3] = true, + KeyUsage::KeyAgreement => bit_vec[4] = true, + KeyUsage::KeyCertSign => bit_vec[5] = true, + KeyUsage::CrlSign => bit_vec[6] = true, + KeyUsage::EncipherOnly => bit_vec[7] = true, + KeyUsage::DecipherOnly => bit_vec[8] = true, } - // This is how booleans are apparently encoded in ASN.1 - let boolean_value = match inner_value.value() { - &[0x00] => false, - &[0xFF] | &[0x01] => true, - _ => { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: "Encountered unexpected value for Boolean tag".to_string(), - }); - } - }; - // Now we have to match the OID of the attribute to the known KeyUsage variants - return Ok(match value.oid.to_string().as_str() { - super::OID_KEY_USAGE_CONTENT_COMMITMENT => KeyUsage::ContentCommitment(boolean_value), - super::OID_KEY_USAGE_CRL_SIGN => KeyUsage::CrlSign(boolean_value), - super::OID_KEY_USAGE_DATA_ENCIPHERMENT => KeyUsage::DataEncipherment(boolean_value), - super::OID_KEY_USAGE_DECIPHER_ONLY => KeyUsage::DecipherOnly(boolean_value), - super::OID_KEY_USAGE_DIGITAL_SIGNATURE => KeyUsage::DigitalSignature(boolean_value), - super::OID_KEY_USAGE_ENCIPHER_ONLY => KeyUsage::EncipherOnly(boolean_value), - super::OID_KEY_USAGE_KEY_AGREEMENT => KeyUsage::KeyAgreement(boolean_value), - #[allow(unreachable_patterns)] // cargo thinks the below pattern is unreachable. - super::OID_KEY_USAGE_KEY_CERT_SIGN => KeyUsage::KeyCertSign(boolean_value), - super::OID_KEY_USAGE_KEY_ENCIPHERMENT => KeyUsage::KeyEncipherment(boolean_value), - // If the OID does not match any known KeyUsage variant, we return an error - _ => { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: format!("The OID of the attribute does not match any known KeyUsage variant. Found OID \"{}\"", value.oid) - }, - ) - } - }); } - // If the attribute does not contain a value, we return an error - Err(InvalidInput::IncompatibleVariantForConversion { - reason: "The attribute does not contain a value".to_string(), - }) + bit_vec } -} -impl TryFrom for KeyUsage { - type Error = InvalidInput; + /// Converts the KeyUsages to a bitstring in big endian order. + pub fn to_be_bits(self) -> [bool; 9] { + let mut bit_vec = self.to_le_bits(); + bit_vec.reverse(); + bit_vec + } - /// Performs the conversion. - /// - /// Fails, if the input attribute does not contain a boolean value. Also fails if the OID of - /// the attribute does not match any known KeyUsage variant, especially when the unknown OID is - /// marked as "critical" - fn try_from(value: Extension) -> Result { - #[allow(unreachable_patterns)] - if value.critical - && !matches!( - value.extn_id.to_string().as_str(), - OID_KEY_USAGE_CONTENT_COMMITMENT - | OID_KEY_USAGE_CRL_SIGN - | OID_KEY_USAGE_DATA_ENCIPHERMENT - | OID_KEY_USAGE_DATA_ENCIPHERMENT - | OID_KEY_USAGE_DECIPHER_ONLY - | OID_KEY_USAGE_DIGITAL_SIGNATURE - | OID_KEY_USAGE_ENCIPHER_ONLY - | OID_KEY_USAGE_KEY_AGREEMENT - | OID_KEY_USAGE_KEY_CERT_SIGN - | OID_KEY_USAGE_KEY_ENCIPHERMENT - ) - { - // Error if we encounter a "critical" X.509 extension which we do not know of - return Err(InvalidInput::UnknownCriticalExtension { oid: value.extn_id }); + /// Converts the KeyUsages to a [BitString]. + pub fn to_bitstring(self) -> BitString { + let bits = self.to_be_bits(); + let mut bytes = bits + .iter() + .map(|x| if *x { 1 } else { 0 }) + .collect::>(); + while bytes[0] == 0 { + bytes.remove(0); + } + let mut unused_bits: u8 = 0; + while bytes.len() % 8 != 0 { + bytes.push(0); + unused_bits += 1; } + BitString::new(unused_bits, bytes) + .expect("Error when converting KeyUsages to BitString. Please report this error to https://github.com/polyphony-chat/polyproto") + } +} + +impl From for BitString { + fn from(value: KeyUsages) -> Self { + value.to_bitstring() + } +} + +impl TryFrom for KeyUsages { + type Error = ConversionError; - let boolean_value = match value.extn_value.as_bytes() { - &[0x00] => false, - &[0xFF] | &[0x01] => true, + fn try_from(value: Attribute) -> Result { + if value.tag() != Tag::BitString { + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + format!("Expected BitString, found {}", value.tag(),), + ))); + } + match value.values.len() { + 0 => return Ok(KeyUsages::new(&[])), + 1 => (), _ => { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: "Encountered unexpected value for Boolean tag".to_string(), - }); + return Err(ConversionError::InvalidInput(InvalidInput::Length { + min_length: 0, + max_length: 1, + actual_length: value.values.len().to_string(), + })); } }; - // Now we have to match the OID of the attribute to the known KeyUsage variants - #[allow(unreachable_patterns)] - return Ok(match value.extn_id.to_string().as_str() { - super::OID_KEY_USAGE_CONTENT_COMMITMENT => KeyUsage::ContentCommitment(boolean_value), - super::OID_KEY_USAGE_CRL_SIGN => KeyUsage::CrlSign(boolean_value), - super::OID_KEY_USAGE_DATA_ENCIPHERMENT => KeyUsage::DataEncipherment(boolean_value), - super::OID_KEY_USAGE_DECIPHER_ONLY => KeyUsage::DecipherOnly(boolean_value), - super::OID_KEY_USAGE_DIGITAL_SIGNATURE => KeyUsage::DigitalSignature(boolean_value), - super::OID_KEY_USAGE_ENCIPHER_ONLY => KeyUsage::EncipherOnly(boolean_value), - super::OID_KEY_USAGE_KEY_AGREEMENT => KeyUsage::KeyAgreement(boolean_value), - super::OID_KEY_USAGE_KEY_CERT_SIGN => KeyUsage::KeyCertSign(boolean_value), - super::OID_KEY_USAGE_KEY_ENCIPHERMENT => KeyUsage::KeyEncipherment(boolean_value), - // If the OID does not match any known KeyUsage variant, we return an error - _ => { - return Err(InvalidInput::IncompatibleVariantForConversion { - reason: format!("The OID of the attribute does not match any known KeyUsage variant. Found OID \"{}\"", value.extn_id) - }, - ) - } - }); + let inner_value = value.values.get(0).expect("Illegal state. Please report this error to https://github.com/polyphony-chat/polyproto"); + KeyUsages::from_bitstring(BitString::from_der(inner_value.value())?) } } -impl From for Any { - fn from(value: KeyUsage) -> Self { - Any::new(der::Tag::Boolean,match bool::from(value) { - true => vec![0xff], - false => vec![0x00], - }, - ).expect("Error occurred when converting BasicConstraints bool to der::Any. Please report this crash at https://github.com/polyphony-chat/polyproto.") +fn multiply_starting_number(number: &mut u32) { + if !*number == 256 { + *number *= 2; } } -impl From for ObjectIdentifier { - fn from(value: KeyUsage) -> Self { - let result = match value { - KeyUsage::DigitalSignature(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_DIGITAL_SIGNATURE) - } - KeyUsage::CrlSign(_) => ObjectIdentifier::from_str(super::OID_KEY_USAGE_CRL_SIGN), - KeyUsage::ContentCommitment(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_CONTENT_COMMITMENT) - } - KeyUsage::KeyEncipherment(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_KEY_ENCIPHERMENT) - } - KeyUsage::DataEncipherment(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_DATA_ENCIPHERMENT) - } - KeyUsage::KeyAgreement(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_KEY_AGREEMENT) - } - KeyUsage::KeyCertSign(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_KEY_CERT_SIGN) - } - KeyUsage::EncipherOnly(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_ENCIPHER_ONLY) - } - KeyUsage::DecipherOnly(_) => { - ObjectIdentifier::from_str(super::OID_KEY_USAGE_DECIPHER_ONLY) - } - }; - result.expect("Error occurred when converting KeyUsage enum to ObjectIdentifier. Please report this crash at https://github.com/polyphony-chat/polyproto.") +impl TryFrom for KeyUsages { + type Error = ConversionError; + + fn try_from(value: Extension) -> Result { + if value.extn_id.to_string().as_str() != OID_KEY_USAGE { + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + format!( + "Expected OID {} for KeyUsages, found OID {}", + OID_KEY_USAGE, value.extn_id + ), + ))); + } + let any = Any::from_der(value.extn_value.as_bytes())?; + KeyUsages::from_bitstring(BitString::from_bytes(any.value())?) } } -impl From for Attribute { - fn from(value: KeyUsage) -> Self { - // Creating a Any from a bool is really simple, so we can expect this to never fail. - let any_val = Any::from(value); +impl TryFrom for Attribute { + type Error = ConversionError; + + fn try_from(value: KeyUsages) -> Result { let mut sov = SetOfVec::new(); - // .insert() only fails if the value is not unique. We are inserting a single value, so this - // should never fail. See tests below for verification. - sov.insert(any_val).expect("Error occurred when inserting KeyUsage into der::Any to SetOfVec. Please report this crash at https://github.com/polyphony-chat/polyproto"); - Attribute { - oid: value.into(), + let bitstring = value.to_bitstring(); + sov.insert(Any::from_der(&bitstring.to_der()?)?)?; + Ok(Attribute { + oid: ObjectIdentifier::from_str(OID_KEY_USAGE)?, values: sov, - } + }) } } -impl From for Extension { - fn from(value: KeyUsage) -> Self { - Extension { - extn_id: value.into(), +impl TryFrom for Extension { + type Error = ConversionError; + fn try_from(value: KeyUsages) -> Result { + let bitstring = value.to_bitstring(); + let any = Any::from_der(&bitstring.to_der()?)?; + Ok(Extension { + extn_id: ObjectIdentifier::from_str(OID_KEY_USAGE)?, critical: true, - extn_value: OctetString::new(bool::from(value).to_der().expect("Error occured when trying to convert bool to DER. Please report this crash at https://github.com/polyphony-chat/polyproto")).expect("Error occured when trying to convert bool to an OctetString. Please report this crash at https://github.com/polyphony-chat/polyproto"), - } + extn_value: OctetString::new(any.to_der()?)?, + }) } } +#[cfg(test)] +#[allow(clippy::unwrap_used)] #[cfg(test)] mod test { use super::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] - fn key_usage_to_extension() { - let key_usage = KeyUsage::KeyCertSign(true); - let extension = Extension::from(key_usage); - dbg!(extension); + fn key_usages_to_bitstring() { + let key_usages = KeyUsages::new(&[ + KeyUsage::CrlSign, + KeyUsage::EncipherOnly, + KeyUsage::KeyAgreement, + ]); + let _bitstring = BitString::from(key_usages); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn key_usages_vec_sorts_correctly() { + #[allow(clippy::useless_vec)] + let mut vec = vec![ + KeyUsage::ContentCommitment, + KeyUsage::EncipherOnly, + KeyUsage::DataEncipherment, + KeyUsage::DigitalSignature, + ]; + vec.sort(); + assert!(*vec.first().unwrap() == KeyUsage::DigitalSignature); + assert!(*vec.get(1).unwrap() == KeyUsage::ContentCommitment); + assert!(*vec.get(2).unwrap() == KeyUsage::DataEncipherment); + assert!(*vec.get(3).unwrap() == KeyUsage::EncipherOnly); } } diff --git a/src/certs/capabilities/mod.rs b/src/certs/capabilities/mod.rs index 64beadb..2cc490c 100644 --- a/src/certs/capabilities/mod.rs +++ b/src/certs/capabilities/mod.rs @@ -16,7 +16,8 @@ use x509_cert::attr::{Attribute, Attributes}; use x509_cert::ext::{Extension, Extensions}; use crate::errors::base::InvalidInput; -use crate::{Constrained, ConstraintError}; +use crate::errors::composite::ConversionError; +use crate::Constrained; /// Object Identifier for the KeyUsage::DigitalSignature variant. pub const OID_KEY_USAGE_DIGITAL_SIGNATURE: &str = "1.3.6.1.5.5.7.3.3"; @@ -38,6 +39,8 @@ pub const OID_KEY_USAGE_ENCIPHER_ONLY: &str = "1.3.6.1.5.5.7.3.7"; pub const OID_KEY_USAGE_DECIPHER_ONLY: &str = "1.3.6.1.5.5.7.3.6"; /// Object Identifier for the BasicConstraints variant. pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19"; +/// Object Identifier for the KeyUsage flag. +pub const OID_KEY_USAGE: &str = "2.5.29.15"; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] /// Capabilities which an ID-Cert or ID-CSR might have. For ID-Certs, you'd find these capabilities @@ -48,7 +51,7 @@ pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19"; /// are relevant to polyproto certificates. pub struct Capabilities { /// The key usage extension defines the purpose of the key contained in the certificate. - pub key_usage: Vec, + pub key_usage: KeyUsages, /// Extension type that defines whether a given certificate is allowed /// to sign additional certificates and what path length restrictions may exist. pub basic_constraints: BasicConstraints, @@ -70,7 +73,7 @@ impl Capabilities { /// Sane default for actor [IdCsr]/[IdCert] [Capabilities]. Uses the DigitalSignature flag, /// not the ContentCommitment flag. pub fn default_actor() -> Self { - let key_usage = vec![KeyUsage::DigitalSignature(true)]; + let key_usage = KeyUsages::new(&[KeyUsage::DigitalSignature]); let basic_constraints = BasicConstraints { ca: false, path_length: None, @@ -83,7 +86,7 @@ impl Capabilities { /// Sane default for home server [IdCsr]/[IdCert] [Capabilities]. pub fn default_home_server() -> Self { - let key_usage = vec![KeyUsage::KeyCertSign(true)]; + let key_usage = KeyUsages::new(&[KeyUsage::KeyCertSign]); let basic_constraints = BasicConstraints { ca: true, path_length: Some(1), @@ -96,7 +99,7 @@ impl Capabilities { } impl TryFrom for Capabilities { - type Error = InvalidInput; + type Error = ConversionError; /// Performs the conversion. /// @@ -105,27 +108,19 @@ impl TryFrom for Capabilities { /// to ensure that the constraints are valid according to the X.509 standard and the polyproto /// specification. fn try_from(value: Attributes) -> Result { - let mut key_usages: Vec = Vec::new(); + let mut key_usages = KeyUsages::new(&[]); let mut basic_constraints = BasicConstraints::default(); let mut num_basic_constraints = 0u8; for item in value.iter() { match item.oid.to_string().as_str() { #[allow(unreachable_patterns)] // cargo thinks the below pattern is unreachable. - OID_KEY_USAGE_CONTENT_COMMITMENT - | OID_KEY_USAGE_CRL_SIGN - | OID_KEY_USAGE_DATA_ENCIPHERMENT - | OID_KEY_USAGE_DECIPHER_ONLY - | OID_KEY_USAGE_DIGITAL_SIGNATURE - | OID_KEY_USAGE_ENCIPHER_ONLY - | OID_KEY_USAGE_KEY_AGREEMENT - | OID_KEY_USAGE_KEY_CERT_SIGN - | OID_KEY_USAGE_KEY_ENCIPHERMENT => { - key_usages.push(KeyUsage::try_from(item.clone())?); + OID_KEY_USAGE => { + key_usages = KeyUsages::try_from(item.clone())?; } OID_BASIC_CONSTRAINTS => { num_basic_constraints += 1; if num_basic_constraints > 1 { - return Err(InvalidInput::IncompatibleVariantForConversion { reason: "Tried inserting > 1 BasicConstraints into Capabilities. Expected 1 BasicConstraints".to_string() }); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting > 1 BasicConstraints into Capabilities. Expected 1 BasicConstraints".to_string()))); } else { basic_constraints = BasicConstraints::try_from(item.clone())?; } @@ -141,44 +136,41 @@ impl TryFrom for Capabilities { } impl TryFrom for Attributes { + type Error = ConversionError; + /// Performs the conversion. /// /// Fails, if `Capabilities::verify()` using the `Constrained` trait fails. fn try_from(value: Capabilities) -> Result { value.validate()?; let mut sov = SetOfVec::new(); - for item in value.key_usage.iter() { - let insertion = sov.insert(Attribute::from(*item)); - if insertion.is_err() { - return Err(ConstraintError::Malformed(Some("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_string()))); - } + let insertion = sov.insert(Attribute::try_from(value.key_usage)?); + if insertion.is_err() { + return Err(ConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_string()))); } - let insertion = sov.insert(Attribute::from(value.basic_constraints)); + let insertion = sov.insert(Attribute::try_from(value.basic_constraints)?); if insertion.is_err() { - return Err(ConstraintError::Malformed(Some("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_string()))); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed("Tried inserting non-unique element into SetOfVec. You likely have a duplicate value in your Capabilities".to_string()))); } Ok(sov) } - - type Error = ConstraintError; } -impl From for Extensions { +impl TryFrom for Extensions { + type Error = ConversionError; /// Performs the conversion. /// /// try_from does **not** check whether the resulting [Extensions] are well-formed. - fn from(value: Capabilities) -> Self { - let mut vec = Vec::new(); - vec.push(Extension::from(value.basic_constraints)); - for item in value.key_usage.iter() { - vec.push(Extension::from(*item)); - } - vec + fn try_from(value: Capabilities) -> Result { + Ok(vec![ + Extension::try_from(value.basic_constraints)?, + Extension::try_from(value.key_usage)?, + ]) } } impl TryFrom for Capabilities { - type Error = InvalidInput; + type Error = ConversionError; /// Performs the conversion. /// @@ -187,25 +179,17 @@ impl TryFrom for Capabilities { /// these resulting [Capabilities]. fn try_from(value: Extensions) -> Result { let mut basic_constraints: BasicConstraints = BasicConstraints::default(); - let mut key_usage: Vec = Vec::new(); + let mut key_usage: KeyUsages = KeyUsages::default(); for item in value.iter() { #[allow(unreachable_patterns)] // cargo thinks that we have an unreachable pattern here match item.extn_id.to_string().as_str() { OID_BASIC_CONSTRAINTS => { basic_constraints = BasicConstraints::try_from(item.clone())? } - OID_KEY_USAGE_CONTENT_COMMITMENT - | OID_KEY_USAGE_CRL_SIGN - | OID_KEY_USAGE_DATA_ENCIPHERMENT - | OID_KEY_USAGE_DECIPHER_ONLY - | OID_KEY_USAGE_DIGITAL_SIGNATURE - | OID_KEY_USAGE_ENCIPHER_ONLY - | OID_KEY_USAGE_KEY_AGREEMENT - | OID_KEY_USAGE_KEY_CERT_SIGN - | OID_KEY_USAGE_KEY_ENCIPHERMENT => { - key_usage.push(KeyUsage::try_from(item.clone())?) + OID_KEY_USAGE => { + key_usage = KeyUsages::try_from(item.clone())? }, - _ => return Err(InvalidInput::ConstraintError(ConstraintError::Malformed(Some(format!("Invalid OID found for converting this set of Extensions to Capabilities: {} is not a valid OID for BasicConstraints or KeyUsages", item.extn_id))))) + _ => return Err(ConversionError::InvalidInput(InvalidInput::Malformed(format!("Invalid OID found for converting this set of Extensions to Capabilities: {} is not a valid OID for BasicConstraints or KeyUsages", item.extn_id)))) }; } Ok(Capabilities { @@ -215,48 +199,13 @@ impl TryFrom for Capabilities { } } +#[cfg(test)] +#[allow(clippy::unwrap_used)] #[cfg(test)] mod test { use spki::ObjectIdentifier; use super::*; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_key_usage_to_object_identifier() { - let _ = ObjectIdentifier::from(KeyUsage::DigitalSignature(true)); - let _ = ObjectIdentifier::from(KeyUsage::CrlSign(true)); - let _ = ObjectIdentifier::from(KeyUsage::ContentCommitment(true)); - let _ = ObjectIdentifier::from(KeyUsage::KeyEncipherment(true)); - let _ = ObjectIdentifier::from(KeyUsage::DataEncipherment(true)); - let _ = ObjectIdentifier::from(KeyUsage::KeyAgreement(true)); - let _ = ObjectIdentifier::from(KeyUsage::KeyCertSign(true)); - let _ = ObjectIdentifier::from(KeyUsage::EncipherOnly(true)); - let _ = ObjectIdentifier::from(KeyUsage::DecipherOnly(true)); - } - - fn test_key_usage_to_attribute(val: bool) { - let _ = Attribute::from(KeyUsage::DigitalSignature(val)); - let _ = Attribute::from(KeyUsage::CrlSign(val)); - let _ = Attribute::from(KeyUsage::ContentCommitment(val)); - let _ = Attribute::from(KeyUsage::KeyEncipherment(val)); - let _ = Attribute::from(KeyUsage::DataEncipherment(val)); - let _ = Attribute::from(KeyUsage::KeyAgreement(val)); - let _ = Attribute::from(KeyUsage::KeyCertSign(val)); - let _ = Attribute::from(KeyUsage::EncipherOnly(val)); - let _ = Attribute::from(KeyUsage::DecipherOnly(val)); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_key_usage_to_attribute_true() { - test_key_usage_to_attribute(true); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_key_usage_to_attribute_false() { - test_key_usage_to_attribute(false); - } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] @@ -275,19 +224,19 @@ mod test { ca: false, path_length: None, }; - let _ = Attribute::from(bc); + let _ = Attribute::try_from(bc).unwrap(); bc.ca = true; - let _ = Attribute::from(bc); + let _ = Attribute::try_from(bc).unwrap(); bc.path_length = Some(0); - let _ = Attribute::from(bc); + let _ = Attribute::try_from(bc).unwrap(); // Why not test all sorts of values? :3 let mut county_count = 2u64; while county_count != u64::MAX { bc.path_length = Some(county_count); - let _ = Attribute::from(bc); + let _ = Attribute::try_from(bc).unwrap(); if let Some(res) = county_count.checked_mul(2) { county_count = res; } else { @@ -307,30 +256,6 @@ mod test_key_usage_from_attribute { use super::*; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_key_usage_from_attribute() { - let key_usage = KeyUsage::ContentCommitment(true); - let attribute = Attribute::from(key_usage); - let result = KeyUsage::try_from(attribute); - dbg!(&result); - assert!(result.is_ok()); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_key_usage_wrong_value_amount() { - let key_usage = KeyUsage::ContentCommitment(true); - let mut attribute = Attribute::from(key_usage); - attribute - .values - .insert(Any::from(KeyUsage::DataEncipherment(false))) - .unwrap(); - let result = KeyUsage::try_from(attribute); - dbg!(&result); - assert!(result.is_err()); - } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_key_usage_tag_mismatch() { @@ -341,8 +266,7 @@ mod test_key_usage_from_attribute { oid: ObjectIdentifier::from_str(OID_KEY_USAGE_CONTENT_COMMITMENT).unwrap(), values: sov, }; - let result = KeyUsage::try_from(attribute); - dbg!(&result); + let result = KeyUsages::try_from(attribute); assert!(result.is_err()); } @@ -356,8 +280,7 @@ mod test_key_usage_from_attribute { oid: ObjectIdentifier::from_str("1.2.4.2.1.1.1.1.1.1.1.1.1.1.161.69").unwrap(), values: sov, }; - let result = KeyUsage::try_from(attribute); - dbg!(&result); + let result = KeyUsages::try_from(attribute); assert!(result.is_err()); } @@ -371,8 +294,7 @@ mod test_key_usage_from_attribute { oid: ObjectIdentifier::from_str(OID_KEY_USAGE_CONTENT_COMMITMENT).unwrap(), values: sov, }; - let result = KeyUsage::try_from(attribute); - dbg!(&result); + let result = KeyUsages::try_from(attribute); assert!(result.is_err()); } } @@ -395,29 +317,11 @@ mod test_basic_constraints_from_attribute { ca: true, path_length: Some(0), }; - let attribute = Attribute::from(bc); + let attribute = Attribute::try_from(bc).unwrap(); let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_ok()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn test_basic_constraints_wrong_value_amount() { - let bc = BasicConstraints { - ca: true, - path_length: Some(0), - }; - let mut attribute = Attribute::from(bc); - attribute - .values - .insert(Any::from(KeyUsage::DataEncipherment(false))) - .unwrap(); - let result = BasicConstraints::try_from(attribute); - dbg!(&result); - assert!(result.is_err()); - } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_basic_constraints_wrong_value_type() { @@ -429,7 +333,6 @@ mod test_basic_constraints_from_attribute { values: sov, }; let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_err()); } @@ -440,10 +343,9 @@ mod test_basic_constraints_from_attribute { ca: true, path_length: Some(0), }; - let mut attribute = Attribute::from(bc); + let mut attribute = Attribute::try_from(bc).unwrap(); attribute.oid = ObjectIdentifier::from_str("0.0.161.80085").unwrap(); let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_err()); } @@ -460,7 +362,6 @@ mod test_basic_constraints_from_attribute { values: sov, }; let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_err()); let mut sov = SetOfVec::new(); @@ -473,7 +374,6 @@ mod test_basic_constraints_from_attribute { values: sov, }; let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_err()); } @@ -488,7 +388,6 @@ mod test_basic_constraints_from_attribute { values: sov, }; let result = BasicConstraints::try_from(attribute); - dbg!(&result); assert!(result.is_err()); } } diff --git a/src/certs/idcert.rs b/src/certs/idcert.rs index 0db1489..b43cbd6 100644 --- a/src/certs/idcert.rs +++ b/src/certs/idcert.rs @@ -4,12 +4,12 @@ use der::asn1::Uint; use der::{Decode, Encode}; -use spki::AlgorithmIdentifierOwned; use x509_cert::name::Name; use x509_cert::time::Validity; use x509_cert::Certificate; -use crate::errors::composite::IdCertError; +use crate::errors::base::InvalidInput; +use crate::errors::composite::ConversionError; use crate::key::{PrivateKey, PublicKey}; use crate::signature::Signature; use crate::Constrained; @@ -50,16 +50,14 @@ impl> IdCert { serial_number: Uint, issuer: Name, validity: Validity, - ) -> Result { + ) -> Result { // IdCsr gets validated in IdCertTbs::from_..._csr let signature_algorithm = signing_key.algorithm_identifier(); issuer.validate()?; // TODO: Maybe this and the below validation should be done in IdCertTbs? if !equal_domain_components(&id_csr.inner_csr.subject, &issuer) { - return Err(IdCertError::ConstraintError( - crate::errors::base::ConstraintError::Malformed(Some( - "Domain components of the issuer and the subject do not match".to_string(), - )), - )); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + "Domain components of the issuer and the subject do not match".to_string(), + ))); } let id_cert_tbs = IdCertTbs::from_ca_csr(id_csr, serial_number, signature_algorithm, issuer, validity)?; @@ -84,15 +82,15 @@ impl> IdCert { serial_number: Uint, issuer: Name, validity: Validity, - ) -> Result { + ) -> Result { // IdCsr gets validated in IdCertTbs::from_..._csr let signature_algorithm = signing_key.algorithm_identifier(); issuer.validate()?; if !equal_domain_components(&id_csr.inner_csr.subject, &issuer) { - return Err(IdCertError::ConstraintError( - crate::errors::base::ConstraintError::Malformed(Some( + return Err(ConversionError::InvalidInput( + crate::errors::base::InvalidInput::Malformed( "Domain components of the issuer and the subject do not match".to_string(), - )), + ), )); } let id_cert_tbs = IdCertTbs::from_actor_csr( @@ -112,20 +110,20 @@ impl> IdCert { } /// Create an IdCsr from a byte slice containing a DER encoded X.509 Certificate. - pub fn from_der(value: Vec) -> Result { + pub fn from_der(value: Vec) -> Result { let cert = IdCert::try_from(Certificate::from_der(&value)?)?; cert.validate()?; Ok(cert) } /// Encode this type as DER, returning a byte vector. - pub fn to_der(self) -> Result, IdCertError> { + pub fn to_der(self) -> Result, ConversionError> { Ok(Certificate::try_from(self)?.to_der()?) } } impl> TryFrom> for Certificate { - type Error = IdCertError; + type Error = ConversionError; fn try_from(value: IdCert) -> Result { Ok(Self { tbs_certificate: value.id_cert_tbs.clone().try_into()?, @@ -136,7 +134,7 @@ impl> TryFrom> for Certificate { } impl> TryFrom for IdCert { - type Error = IdCertError; + type Error = ConversionError; fn try_from(value: Certificate) -> Result { let id_cert_tbs = value.tbs_certificate.try_into()?; diff --git a/src/certs/idcerttbs.rs b/src/certs/idcerttbs.rs index 797f4e7..bc03209 100644 --- a/src/certs/idcerttbs.rs +++ b/src/certs/idcerttbs.rs @@ -12,7 +12,8 @@ use x509_cert::serial_number::SerialNumber; use x509_cert::time::Validity; use x509_cert::TbsCertificate; -use crate::errors::composite::{IdCertTbsError, IdCertToTbsCert, TbsCertToIdCert}; +use crate::errors::base::InvalidInput; +use crate::errors::composite::ConversionError; use crate::key::PublicKey; use crate::signature::Signature; use crate::Constrained; @@ -74,13 +75,11 @@ impl> IdCertTbs { signature_algorithm: AlgorithmIdentifierOwned, issuer: Name, validity: Validity, - ) -> Result { + ) -> Result { if id_csr.inner_csr.capabilities.basic_constraints.ca { - return Err(IdCertTbsError::ConstraintError( - crate::errors::base::ConstraintError::Malformed(Some( - "Actor ID-Cert cannot have \"CA\" BasicConstraint set to true".to_string(), - )), - )); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + "Actor ID-Cert cannot have \"CA\" BasicConstraint set to true".to_string(), + ))); } id_csr.validate()?; issuer.validate()?; @@ -113,13 +112,11 @@ impl> IdCertTbs { signature_algorithm: AlgorithmIdentifierOwned, issuer: Name, validity: Validity, - ) -> Result { + ) -> Result { if !id_csr.inner_csr.capabilities.basic_constraints.ca { - return Err(IdCertTbsError::ConstraintError( - crate::errors::base::ConstraintError::Malformed(Some( - "CA ID-Cert must have \"CA\" BasicConstraint set to true".to_string(), - )), - )); + return Err(ConversionError::InvalidInput(InvalidInput::Malformed( + "CA ID-Cert must have \"CA\" BasicConstraint set to true".to_string(), + ))); } id_csr.validate()?; // Verify if signature of IdCsr matches contents @@ -140,12 +137,12 @@ impl> IdCertTbs { } /// Encode this type as DER, returning a byte vector. - pub fn to_der(self) -> Result, IdCertTbsError> { + pub fn to_der(self) -> Result, ConversionError> { Ok(TbsCertificate::try_from(self)?.to_der()?) } /// Create an IdCsr from a byte slice containing a DER encoded PKCS #10 CSR. - pub fn from_der(bytes: &[u8]) -> Result { + pub fn from_der(bytes: &[u8]) -> Result { IdCertTbs::try_from(TbsCertificate::from_der(bytes)?) } } @@ -153,22 +150,25 @@ impl> IdCertTbs { impl> TryFrom> for IdCertTbs { - type Error = TbsCertToIdCert; + type Error = ConversionError; fn try_from(value: TbsCertificateInner

) -> Result { value.subject.validate()?; - let capabilities = match value.extensions { - Some(ext) => Capabilities::try_from(ext)?, - None => return Err(TbsCertToIdCert::Extensions), - }; + let capabilities = + match value.extensions { + Some(ext) => Capabilities::try_from(ext)?, + None => return Err(ConversionError::InvalidInput( + crate::errors::base::InvalidInput::Malformed( + "field 'extensions' was None. Expected: Some(x509_cert::ext::Extensions)" + .to_string(), + ), + )), + }; let subject_public_key_info = PublicKey::from_public_key_info(PublicKeyInfo::from(value.subject_public_key_info)); - let serial_number = match Uint::new(value.serial_number.as_bytes()) { - Ok(snum) => snum, - Err(e) => return Err(TbsCertToIdCert::Signature(e)), - }; + let serial_number = Uint::new(value.serial_number.as_bytes())?; Ok(Self { serial_number, @@ -186,12 +186,19 @@ impl> TryFrom> impl> TryFrom> for TbsCertificateInner

{ - type Error = IdCertToTbsCert; + type Error = ConversionError; fn try_from(value: IdCertTbs) -> Result { let serial_number = match SerialNumber::

::new(value.serial_number.as_bytes()) { Ok(sernum) => sernum, - Err(e) => return Err(IdCertToTbsCert::SerialNumber(e)), + Err(e) => { + return Err(ConversionError::InvalidInput( + crate::errors::base::InvalidInput::Malformed(format!( + "Could not convert serial number: {}", + e + )), + )) + } }; let signature = AlgorithmIdentifierOwned { @@ -209,7 +216,7 @@ impl> TryFrom> subject_public_key_info: value.subject_public_key_info.public_key_info().into(), issuer_unique_id: None, subject_unique_id: None, - extensions: Some(Extensions::from(value.capabilities)), + extensions: Some(Extensions::try_from(value.capabilities)?), }) } } diff --git a/src/certs/idcsr.rs b/src/certs/idcsr.rs index 2845be1..e072b5a 100644 --- a/src/certs/idcsr.rs +++ b/src/certs/idcsr.rs @@ -10,7 +10,7 @@ use x509_cert::attr::Attributes; use x509_cert::name::Name; use x509_cert::request::{CertReq, CertReqInfo}; -use crate::errors::composite::{IdCsrError, IdCsrInnerError}; +use crate::errors::composite::ConversionError; use crate::key::{PrivateKey, PublicKey}; use crate::signature::Signature; use crate::{Constrained, ConstraintError}; @@ -62,7 +62,7 @@ impl> IdCsr { subject: &Name, signing_key: &impl PrivateKey, capabilities: &Capabilities, - ) -> Result, IdCsrError> { + ) -> Result, ConversionError> { subject.validate()?; let inner_csr = IdCsrInner::::new(subject, signing_key.pubkey(), capabilities)?; let signature = signing_key.sign(&inner_csr.clone().to_der()?); @@ -118,12 +118,12 @@ impl> IdCsr { } /// Create an IdCsr from a byte slice containing a DER encoded PKCS #10 CSR. - pub fn from_der(bytes: &[u8]) -> Result { + pub fn from_der(bytes: &[u8]) -> Result { IdCsr::try_from(CertReq::from_der(bytes)?) } /// Encode this type as DER, returning a byte vector. - pub fn to_der(self) -> Result, IdCsrError> { + pub fn to_der(self) -> Result, ConversionError> { Ok(CertReq::try_from(self)?.to_der()?) } } @@ -160,7 +160,7 @@ impl> IdCsrInner { subject: &Name, public_key: &P, capabilities: &Capabilities, - ) -> Result, IdCsrInnerError> { + ) -> Result, ConversionError> { subject.validate()?; capabilities.validate()?; @@ -177,31 +177,30 @@ impl> IdCsrInner { } /// Create an IdCsrInner from a byte slice containing a DER encoded PKCS #10 CSR. - pub fn from_der(bytes: &[u8]) -> Result { + pub fn from_der(bytes: &[u8]) -> Result { IdCsrInner::try_from(CertReqInfo::from_der(bytes)?) } /// Encode this type as DER, returning a byte vector. - pub fn to_der(self) -> Result, IdCsrInnerError> { + pub fn to_der(self) -> Result, ConversionError> { Ok(CertReqInfo::try_from(self)?.to_der()?) } } impl> TryFrom for IdCsr { - type Error = IdCsrError; + type Error = ConversionError; fn try_from(value: CertReq) -> Result { Ok(IdCsr { inner_csr: IdCsrInner::try_from(value.info)?, signature_algorithm: value.algorithm, - // TODO: raw_bytes() or as_bytes()? signature: S::from_bitstring(value.signature.raw_bytes()), }) } } impl> TryFrom for IdCsrInner { - type Error = IdCsrInnerError; + type Error = ConversionError; fn try_from(value: CertReqInfo) -> Result { let rdn_sequence = value.subject; @@ -222,7 +221,7 @@ impl> TryFrom for IdCsrInner { } impl> TryFrom> for CertReq { - type Error = IdCsrError; + type Error = ConversionError; fn try_from(value: IdCsr) -> Result { Ok(CertReq { @@ -234,7 +233,7 @@ impl> TryFrom> for CertReq { } impl> TryFrom> for CertReqInfo { - type Error = IdCsrInnerError; + type Error = ConversionError; fn try_from(value: IdCsrInner) -> Result { Ok(CertReqInfo { version: x509_cert::request::Version::V1, diff --git a/src/certs/mod.rs b/src/certs/mod.rs index 58ff84c..05377d1 100644 --- a/src/certs/mod.rs +++ b/src/certs/mod.rs @@ -112,6 +112,8 @@ impl From for SubjectPublicKeyInfoOwned { } } +/// Checks, if the domain components of two [Name]s are equal and ordered in the same way. Returns +/// `true`, if the domain components are equal, `false` otherwise. pub fn equal_domain_components(name_1: &Name, name_2: &Name) -> bool { let mut domain_components_1 = Vec::new(); let mut domain_components_2 = Vec::new(); diff --git a/src/constraints.rs b/src/constraints.rs index f5705a9..db9a78b 100644 --- a/src/constraints.rs +++ b/src/constraints.rs @@ -143,23 +143,23 @@ impl Constrained for Capabilities { let mut has_key_agreement = false; // Iterate over all the entries in the KeyUsage vector, check if they exist/are true - for item in self.key_usage.iter() { - if !has_only_encipher && item == &KeyUsage::EncipherOnly(true) { + for item in self.key_usage.key_usages.iter() { + if !has_only_encipher && item == &KeyUsage::EncipherOnly { has_only_encipher = true; } - if !has_only_decipher && item == &KeyUsage::DecipherOnly(true) { + if !has_only_decipher && item == &KeyUsage::DecipherOnly { has_only_decipher = true; } - if !has_key_agreement && item == &KeyUsage::KeyAgreement(true) { + if !has_key_agreement && item == &KeyUsage::KeyAgreement { has_key_agreement = true; } - if !has_key_agreement && item == &KeyUsage::ContentCommitment(true) { + if !has_key_agreement && item == &KeyUsage::ContentCommitment { can_commit_content = true; } - if !has_key_agreement && item == &KeyUsage::DigitalSignature(true) { + if !has_key_agreement && item == &KeyUsage::DigitalSignature { can_sign = true; } - if !has_key_agreement && item == &KeyUsage::KeyCertSign(true) { + if !has_key_agreement && item == &KeyUsage::KeyCertSign { key_cert_sign = true; } } diff --git a/src/errors/base.rs b/src/errors/base.rs index 794506e..2e89bc2 100644 --- a/src/errors/base.rs +++ b/src/errors/base.rs @@ -2,7 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use spki::ObjectIdentifier; use thiserror::Error; #[derive(Error, Debug, PartialEq, Clone)] @@ -21,27 +20,12 @@ pub enum ConstraintError { /// Represents errors for invalid input in IdCsr or IdCert generation. #[derive(Error, Debug, PartialEq, Clone)] pub enum InvalidInput { - #[error("The der library has reported the following error with the input")] - DerError(der::Error), - #[error("subject_session_id MUST NOT exceed length limit of 32 characters")] - SessionIdTooLong, - #[error( - "Cannot perform conversion, as input variant can not be converted to output. {reason:}" - )] - IncompatibleVariantForConversion { reason: String }, - #[error("Critical extension cannot be converted")] - UnknownCriticalExtension { oid: ObjectIdentifier }, - #[error(transparent)] - ConstraintError(#[from] ConstraintError), -} - -#[derive(Error, Debug, PartialEq, Clone)] -// TODO: Replace usages of InvalidInput::IncompatibleVariantForConversion with this Enum -pub enum UnsuccessfulConversion { - #[error( - "Cannot perform conversion, as input variant can not be converted to output. {reason:}" - )] - IncompatibleVariant { reason: String }, - #[error("Conversion failed due to invalid input")] - InvalidInput(String), + #[error("The value is malformed and cannot be used as input: {0}")] + Malformed(String), + #[error("The value was expected to be between {min_length:?} and {max_length:?} but was {actual_length:?}")] + Length { + min_length: usize, + max_length: usize, + actual_length: String, + }, } diff --git a/src/errors/composite.rs b/src/errors/composite.rs index 356f6c9..9172408 100644 --- a/src/errors/composite.rs +++ b/src/errors/composite.rs @@ -2,44 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use spki::ObjectIdentifier; use thiserror::Error; use super::base::{ConstraintError, InvalidInput}; -/// Error type covering possible failures when converting a [x509_cert::TbsCertificate] -/// to a [crate::cert::IdCertTbs] -#[derive(Error, Debug, PartialEq, Clone)] -pub enum TbsCertToIdCert { - #[error("field 'subject_unique_id' was None. Expected: Some(der::asn1::BitString)")] - SubjectUid, - #[error("field 'extensions' was None. Expected: Some(x509_cert::ext::Extensions)")] - Extensions, - #[error("Supplied integer too long")] - Signature(der::Error), - #[error(transparent)] - ConstraintError(#[from] ConstraintError), - #[error(transparent)] - InvalidInput(#[from] InvalidInput), - #[error("Encounter DER encoding error")] - DerError(der::Error), -} - -impl From for TbsCertToIdCert { - fn from(value: der::Error) -> Self { - Self::DerError(value) - } -} - -/// Error type covering possible failures when converting a [crate::cert::IdCertTbs] -/// to a [x509_cert::TbsCertificate] -#[derive(Error, Debug, PartialEq, Clone)] -pub enum IdCertToTbsCert { - #[error("Serial number could not be converted")] - SerialNumber(der::Error), - #[error(transparent)] - ConstraintError(#[from] ConstraintError), -} - #[derive(Error, Debug, PartialEq, Clone)] pub enum InvalidCert { #[error("The signature does not match the contents of the certificate")] @@ -54,68 +21,6 @@ pub enum InvalidCert { InvalidCapabilities(ConstraintError), } -impl From for InvalidInput { - fn from(value: der::Error) -> Self { - Self::DerError(value) - } -} - -#[derive(Error, Debug, PartialEq, Clone)] -pub enum CertReqToIdCsr { - #[error(transparent)] - CertReqInfoToIdCsrInner(#[from] CertReqInfoError), -} - -#[derive(Error, Debug, PartialEq, Clone)] -pub enum CertReqInfoError { - #[error(transparent)] - ConstraintError(#[from] ConstraintError), - #[error(transparent)] - InvalidInput(#[from] InvalidInput), - #[error("Couldn't parse CertReqInfo from provided DER bytes")] - DerError(der::Error), - #[error(transparent)] - MalformedIdCsrInner(#[from] IdCsrInnerError), -} - -impl From for CertReqInfoError { - fn from(value: der::Error) -> Self { - Self::DerError(value) - } -} - -#[derive(Error, Debug, PartialEq, Clone)] -pub enum IdCsrError { - #[error("The Bitstring is invalid")] - InvalidBitstring(der::Error), - #[error(transparent)] - IdCsrInnerToCertReqInfo(#[from] IdCsrInnerError), - #[error(transparent)] - ConstraintError(#[from] ConstraintError), -} - -impl From for IdCsrError { - fn from(value: der::Error) -> Self { - Self::InvalidBitstring(value) - } -} - -#[derive(Error, Debug, PartialEq, Clone)] -pub enum IdCsrInnerError { - #[error(transparent)] - ConstraintError(#[from] ConstraintError), - #[error("Encountered DER encoding error")] - DerError(der::Error), - #[error(transparent)] - InvalidInput(#[from] InvalidInput), -} - -impl From for IdCsrInnerError { - fn from(value: der::Error) -> Self { - Self::DerError(value) - } -} - #[derive(Error, Debug, PartialEq, Hash, Clone)] pub enum PublicKeyError { #[error("The signature does not match the data")] @@ -125,43 +30,29 @@ pub enum PublicKeyError { } #[derive(Error, Debug, PartialEq, Clone)] -pub enum IdCertTbsError { - #[error(transparent)] - PublicKeyError(#[from] PublicKeyError), +pub enum ConversionError { #[error(transparent)] ConstraintError(#[from] ConstraintError), #[error(transparent)] - IdCsrInnerError(#[from] IdCsrInnerError), - #[error(transparent)] - IdCertToTbsCert(#[from] IdCertToTbsCert), + InvalidInput(#[from] InvalidInput), #[error("Encountered DER encoding error")] DerError(der::Error), + #[error("Encountered DER OID error")] + ConstOidError(der::oid::Error), + #[error("Critical extension cannot be converted")] + UnknownCriticalExtension { oid: ObjectIdentifier }, + #[error(transparent)] + IdCertError(#[from] PublicKeyError), } -impl From for IdCertTbsError { +impl From for ConversionError { fn from(value: der::Error) -> Self { Self::DerError(value) } } -#[derive(Error, Debug, PartialEq, Clone)] -pub enum IdCertError { - #[error(transparent)] - PublicKeyError(#[from] PublicKeyError), - #[error("Encountered DER encoding error")] - DerError(der::Error), - #[error(transparent)] - IdCertTbsError(#[from] IdCertTbsError), - #[error(transparent)] - IdCertToTbsCert(#[from] IdCertToTbsCert), - #[error(transparent)] - TbsCertToIdCert(#[from] TbsCertToIdCert), - #[error(transparent)] - ConstraintError(#[from] ConstraintError), -} - -impl From for IdCertError { - fn from(value: der::Error) -> Self { - Self::DerError(value) +impl From for ConversionError { + fn from(value: der::oid::Error) -> Self { + Self::ConstOidError(value) } } diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 542f7ae..8edc686 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -7,7 +7,6 @@ pub mod base; /// "Composite" error types which consist of one or more "base" error types pub mod composite; -// TODO // PRETTYFYME // This module can be restructured to be a reflection of the src/ file tree. It would then be very // easy to tell, which file covers error types of which data types diff --git a/src/lib.rs b/src/lib.rs index 005df36..d873b61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,9 @@ pub mod errors; pub(crate) mod constraints; +pub use der; +pub use spki; + /// Traits implementing [Constrained] can be validated to be well-formed. This does not guarantee /// that a validated type will always be *correct* in the context it is in. ///